Kod kılavuzu: İşlevler ile sunucusuz uygulama

Azure Event Hubs
Azure Functions

Sunucusuz modeller, temel alınan işlem altyapısından kod soyutlar ve geliştiricilerin kapsamlı kurulum yapmadan iş mantığına odaklanmasını sağlar. Sunucusuz kod, yalnızca kod yürütme kaynakları ve süresi için ödeme yaptığınız için maliyetleri azaltır.

Sunucusuz olay odaklı model, belirli bir olayın tanımlı bir eylemi tetiklediği durumlara uyar. Örneğin, gelen cihaz iletisinin alınması daha sonra kullanmak üzere depolamayı tetikler veya veritabanı güncelleştirmesi bazı daha fazla işlem tetikler.

Microsoft, Azure'daki Azure sunucusuz teknolojilerini keşfetmenize yardımcı olmak için Azure İşlevleri kullanan sunucusuz bir uygulama geliştirdi ve test etti. Bu makalede sunucusuz İşlevler çözümünün kodunda yol gösterilir ve tasarım kararları, uygulama ayrıntıları ve karşılaşabileceğiniz bazı "gotchas" açıklanmaktadır.

Çözümü keşfedin

İki parçalı çözüm, varsayımsal bir insansız hava aracı teslimat sistemini tanımlar. Dronlar buluta uçuş durumunu gönderiyor ve bu iletiler daha sonra kullanılmak üzere depolanıyor. Web uygulaması, kullanıcıların cihazların en son durumunu almak için iletileri almasına olanak tanır.

Bu çözümün kodunu GitHub'dan indirebilirsiniz.

Bu kılavuzda aşağıdaki teknolojiler hakkında temel bilgiler olduğu varsayılır:

İşlevler veya Event Hubs konusunda uzman olmanız gerekmez ama bunların işlevlerini genel çizgileriyle anlıyor olmalısınız. Başlangıç için kullanabileceğiniz bazı yararlı kaynaklar:

Senaryoyu anlama

Diagram of the functional blocks

Fabrikam bir dron teslim hizmeti için dron filosu yönetiyor. Uygulama iki ana işlevsel alandan oluşuyor:

  • Olay alımı. Dronlar uçuş sırasında bir bulut uç noktasına durum iletileri gönderiyor. Uygulama bu iletileri alır ve işler ve sonuçları bir arka uç veritabanına (Azure Cosmos DB) yazar. Cihazlar iletileri protokol arabirimi (protobuf) biçiminde gönderiyor. Protobuf verimli, kendi kendini açıklayan bir serileştirme biçimidir.

    Bu iletiler kısmi güncelleştirmeler içeriyor. Her dron sabit aralıklarla tüm durum alanlarını içeren bir "ana çerçeve" iletisi gönderiyor. Anahtar çerçeveler arasında durum iletileri yalnızca son iletiden bu yana değişen alanları içeriyor. Bu davranış bant genişliğinden ve güçten tasarruf etmesi gereken birçok IoT cihazının tipik davranışıdır.

  • Web uygulaması. Bir web uygulaması kullanıcıların cihazı bulmasını ve cihazın son bilinen durumunu sorgulamasını sağlıyor. Kullanıcıların uygulamada oturum açması ve Microsoft Entra Id ile kimlik doğrulaması yapması gerekir. Uygulama yalnızca uygulamaya erişim yetkisi olan kullanıcıların isteklerine izin veriyor.

Burada web uygulamasının sorgunun sonucunu gösteren bir ekran görüntüsü verilmiştir:

Screenshot of client app

Uygulamayı tasarlama

Fabrikam uygulama iş mantığını gerçekleştirmek için Azure İşlevleri'ni kullanmaya karar vermişti. Azure İşlevleri, bir Hizmet Olarak İşlev" (FaaS) örneğidir. Bu bilgi işlem modelinde işlev, buluta dağıtılan ve barındırma ortamında çalışan bir kod parçasıdır. Bu barındırma ortamı kodu çalıştıran sunucuları tamamen soyutlar.

Neden sunucusuz bir yaklaşım seçmeli?

İşlevler'le sunucusuz bir mimari, olay odaklı mimarinin bir örneğidir. İşlev kodu, işlevin dışında olan bir olay tarafından tetiklenen bir durumdur. Bu durumda, bir insansız hava aracından gelen bir ileti veya istemci uygulamasından http isteği. İşlev uygulamasıyla, tetikleyici için hiçbir kod yazmanız gerekmez. Yalnızca tetikleyiciye yanıt olarak çalıştırılan kodu yazarsınız. Başka bir deyişle mesajlaşma gibi altyapıyla ilgili konuları işleyen birçok kod yazmak yerine iş mantığınıza odaklanabilirsiniz.

Sunucusuz mimari kullanmanın operasyon açısından da bazı avantajları vardır:

  • Sunucuları yönetmek gerekmez.
  • İşlem kaynakları gerektiğinde dinamik olarak ayrılır.
  • Yalnızca kodunuzu yürütmek için kullanılan işlem kaynakları için ücretlendirilirsiniz.
  • İşlem kaynakları trafiğe göre isteğe bağlı olarak ölçeklendirilir.

Mimari

Aşağıdaki diyagramda uygulamanın üst düzey mimarisi gösterilir:

Diagram showing the high-level architecture of the serverless Functions application.

Bir veri akışında oklar Cihazlar'dan Event Hubs'a giden ve işlev uygulamasını tetikleyen iletileri gösterir. Uygulamadan, bir ok bir depolama kuyruğuna giden teslim edilemeyen iletileri ve Azure Cosmos DB'ye yazmayı gösteren başka bir ok gösterir. Başka bir veri akışında oklar, istemci web uygulamasının blob depolama statik web barındırma hizmetinden CDN aracılığıyla statik dosyalar almayı gösterir. Başka bir ok, API Management üzerinden giden istemci HTTP isteğini gösterir. API Management'ta bir ok, Azure Cosmos DB'den verileri tetikleyen ve okuyan işlev uygulamasını gösterir. Başka bir ok, Microsoft Entra Id aracılığıyla kimlik doğrulamayı gösterir. Kullanıcı ayrıca Microsoft Entra Id'de oturum açar.

Olay alımı:

  1. Dron iletileri Azure Event Hubs tarafından alınır.
  2. Event Hubs ileti verilerini içeren bir olay akışı oluşturur.
  3. Bu olaylar, bunları işlemek için bir Azure İşlevleri uygulamasını tetikler.
  4. Sonuçlar Azure Cosmos DB'de depolanır.

Web uygulaması:

  1. Statik dosyalar Blob depolama alanından CDN tarafından sunulur.
  2. Bir kullanıcı, Microsoft Entra Id kullanarak web uygulamasında oturum açar.
  3. Azure API Management, REST API uç noktasını kullanıma sunan bir ağ geçidi işlevi görür.
  4. İstemciden gelen HTTP istekleri, Azure Cosmos DB'den okuyan ve sonucu döndüren bir Azure İşlevleri uygulamasını tetikler.

Bu uygulama, yukarıda açıkladığımız iki işlev bloğuna karşılık gelen iki referans mimarisine dayanır:

Bu makaleleri okuyarak üst düzey mimari, çözümde kullanılan Azure hizmetleri ve ayrıca ölçeklenebilirlik, güvenlik ve güvenilirlikle ilgili önemli noktalar hakkında daha fazla bilgi edinebilirsiniz.

Dron telemetri işlevi

Başlangıç olarak Event Hubs'dan gelen dron iletilerinin işlendiği işleve bakalım. İşlev RawTelemetryFunction adlı bir sınıfta tanımlanıyor:

namespace DroneTelemetryFunctionApp
{
    public class RawTelemetryFunction
    {
        private readonly ITelemetryProcessor telemetryProcessor;
        private readonly IStateChangeProcessor stateChangeProcessor;
        private readonly TelemetryClient telemetryClient;

        public RawTelemetryFunction(ITelemetryProcessor telemetryProcessor, IStateChangeProcessor stateChangeProcessor, TelemetryClient telemetryClient)
        {
            this.telemetryProcessor = telemetryProcessor;
            this.stateChangeProcessor = stateChangeProcessor;
            this.telemetryClient = telemetryClient;
        }
    }
    ...
}

Bu sınıfın çeşitli bağımlılıkları var ve bunlar bağımlılık alımı kullanılarak oluşturucuya alınıyor:

  • ITelemetryProcessor ve IStateChangeProcessor arabirimleri iki yardımcı nesne tanımlıyor. Daha sonra göreceğimiz gibi işin büyük bölümünü bu nesneler yapıyor.

  • TelemetryClient, Application Insights SDK'sının bir parçasıdır. Application Insights'a özel uygulama ölçümleri göndermek için kullanılır.

Daha sonra bağımlılık alımının nasıl yapılandırıldığını göreceğiz. Şimdilik bu bağımlılıkların var olduğunu varsaymamız yeterli.

Event Hubs tetikleyicisini yapılandırma

İşlevdeki mantık RunAsync adlı zaman uyumsuz bir yöntem olarak uygulanır. Yöntem imzası şöyledir:

[FunctionName("RawTelemetryFunction")]
[StorageAccount("DeadLetterStorage")]
public async Task RunAsync(
    [EventHubTrigger("%EventHubName%", Connection = "EventHubConnection", ConsumerGroup ="%EventHubConsumerGroup%")]EventData[] messages,
    [Queue("deadletterqueue")] IAsyncCollector<DeadLetterMessage> deadLetterMessages,
    ILogger logger)
{
    // implementation goes here
}

Yöntem aşağıdaki parametreleri alır:

  • messages bir dizi olay hub'ı iletisidir.
  • deadLetterMessages, teslim edilmeyen iletileri depolamak için kullanılan bir Azure Depolama Kuyruğudur.
  • logging, uygulama günlüklerine yazmak için bir günlük arabirimi sağlar. Bu günlükler Azure İzleyici'ye gönderilir.

messages parametresindeki EventHubTrigger özniteliği tetikleyiciyi yapılandırır. Özniteliğin özellikleri bir olay hub'ı adı, bağlantı dizesi ve tüketici grubu belirtir. (Tüketici grubu , Event Hubs olay akışının yalıtılmış bir görünümüdür. Bu soyutlama, aynı olay hub'ının birden çok tüketicisine olanak tanır.)

Bazı öznitelik özelliklerindeki yüzde işaretlerine (%) dikkat edin. Bunlar özelliğin bir uygulama ayarının adını belirttiğine ve asıl değerin çalışma zamanında uygulama ayarından alındığına işaret eder. Aksi takdirde, yüzde işaretleri olmazsa özellik değişmez değeri verir.

Connection özelliği özel bir durumdur. Bu özellik her zaman uygulama ayarı adı belirtir, hiçbir zaman değişmez değer belirtmez; dolayısıyla yüzde işareti gerekli değildir. Bu ayrım yapılır çünkü bağlantı dizesi bir gizli dizidir ve kaynak koduna hiçbir zaman iade edilmemesi gerekir.

Diğer iki özellik (olay hub'ı adı ve tüketici grubu) bağlantı dizesi gibi hassas veriler olmasa da, bunları sabit kodlama yerine uygulama ayarlarına koymak yine de daha iyi olacaktır. Bu şekilde, uygulamayı yeniden derlemeden bu özellikler güncelleştirilebilir.

Bu tetikleyiciyi yapılandırma hakkında daha fazla bilgi için bkz. Azure İşlevleri için Azure Event Hubs bağlamaları.

İleti işleme mantığı

Burada toplu iletileri işleyen RawTelemetryFunction.RunAsync yönteminin nasıl uygulandığı gösterilmiştir:

[FunctionName("RawTelemetryFunction")]
[StorageAccount("DeadLetterStorage")]
public async Task RunAsync(
    [EventHubTrigger("%EventHubName%", Connection = "EventHubConnection", ConsumerGroup ="%EventHubConsumerGroup%")]EventData[] messages,
    [Queue("deadletterqueue")] IAsyncCollector<DeadLetterMessage> deadLetterMessages,
    ILogger logger)
{
    telemetryClient.GetMetric("EventHubMessageBatchSize").TrackValue(messages.Length);

    foreach (var message in messages)
    {
        DeviceState deviceState = null;

        try
        {
            deviceState = telemetryProcessor.Deserialize(message.Body.Array, logger);

            try
            {
                await stateChangeProcessor.UpdateState(deviceState, logger);
            }
            catch (Exception ex)
            {
                logger.LogError(ex, "Error updating status document", deviceState);
                await deadLetterMessages.AddAsync(new DeadLetterMessage { Exception = ex, EventData = message, DeviceState = deviceState });
            }
        }
        catch (Exception ex)
        {
            logger.LogError(ex, "Error deserializing message", message.SystemProperties.PartitionKey, message.SystemProperties.SequenceNumber);
            await deadLetterMessages.AddAsync(new DeadLetterMessage { Exception = ex, EventData = message });
        }
    }
}

İşlev çağrıldığında messages parametresi olay hub'ından bir dizi ileti içerir. İletilerin toplu olarak işlenmesi genel olarak bir kerede bir ileti okumaktan daha iyi performans gösterir. Bununla birlikte işlevin dayanıklı olduğundan ve hatalarla özel durumları düzgün işlediğinden emin olmalısınız. Aksi takdirde, işlev toplu işin ortasında işlenmeyen bir özel durum oluşturursa kalan iletileri kaybedebilirsiniz. Bu konu Hata işleme bölümünde daha ayrıntılı ele alınmıştır.

Ama özel durum işlemeyi yoksayarsanız her iletinin işleme mantığı gayet basittir:

  1. Cihaz durumu değişikliğini içeren iletiyi seri durumdan çıkarmak için ITelemetryProcessor.Deserialize yöntemini çağırın.
  2. Durum değişikliğini işlemek için IStateChangeProcessor.UpdateState yöntemini çağırın.

Şimdi Deserialize yöntemiyle başlayarak bu iki yönteme daha yakından bakalım.

Deserialize yöntemi

TelemetryProcess.Deserialize yöntemi ileti yükünü içeren bir bayt dizisi alır. Bu yükü seri durumdan çıkarır ve dronun durumunu gösteren bir DeviceState nesnesi döndürür. Durum, yalnızca son bilinen durumdan sonraki değişikliği içeren bir kısmi güncelleştirmeyi temsil ediyor olabilir. Dolayısıyla yöntemin seri durumdan çıkarılmış null alanlarını işlemesi gerekir.

public class TelemetryProcessor : ITelemetryProcessor
{
    private readonly ITelemetrySerializer<DroneState> serializer;

    public TelemetryProcessor(ITelemetrySerializer<DroneState> serializer)
    {
        this.serializer = serializer;
    }

    public DeviceState Deserialize(byte[] payload, ILogger log)
    {
        DroneState restored = serializer.Deserialize(payload);

        log.LogInformation("Deserialize message for device ID {DeviceId}", restored.DeviceId);

        var deviceState = new DeviceState();
        deviceState.DeviceId = restored.DeviceId;

        if (restored.Battery != null)
        {
            deviceState.Battery = restored.Battery;
        }
        if (restored.FlightMode != null)
        {
            deviceState.FlightMode = (int)restored.FlightMode;
        }
        if (restored.Position != null)
        {
            deviceState.Latitude = restored.Position.Value.Latitude;
            deviceState.Longitude = restored.Position.Value.Longitude;
            deviceState.Altitude = restored.Position.Value.Altitude;
        }
        if (restored.Health != null)
        {
            deviceState.AccelerometerOK = restored.Health.Value.AccelerometerOK;
            deviceState.GyrometerOK = restored.Health.Value.GyrometerOK;
            deviceState.MagnetometerOK = restored.Health.Value.MagnetometerOK;
        }
        return deviceState;
    }
}

Bu yöntem ham iletiyi seri durumdan çıkarmak için başka bir yardımcı arabirim (ITelemetrySerializer<T>) kullanır. Sonra da sonuçlar, üzerinde çalışması daha kolay olan POCO modeline dönüştürülür. Bu tasarım mantığı işleme sürecini serileştirmeyi uygulama ayrıntılarından yalıtmaya yardımcı olur. ITelemetrySerializer<T> arabirimi bir paylaşılan kitaplıkta tanımlanır. Bu, cihaz simülatörü tarafından simülasyon cihazı olaylarını oluşturmak ve bunları Event Hubs'a göndermek için de kullanılır.

using System;

namespace Serverless.Serialization
{
    public interface ITelemetrySerializer<T>
    {
        T Deserialize(byte[] message);

        ArraySegment<byte> Serialize(T message);
    }
}

UpdateState yöntemi

StateChangeProcessor.UpdateState yöntemi durum değişikliklerine uygulanır. Her insansız hava aracı için bilinen son durum, Azure Cosmos DB'de bir JSON belgesi olarak depolanır. Dronlar kısmi güncelleştirmeler gönderdiğinden, uygulama bir güncelleştirme aldığında doğrudan belgenin üzerine yazamaz. Bunun yerine önceki durumu getirmesi, alanları birleştirmesi ve ardından bir upsert işlemi yapması gerekir.

public class StateChangeProcessor : IStateChangeProcessor
{
    private IDocumentClient client;
    private readonly string cosmosDBDatabase;
    private readonly string cosmosDBCollection;

    public StateChangeProcessor(IDocumentClient client, IOptions<StateChangeProcessorOptions> options)
    {
        this.client = client;
        this.cosmosDBDatabase = options.Value.COSMOSDB_DATABASE_NAME;
        this.cosmosDBCollection = options.Value.COSMOSDB_DATABASE_COL;
    }

    public async Task<ResourceResponse<Document>> UpdateState(DeviceState source, ILogger log)
    {
        log.LogInformation("Processing change message for device ID {DeviceId}", source.DeviceId);

        DeviceState target = null;

        try
        {
            var response = await client.ReadDocumentAsync(UriFactory.CreateDocumentUri(cosmosDBDatabase, cosmosDBCollection, source.DeviceId),
                                                            new RequestOptions { PartitionKey = new PartitionKey(source.DeviceId) });

            target = (DeviceState)(dynamic)response.Resource;

            // Merge properties
            target.Battery = source.Battery ?? target.Battery;
            target.FlightMode = source.FlightMode ?? target.FlightMode;
            target.Latitude = source.Latitude ?? target.Latitude;
            target.Longitude = source.Longitude ?? target.Longitude;
            target.Altitude = source.Altitude ?? target.Altitude;
            target.AccelerometerOK = source.AccelerometerOK ?? target.AccelerometerOK;
            target.GyrometerOK = source.GyrometerOK ?? target.GyrometerOK;
            target.MagnetometerOK = source.MagnetometerOK ?? target.MagnetometerOK;
        }
        catch (DocumentClientException ex)
        {
            if (ex.StatusCode == System.Net.HttpStatusCode.NotFound)
            {
                target = source;
            }
        }

        var collectionLink = UriFactory.CreateDocumentCollectionUri(cosmosDBDatabase, cosmosDBCollection);
        return await client.UpsertDocumentAsync(collectionLink, target);
    }
}

Bu kod, IDocumentClient Azure Cosmos DB'den belge getirmek için arabirimini kullanır. Belge varsa, yeni durum değerleri mevcut belgeyle birleştirilir. Aksi takdirde yeni bir belge oluşturulur. Her iki durum da UpsertDocumentAsync yöntemi tarafından işlenir.

Bu kod belgenin zaten mevcut olduğu ve birleştirilebileceği durum için iyileştirilir. Belirli bir drondan ilk telemetri iletisi geldiğinde ReadDocumentAsync yöntemi özel durum oluşturur çünkü söz konu dron için belge yoktur. İlk iletiden sonra belge sağlanacaktır.

Bu sınıfın Azure Cosmos DB için öğesini ve yapılandırma ayarlarını içeren bir IOptions<T> öğesini eklemek IDocumentClient için bağımlılık eklemeyi kullandığına dikkat edin. Bağımlılık eklemenin nasıl ayarlandığını daha sonra göreceğiz.

Dekont

Azure İşlevleri, Azure Cosmos DB için çıkış bağlamasını destekler. Bu bağlama, işlev uygulamasının Azure Cosmos DB'de herhangi bir kod olmadan belge yazmasına olanak tanır. Öte yandan çıkış bağlama gereken özel upsert mantığından dolayı buradaki senaryoda çalışmaz.

Hata işleme

Daha önce de belirtildiği gibi RawTelemetryFunction işlev uygulaması iletileri toplu olarak bir döngüde işler. Başka bir deyişle işlevin tüm özel durumları düzgün işlemesi ve toplu işin kalan bölümünü işlemeye devam etmesi gerekir. Aksi takdirde iletiler bırakılabilir.

İleti işlenirken bir özel durumla karşılaşılırsa, işlev iletiyi teslim edilmeyen ileti kuyruğuna yerleştirir:

catch (Exception ex)
{
    logger.LogError(ex, "Error deserializing message", message.SystemProperties.PartitionKey, message.SystemProperties.SequenceNumber);
    await deadLetterMessages.AddAsync(new DeadLetterMessage { Exception = ex, EventData = message });
}

Teslim edilmeyen ileti kuyruğu, depolama kuyruğuna bir çıkış bağlaması kullanılarak tanımlanır:

[FunctionName("RawTelemetryFunction")]
[StorageAccount("DeadLetterStorage")]  // App setting that holds the connection string
public async Task RunAsync(
    [EventHubTrigger("%EventHubName%", Connection = "EventHubConnection", ConsumerGroup ="%EventHubConsumerGroup%")]EventData[] messages,
    [Queue("deadletterqueue")] IAsyncCollector<DeadLetterMessage> deadLetterMessages,  // output binding
    ILogger logger)

Burada Queue özniteliği çıkış bağlamasını ve StorageAccount özniteliği de depolama hesabının bağlantı dizesini barındıran uygulama ayarının adını belirtir.

Dağıtım ipucu: Depolama hesabını oluşturan Resource Manager şablonunda, bir uygulama ayarını bağlantı dizesi ile otomatik olarak doldurabilirsiniz. İşin püf noktası listKeys işlevini kullanmaktır.

İşte şablonun kuyruk için depolama hesabını oluşturan bölümü:

    {
        "name": "[variables('droneTelemetryDeadLetterStorageQueueAccountName')]",
        "type": "Microsoft.Storage/storageAccounts",
        "location": "[resourceGroup().location]",
        "apiVersion": "2017-10-01",
        "sku": {
            "name": "[parameters('storageAccountType')]"
        },

İşte şablonun işlev uygulamasını oluşturan bölümü:


    {
        "apiVersion": "2015-08-01",
        "type": "Microsoft.Web/sites",
        "name": "[variables('droneTelemetryFunctionAppName')]",
        "location": "[resourceGroup().location]",
        "tags": {
            "displayName": "Drone Telemetry Function App"
        },
        "kind": "functionapp",
        "dependsOn": [
            "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]",
            ...
        ],
        "properties": {
            "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]",
            "siteConfig": {
                "appSettings": [
                    {
                        "name": "DeadLetterStorage",
                        "value": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('droneTelemetryDeadLetterStorageQueueAccountName'), ';AccountKey=', listKeys(variables('droneTelemetryDeadLetterStorageQueueAccountId'),'2015-05-01-preview').key1)]"
                    },
                    ...

Bu DeadLetterStorage adlı, değeri listKeys işlevi kullanılarak doldurulan bir uygulama ayarını tanımlar. İşlev uygulaması kaynağının depolama hesabı kaynağına bağımlı yapılması önemlidir (dependsOn öğesine bakın). Bu sayede depolama hesabının önce oluşturulması ve bağlantı dizesinin sağlanması garanti edilir.

Bağımlılık eklemeyi ayarlama

Aşağıdaki kod RawTelemetryFunction işlevi için bağımlılık eklemeyi ayarlar:

[assembly: FunctionsStartup(typeof(DroneTelemetryFunctionApp.Startup))]

namespace DroneTelemetryFunctionApp
{
    public class Startup : FunctionsStartup
    {
        public override void Configure(IFunctionsHostBuilder builder)
        {
            builder.Services.AddOptions<StateChangeProcessorOptions>()
                .Configure<IConfiguration>((configSection, configuration) =>
                {
                    configuration.Bind(configSection);
                });

            builder.Services.AddTransient<ITelemetrySerializer<DroneState>, TelemetrySerializer<DroneState>>();
            builder.Services.AddTransient<ITelemetryProcessor, TelemetryProcessor>();
            builder.Services.AddTransient<IStateChangeProcessor, StateChangeProcessor>();

            builder.Services.AddSingleton<IDocumentClient>(ctx => {
                var config = ctx.GetService<IConfiguration>();
                var cosmosDBEndpoint = config.GetValue<string>("CosmosDBEndpoint");
                var cosmosDBKey = config.GetValue<string>("CosmosDBKey");
                return new DocumentClient(new Uri(cosmosDBEndpoint), cosmosDBKey);
            });
        }
    }
}

.NET için yazılmış Azure İşlevleri ASP.NET Core bağımlılık ekleme çerçevesini kullanabilir. Temel fikir derlemeniz için bir başlatma yöntemi bildirmenizdir. Yöntem, DI için bağımlılıkları bildirirken kullanılan bir IFunctionsHostBuilder arabirimi alır. Bunu yapmak için Services nesnesinde Add* yöntemini çağırırsınız. Bağımlılığı eklerken yaşam süresini belirtirsiniz:

  • Geçici nesneler her istendiğinde oluşturulur.
  • Kapsamlı nesneler her işlev yürütmesi için bir kez oluşturulur.
  • Tekil nesneler işlev konağının yaşam süresi içinde işlev yürütmeleri arasında yeniden kullanılır.

Bu örnekte TelemetryProcessor ve StateChangeProcessor nesneleri geçici olarak bildirilmiştir. Bu basit, durum bilgisi olmayan hizmetlere uygundur. Öte yandan en iyi performansı elde etmek için DocumentClient sınıfının tekil olması gerekir. Daha fazla bilgi için bkz. Azure Cosmos DB ve .NET için performans ipuçları.

RawTelemetryFunction için yeniden koda dönerseniz, orada DI kurulum kodunda gösterilmeyen başka bir bağımlılık görürsünüz. Bu bağımlılık, uygulama ölçümlerini günlüğe kaydetmek için kullanılan TelemetryClient sınıfıdır. İşlevler'in çalışma zamanı bu sınıfı DI kapsayıcısına otomatik olarak kaydeder, bu nedenle sizin açıkça kaydetmeniz gerekmez.

Azure İşlevleri'ndeki DI hakkında daha fazla bilgi edinmek için aşağıdaki makalelere bakın:

DI'de yapılandırma ayarlarını geçirme

Bazen nesnenin bazı yapılandırma değerleriyle başlatılması gerekir. Genellikle bu ayarlar uygulama ayarlarından veya (gizli diziler söz konusu olduğunda) Azure Key Vault'tan gelmelidir.

Bu uygulamada iki örnek vardır. İlk olarak, DocumentClient sınıfı bir Azure Cosmos DB hizmet uç noktası ve anahtarı alır. Bu nesne için uygulama DI kapsayıcısı tarafından çağrılacak bir lambda kaydeder. Bu lambda yapılandırma değerlerini okumak için IConfiguration arabirimini kullanır:

builder.Services.AddSingleton<IDocumentClient>(ctx => {
    var config = ctx.GetService<IConfiguration>();
    var cosmosDBEndpoint = config.GetValue<string>("CosmosDBEndpoint");
    var cosmosDBKey = config.GetValue<string>("CosmosDBKey");
    return new DocumentClient(new Uri(cosmosDBEndpoint), cosmosDBKey);
});

İkinci örnek StateChangeProcessor sınıfıdır. Bu nesne için seçenek deseni olarak adlandırılan bir yaklaşım kullanırız. İşleyişi şu şekildedir:

  1. Yapılandırma ayarlarınızı içeren bir T sınıfı tanımlayın. Bu durumda, Azure Cosmos DB veritabanı adı ve koleksiyon adı.

    public class StateChangeProcessorOptions
    {
        public string COSMOSDB_DATABASE_NAME { get; set; }
        public string COSMOSDB_DATABASE_COL { get; set; }
    }
    
  2. T sınıfını DI için bir seçenek sınıfı olarak ekleyin.

    builder.Services.AddOptions<StateChangeProcessorOptions>()
        .Configure<IConfiguration>((configSection, configuration) =>
        {
            configuration.Bind(configSection);
        });
    
  3. Yapılandırılmakta olan sınıfın oluşturucusuna bir IOptions<T> parametresi ekleyin.

    public StateChangeProcessor(IDocumentClient client, IOptions<StateChangeProcessorOptions> options)
    

DI sistemi seçenek sınıfını yapılandırma değerleriyle otomatik olarak doldurur ve bunu oluşturucuya geçirir.

Bu yaklaşımın çeşitli avantajları vardır:

  • Sınıf, yapılandırma değerlerinin kaynağından ayrılır.
  • Ortam değişkenleri veya JSON yapılandırma dosyaları gibi farklı yapılandırma kaynakları kolayca ayarlanır.
  • Birim testi basitleştirilir.
  • Yalnızca skaler değerler geçirmekle karşılaştırıldığında hataya daha az eğilimli olan türü güçlü bir şekilde belirtilmiş bir seçenek sınıfı kullanılır.

GetStatus işlevi

Bu çözümdeki diğer İşlevler uygulaması, dronun son bilinen durumunu almak için basit bir REST API kullanır. Bu işlev GetStatusFunction adlı bir sınıfta tanımlanır. Burada işlevin tam kodu verilmiştir:

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Extensions.Logging;
using System.Security.Claims;
using System.Threading.Tasks;

namespace DroneStatusFunctionApp
{
    public static class GetStatusFunction
    {
        public const string GetDeviceStatusRoleName = "GetStatus";

        [FunctionName("GetStatusFunction")]
        public static IActionResult Run(
            [HttpTrigger(AuthorizationLevel.Function, "get", Route = null)]HttpRequest req,
            [CosmosDB(
                databaseName: "%COSMOSDB_DATABASE_NAME%",
                collectionName: "%COSMOSDB_DATABASE_COL%",
                ConnectionStringSetting = "COSMOSDB_CONNECTION_STRING",
                Id = "{Query.deviceId}",
                PartitionKey = "{Query.deviceId}")] dynamic deviceStatus,
            ClaimsPrincipal principal,
            ILogger log)
        {
            log.LogInformation("Processing GetStatus request.");

            if (!principal.IsAuthorizedByRoles(new[] { GetDeviceStatusRoleName }, log))
            {
                return new UnauthorizedResult();
            }

            string deviceId = req.Query["deviceId"];
            if (deviceId == null)
            {
                return new BadRequestObjectResult("Missing DeviceId");
            }

            if (deviceStatus == null)
            {
                return new NotFoundResult();
            }
            else
            {
                return new OkObjectResult(deviceStatus);
            }
        }
    }
}

Bu işlev HTTP GET isteğini işlemek için bir HTTP tetikleyicisi kullanır. İşlev, istenen belgeyi getirmek için bir Azure Cosmos DB giriş bağlaması kullanır. Bu bağlayıcının, işlev içinde gerçekleştirilen yetkilendirme mantığından önce çalıştırılacağına dikkat edilmelidir. Yetkisiz bir kullanıcı belge isteğinde bulunursa, işlev bağlaması yine de belgeyi getirir. Ardından yetkilendirme kodu bir 401 hatası döndürür, dolayısıyla kullanıcı belgeyi göremez. Bu davranışın kabul edilir olup olmadığı, sizin gereksinimlerinize bağlıdır. Örneğin bu yaklaşım hassas veriler için veri erişiminin denetlenmesini zorlaştırabilir.

Kimlik doğrulaması ve yetkilendirme

Web uygulaması, kullanıcıların kimliğini doğrulamak için Microsoft Entra Id kullanır. Bu tarayıcıda çalışan tek sayfalı bir uygulama (SPA) olduğundan örtük onay verme akışı uygun olur:

  1. Web uygulaması kullanıcıyı kimlik sağlayıcısına (bu örnekte Microsoft Entra Id) yönlendirir.
  2. Kullanıcı kimlik bilgilerini girer.
  3. Kimlik sağlayıcısı bir erişim belirteciyle geriye web uygulamasına yönlendirir.
  4. Web uygulaması isteği web API'sine gönderir ve erişim belirtecini Yetkilendirme üst bilgisine ekler.

Implicit flow diagram

Sıfır kodla kullanıcıların kimliğini doğrulamak için bir İşlev uygulaması yapılandırılabilir. Daha fazla bilgi için bkz. Azure App Service’de kimlik doğrulama ve yetkilendirme.

Öte yandan yetkilendirme için genel olarak bir iş mantığı gerekir. Microsoft Entra ID, talep tabanlı kimlik doğrulamayı destekler. Bu modelde kullanıcının kimliği kimlik sağlayıcısından gelen bir dizi beyanla gösterilir. Beyan kullanıcı hakkında herhangi bir bilgi parçası, örneğin adı veya e-posta adresi olabilir.

Erişim belirteci, kullanıcı beyanlarının bir alt kümesini içerir. Kullanıcının atandığı uygulama rolleri de bunlar arasındadır.

İşlevin principal parametresi, erişim belirtecindeki beyanları içeren bir ClaimsPrincipal nesnesidir. Her beyan, beyan türü ile beyan değerinden oluşan bir anahtar/değer çiftidir. Uygulama, isteği yetkilendirmek için bunları kullanır.

Aşağıdaki uzantı yöntemi ClaimsPrincipal nesnesinin bir rol kümesi içerip içermediğini test eder. Belirtilen rollerden herhangi biri eksikse false döndürür. Bu yöntem false döndürürse işlev HTTP 401 (Yetkisiz) döndürür.

namespace DroneStatusFunctionApp
{
    public static class ClaimsPrincipalAuthorizationExtensions
    {
        public static bool IsAuthorizedByRoles(
            this ClaimsPrincipal principal,
            string[] roles,
            ILogger log)
        {
            var principalRoles = new HashSet<string>(principal.Claims.Where(kvp => kvp.Type == "roles").Select(kvp => kvp.Value));
            var missingRoles = roles.Where(r => !principalRoles.Contains(r)).ToArray();
            if (missingRoles.Length > 0)
            {
                log.LogWarning("The principal does not have the required {roles}", string.Join(", ", missingRoles));
                return false;
            }

            return true;
        }
    }
}

Bu uygulamada kimlik doğrulaması ve yetkilendirme hakkında daha fazla bilgi için referans mimarisinin Güvenlikle ilgili dikkat edilmesi gerekenler bölümüne bakın.

Sonraki adımlar

Bu başvuru çözümünün nasıl çalıştığı hakkında bilgi edindikten sonra, benzer çözümler için en iyi yöntemleri ve önerileri öğrenin.

  • Sunucusuz olay alımı çözümü için bkz. Azure İşlevleri kullanarak sunucusuz olay işleme.
  • Sunucusuz bir web uygulaması için bkz . Azure'da sunucusuz web uygulaması.

Azure İşlevleri yalnızca bir Azure işlem seçeneğidir. İşlem teknolojisi seçme konusunda yardım için bkz . Uygulamanız için azure işlem hizmeti seçme.