A kiszolgáló nélküli modellek absztrakciós kódját a mögöttes számítási infrastruktúra teszi lehetővé, hogy a fejlesztők átfogó beállítás nélkül összpontosítsanak az üzleti logikára. A kiszolgáló nélküli kód csökkenti a költségeket, mivel csak a kódvégrehajtási erőforrásokért és az időtartamért kell fizetnie.
A kiszolgáló nélküli eseményvezérelt modell megfelel azoknak a helyzeteknek, amikor egy adott esemény egy meghatározott műveletet indít el. A bejövő eszközüzenetek fogadása például elindítja a tárterületet későbbi használatra, vagy egy adatbázis-frissítés további feldolgozást indít el.
Az Azure kiszolgáló nélküli technológiáinak megismeréséhez a Microsoft kifejlesztett és tesztelt egy Azure Functionst használó kiszolgáló nélküli alkalmazást. Ez a cikk végigvezeti a kiszolgáló nélküli Functions-megoldás kódját, és ismerteti a tervezési döntéseket, a megvalósítás részleteit és néhány lehetséges "gotchas"-t.
A megoldás megismerése
A kétrészes megoldás egy hipotetikus drónkézbesítési rendszert ír le. A drónok elküldik a felhőbe a repülési állapotukat, ahol ezek az üzenetek későbbi felhasználásra tárolva lesznek. A webalkalmazások segítségével a felhasználók lekérhetik az üzeneteket az eszközök legújabb állapotának lekéréséhez.
A megoldás kódját a GitHubról töltheti le.
Ez az útmutató a következő technológiák alapszintű ismeretét feltételezi:
Nem kell Functions- vagy az Event Hubs-szakértőnek lennie, de jól kell ismernie a funkcióikat. Hasznos forrásanyagok az első lépésekhez:
A forgatókönyv megismerése
A Fabrikam egy drónokból álló flottát kezel egy drónos házhozszállítási szolgáltatás részeként. Az alkalmazás két fő funkcionális területtel rendelkezik:
Eseménybetöltés. Repülés közben a drónok állapotüzeneteket küldenek a felhővégpontokra. Az alkalmazás betölti és feldolgozza ezeket az üzeneteket, és az eredményeket egy háttéradatbázisba (Azure Cosmos DB) írja. Az eszközök üzeneteket küldenek protokollpuffer (protobuf) formátumban. A protobuf egy hatékony, önleíró szerializálási formátum.
Ezek az üzenetek részleges frissítéseket tartalmaznak. Rögzített időközönként minden egyes drón „kulcskeret” üzenetet küld, amely az összes állapotmezőt tartalmazza. Az állapotüzenetekben csak olyan mezők szerepelnek kulcskeretek között, amelyek az előző üzenet óta változtak. Ez a viselkedés számos olyan IoT-eszközre jellemző, amelynek sávszélességet és teljesítményt kell megtakarítania.
Webalkalmazás. A webalkalmazás lehetővé teszi a felhasználóknak, hogy megkeressenek egy eszközt, és lekérdezzék az utolsó ismert állapotát. A felhasználóknak be kell jelentkezniük az alkalmazásba, és hitelesíteni kell magukat a Microsoft Entra-azonosítóval. Az alkalmazás kizárólag azoktól a felhasználóktól fogad el kérelmeket, akik jogosultak az alkalmazás használatára.
Íme egy, a lekérdezés eredményét megjelenítő képernyőkép a webalkalmazásról:
Az alkalmazás megtervezése
A Fabrikam úgy döntött, hogy az Azure Functionst fogja használni az alkalmazás üzleti logikájának implementálásához. Az Azure Functions példa a „szolgáltatott függvényekre” (FaaS). Ebben a számítási modellben a függvény egy kódrészlet, amely a felhőben van üzembe helyezve, és üzemeltetési környezetben fut. Az üzemeltetési környezet teljes mértékben absztrahálja a kódokat futtató kiszolgálókat.
Miért válasszon kiszolgáló nélküli megközelítést?
Az eseményvezérelt architektúrára példa a Functionsszel létrehozott kiszolgáló nélküli architektúra. A függvénykódot egy, a függvényen kívüli esemény aktiválja – ebben az esetben egy drón üzenete vagy egy ügyfélalkalmazás HTTP-kérése. Függvényalkalmazással Önnek nem kell kódot írnia a triggerhez. Csak a triggerre adott válasz kódját kell megírnia. Ez azt jelenti, hogy az üzleti logikára összpontosíthat, ahelyett, hogy sok kódot kellene írnia az infrastrukturális funkciók, például az üzenetek kezeléséhez.
A kiszolgáló nélküli architektúra használatának van néhány működést érintő előnye:
- Nincs szükség a kiszolgálók kezelésére.
- A számítási erőforrások lefoglalása dinamikusan, igény szerint történik.
- Kizárólag a kód végrehajtásához felhasznált számítási erőforrások után kell fizetni.
- A számítási erőforrások igény szerint skálázhatók a forgalomtól függően.
Architektúra
A következő ábrán az alkalmazás magas szintű architektúrája látható:
Egy adatfolyamban a nyilak az eszközökről az Event Hubsra érkező üzeneteket jelenítik meg, és aktiválják a függvényalkalmazást. Az alkalmazásból az egyik nyíl egy tárolási üzenetsorba, egy másik pedig az Azure Cosmos DB-be való írást mutatja. Egy másik adatfolyamban a nyilak azt mutatják, hogy az ügyfél webalkalmazás statikus fájlokat kap a Blob Storage statikus webszolgáltatásától egy CDN-en keresztül. Egy másik nyíl az ÜGYFÉL HTTP-kérését jeleníti meg az API Managementen keresztül. Az API Managementben egy nyíl mutatja az Azure Cosmos DB-ből származó adatokat aktiváló és olvasó függvényalkalmazást. Egy másik nyíl a Microsoft Entra-azonosítón keresztüli hitelesítést mutatja. Egy felhasználó is bejelentkezik a Microsoft Entra-azonosítóba.
Eseménybetöltés:
- Az Azure Event Hubs feldolgozza a drónok üzeneteit.
- Az Event Hubs olyan eseménystreameket állít elő, amelyek tartalmazzák az üzenetadatokat.
- Ezek az események aktiválnak egy Azure Functions-alkalmazást a feldolgozásukhoz.
- Az eredmények tárolása az Azure Cosmos DB-ben történik.
Webalkalmazás:
- A statikus fájlokat a Blob Storage CDN-je üzemelteti.
- A felhasználó a Microsoft Entra ID használatával jelentkezik be a webalkalmazásba.
- Az Azure API Management átjáróként szolgál, amely egy REST API-végpontot tesz elérhetővé.
- Az ügyfél HTTP-kérései aktiválnak egy Azure Functions-alkalmazást, amely az Azure Cosmos DB-ből olvas, és visszaadja az eredményt.
Ez az alkalmazás két referenciaarchitektúrán alapszik, a fent leírt két funkcionális blokknak megfelelően:
- Kiszolgáló nélküli eseményfeldolgozás az Azure Functions használatával
- Kiszolgáló nélküli webalkalmazás az Azure-on
Ezekből a cikkekből többet tudhat meg a magas szintű architektúráról, a megoldásban használt Azure-szolgáltatásokról és a méretezhetőségre, biztonságra és megbízhatóságra vonatkozó megfontolandó szempontokról.
Dróntelemetria függvény
Elsőként nézzük meg a függvényt, amely az Event Hubstól érkező drónüzeneteket dolgozza fel. A függvényt egy RawTelemetryFunction
nevű osztály definiálja:
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;
}
}
...
}
Ez az osztály több függőséggel rendelkezik, amelyeket a rendszer függőséginjektálással ad át a konstruktornak:
Az
ITelemetryProcessor
ésIStateChangeProcessor
interfészek két segédobjektumot határoznak meg. Mint rövidesen látni fogjuk, ezek az objektumok a legtöbb feladatot maguktól elvégzik.A TelemetryClient az Application Insights SDK része. A használatával egyéni alkalmazásmetrikákat lehet küldeni az Application Insightsnak.
Később megnézzük, hogyan konfigurálhatja a függőséginjektálást. Egyelőre csak feltételezzük, hogy ezek a függőségek léteznek.
Event Hubs-trigger konfigurálása
A függvény logikáját egy RunAsync
nevű aszinkron metódusként implementáljuk. Íme a metódus aláírása:
[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
}
A metódus a következő paramétereket veszi fel:
- A
messages
az eseményközpont-üzenetek tömbje. - A
deadLetterMessages
egy Azure Storage-üzenetsor, amely a kézbesíthetetlen üzenetek tárolására szolgál. - A
logging
egy naplózási felületet kínál az alkalmazásnaplók megírásához. Ezeket a naplókat a rendszer elküldi az Azure Monitornak.
A messages
paraméter EventHubTrigger
attribútuma konfigurálja az eseményindítót. Az attribútum tulajdonságai meghatározzák az eseményközpont nevét, a kapcsolati sztringet és a fogyasztói csoportot. (A fogyasztói csoport az Event Hubs eseménystreamjének izolált nézete. Ez az absztrakció lehetővé teszi ugyanazon eseményközpont több felhasználójának is.)
Látható, hogy egyes attribútumtulajdonságok százalékjeleket (%) tartalmaznak. Ezek azt jelzik, hogy a tulajdonság egy alkalmazásbeállítás nevét határozza meg, és a rendszer az adott alkalmazásbeállításból kéri le az értéket futásidőben. A százalékjelek nélküli tulajdonságok konstansértékeket adnak meg.
A Connection
tulajdonság kivétel ez alól. Ez a tulajdonság mindig egy alkalmazásbeállítás nevét határozza meg, sosem a szövegkonstansértéket, így a százalékjel használata nem szükséges. Ennek a különbségnek az az oka, hogy a kapcsolati sztring titkos, és sosem kerül be a forráskódba.
Míg a másik két tulajdonság (az eseményközpont neve és a fogyasztói csoport) a kapcsolati sztringgel ellentétben nem bizalmas adat, ajánlott őket rögzített megadás helyett alkalmazásbeállításokban megadni. Ily módon az alkalmazás újrafordítása nélkül frissíthetők.
További információ az eseményindító konfigurálásáról: Azure Event Hubs-kötések az Azure Functionshöz.
Üzenetfeldolgozási logika
Az üzenetkötegeket feldolgozó RawTelemetryFunction.RunAsync
metódus a következőképpen implementálható:
[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 });
}
}
}
A függvény hívásakor a messages
paraméter az eseményközpontból származó üzenetek tömbjét tartalmazza. Az üzenetek kötegenkénti feldolgozása jobb teljesítményt eredményez, mint az üzenetek egyesével történő elolvasása. Azonban meg kell győződnie arról, hogy a függvény rugalmas, és könnyedén kezeli a hibákat és a kivételeket. Ha a függvény nem kezelt kivételt dob fel kötegelés közben, elveszhet a többi üzenet. A téma további részletei a Hibakezelés szakaszban találhatók.
Ha figyelmen kívül hagyja a kivételkezelést, a szabályfeldolgozási logika minden üzenet esetén egyszerű:
- Hívja meg az
ITelemetryProcessor.Deserialize
metódust az eszközállapot-változást tartalmazó üzenet deszerializálásához. - Hívja meg az
IStateChangeProcessor.UpdateState
metódust az állapotváltozás feldolgozásához.
Vizsgáljuk meg ezt a két metódust részletesebben, kezdve a Deserialize
metódussal.
Deserialize metódus
A TelemetryProcess.Deserialize
metódus egy olyan bájttömböt vesz fel, amely tartalmazza az üzenetek hasznos adatait. Deszerializálja ezeket a hasznos adatokat, majd egy DeviceState
objektumot ad vissza, amely a drón állapotát jelöli. Az állapot részleges frissítést jelezhet, amely esetben csak az utolsó ismert állapothoz mért eltérést tartalmazza. Ezért a metódusnak kezelnie kell a null
mezőket a deszerializált hasznos adatokban.
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;
}
}
Ez a metódus egy másik, ITelemetrySerializer<T>
nevű segédfelületet használ a nyers üzenet deszerializálásához. Az eredményeket utána POCO-modellé alakítja át, amely egyszerűbben használható. Ez a kialakítás segít izolálni a szabályfeldolgozási logikát a szerializációs implementáció részleteitől. Az ITelemetrySerializer<T>
interfész egy megosztott kódtárban van meghatározva, amelyet az eszközszimulátor is használ szimulált eszközesemények létrehozására, majd továbbítja őket az Event Hubsba.
using System;
namespace Serverless.Serialization
{
public interface ITelemetrySerializer<T>
{
T Deserialize(byte[] message);
ArraySegment<byte> Serialize(T message);
}
}
UpdateState metódus
A StateChangeProcessor.UpdateState
metódus alkalmazza az állapotváltozásokat. Az egyes drónok utolsó ismert állapota JSON-dokumentumként van tárolva az Azure Cosmos DB-ben. Mivel a drónok részleges frissítéseket küldenek, az alkalmazás nem tudja egyszerűen felülírni a dokumentumot frissítés közben. Ehelyett le kell kérnie az előző állapotot, egyesítenie kell a mezőket, és egy upsert (frissítés/beszúrás) műveletet kell végrehajtania.
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);
}
}
Ez a kód a IDocumentClient
felület használatával lekéri a dokumentumot az Azure Cosmos DB-ből. Ha a dokumentum létezik, a rendszer egyesíti az új állapotértékeket a meglévő dokumentummal. Ellenkező esetben egy új dokumentum jön létre. Mindkét esetet az UpsertDocumentAsync
metódus kezeli.
Ez a kód optimalizálva van arra az esetre, ha a dokumentum már létezik és egyesíthető. Egy adott dróntól érkező első telemetriai üzenetnél a ReadDocumentAsync
metódus kivételt ad vissza, mivel még nem található dokumentum az adott drónhoz. Az első üzenet után a dokumentum elérhető lesz.
Figyelje meg, hogy ez az osztály függőséginjektálással injektálja az IDocumentClient
Azure Cosmos DB-t és egy IOptions<T>
konfigurációs beállításokat. A függőséginjektálás beállítását később tekintjük át.
Megjegyzés:
Az Azure Functions támogatja az Azure Cosmos DB kimeneti kötését. Ez a kötés lehetővé teszi, hogy a függvényalkalmazás kód nélkül írjon dokumentumokat az Azure Cosmos DB-ben. A kimeneti kötés azonban a szükséges egyéni upsert logika miatt nem fog működni ebben a forgatókönyvben.
Hibakezelés
Ahogy korábban említettük, a RawTelemetryFunction
függvényalkalmazás egy hurokban dolgoz fel egy üzenetköteget. Ez azt jelenti, hogy a függvénynek minden kivételt könnyedén kell kezelnie, és folytatnia kell a köteg hátralevő részének feldolgozását. Ellenkező esetben a rendszer elvetheti az üzeneteket.
Ha a rendszer kivételt észlelt egy üzenet feldolgozása során, a függvény a kézbesíthetetlen levelek sorába helyezi az üzenetet:
catch (Exception ex)
{
logger.LogError(ex, "Error deserializing message", message.SystemProperties.PartitionKey, message.SystemProperties.SequenceNumber);
await deadLetterMessages.AddAsync(new DeadLetterMessage { Exception = ex, EventData = message });
}
A kézbesíthetetlen levelek sora egy tárolási üzenetsorra mutató kimeneti kötéssel van meghatározva:
[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)
Itt a Queue
attribútum a kimeneti kötést, a StorageAccount
attribútum pedig a tárfiók kapcsolati sztringjét tartalmazó alkalmazásbeállítás nevét határozza meg.
Üzembe helyezési tipp: A tárfiókot létrehozó Resource Manager-sablonban automatikusan feltölthet egy alkalmazásbeállítást a kapcsolati sztring. A trükk a listKeys függvény használata.
Itt látható az a sablonszakasz, amely létrehozza a tárfiókot a sorhoz:
{
"name": "[variables('droneTelemetryDeadLetterStorageQueueAccountName')]",
"type": "Microsoft.Storage/storageAccounts",
"location": "[resourceGroup().location]",
"apiVersion": "2017-10-01",
"sku": {
"name": "[parameters('storageAccountType')]"
},
Ez pedig a függvényalkalmazást létrehozó sablonszakasz.
{
"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)]"
},
...
Ez egy DeadLetterStorage
nevű alkalmazásbeállítást határoz meg, amelynek értékét a listKeys
függvénnyel lehet megadni. Fontos, hogy a függvényalkalmazás erőforrása a tárfiók erőforrásától függjön (lásd a dependsOn
elemet). Ez garantálja, hogy először a tárfiók jöjjön létre, és a kapcsolati sztring elérhető legyen.
Függőséginjektálás beállítása
A következő kód beállítja a RawTelemetryFunction
függvény függőséginjektálását:
[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);
});
}
}
}
A .NET-hez írt Azure Functions-függvények használhatják az ASP.NET Core függőséginjektálási keretrendszert. Az alapvető elképzelés az, hogy a felhasználó meghatározza a szerelvény indítási metódusát. A metódus egy IFunctionsHostBuilder
interfészt használ, amellyel a függőséginjektálás függőségei határozhatók meg. Ehhez be kell hívni az Add*
metódust a Services
objektumon. Függőség hozzáadásakor meg kell határozni annak élettartamát:
- Átmeneti objektumok akkor jönnek létre, amikor ezek létrehozását kéri.
- Hatókörrel rendelkező objektumok függvényvégrehajtásonként egyszer jönnek létre.
- Az egyedülálló objektumokat a rendszer újra felhasználja a függvényvégrehajtásokban a függvénygazdagép élettartamán belül.
Ebben a példában a TelemetryProcessor
és StateChangeProcessor
objektumok átmenetiként vannak meghatározva. Ez megfelelő megoldás az egyszerű, állapotmentes szolgáltatásokhoz. A DocumentClient
osztálynak azonban a legjobb teljesítmény érdekében egyedülállónak kell lennie. További információkért lásd az Azure Cosmos DB és a .NET teljesítményével kapcsolatos tippeket.
Ha megtekinti a RawTelemetryFunction kódját, láthatja, hogy van egy másik függőség, amely nem jelenik meg a függőséginjektálás beállítási kódjában, a TelemetryClient
osztály, amely az alkalmazásmetrikák naplózására szolgál. A Functions futtatókörnyezete automatikusan regisztrálja ezt az osztályt a függőséginjektálás tárolójában, hogy ne kelljen külön regisztrálnia.
Az Azure Functions függőséginjektálásával kapcsolatos további tudnivalókért tekintse át az alábbi cikkeket:
Konfigurációs beállítások átadása függőséginjektálásba
Időnként konfigurációs értékekkel kell inicializálni egy objektumot. Általában ezek a beállítások alkalmazásbeállításokból vagy (titkos kulcsok esetén) az Azure Key Vaultból származnak.
Ebben a témakörben két példát ismertetünk. Először az DocumentClient
osztály egy Azure Cosmos DB-szolgáltatásvégpontot és -kulcsot vesz igénybe. Ehhez az objektumhoz az alkalmazás egy lambdát regisztrál, amelyet a függőséginjektálási tároló hív meg. A lambda az IConfiguration
interfészt használja a konfigurációs értékek beolvasásához:
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);
});
A második példa a StateChangeProcessor
osztály. Ehhez az objektumhoz a beállításminta nevű megközelítést használjuk. Így működik:
Határozza meg a konfigurációs beállításokat tartalmazó
T
osztályt. Ebben az esetben az Azure Cosmos DB-adatbázis neve és a gyűjtemény neve.public class StateChangeProcessorOptions { public string COSMOSDB_DATABASE_NAME { get; set; } public string COSMOSDB_DATABASE_COL { get; set; } }
Adja meg a
T
osztályt a függőséginjektálás beállításosztályaként.builder.Services.AddOptions<StateChangeProcessorOptions>() .Configure<IConfiguration>((configSection, configuration) => { configuration.Bind(configSection); });
A konfigurálás alatt álló osztály konstruktorában adjon meg egy
IOptions<T>
paramétert.public StateChangeProcessor(IDocumentClient client, IOptions<StateChangeProcessorOptions> options)
A függőséginjektálási rendszer automatikusan kitölti a beállításosztályt a konfigurációs értékekkel, és átadja azt a konstruktornak.
Ennek a módszernek számos előnye van:
- Szétválasztja az osztályt és a konfigurációs értékek forrását.
- Könnyedén beállíthatók különböző konfigurációforrások, például környezeti változók vagy JSON konfigurációs fájlok.
- Leegyszerűsíti az egységtesztelést.
- Szigorú típusmeghatározású beállításosztályt használ, amely kevesebb hibalehetőséget rejt magában, mintha csak skaláris értékeket adna át.
A GetStatus függvény
Ebben a megoldásban a másik függvényalkalmazás egy egyszerű REST API-t implementál egy drón utolsó ismert állapotának lekéréséhez. Ez a függvény egy GetStatusFunction
nevű osztályban van meghatározva. A függvény teljes kódja:
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);
}
}
}
}
Ez a függvény HTTP-triggert használ egy HTTP GET kérés feldolgozásához, A függvény egy Azure Cosmos DB bemeneti kötéssel kéri le a kért dokumentumot. Lényeges, hogy a kötés azelőtt fusson, hogy a rendszer végrehajtaná a függvényben az engedélyezési logikát. A függvénykötés akkor is beolvassa a dokumentumot, ha egy jogosulatlan felhasználó kéri ki. Ezután az engedélyezési kód 401-es hibaüzenetet ad vissza, így a felhasználó nem fogja látni a dokumentumot. Az adott követelményektől függ, hogy ez a viselkedés elfogadható-e. Ez a megközelítés megnehezítheti például a bizalmas adatok hozzáférésének naplózását.
Hitelesítés és engedélyezés
A webalkalmazás Microsoft Entra-azonosítót használ a felhasználók hitelesítéséhez. Mivel az alkalmazás egy böngészőben futó egyoldalas alkalmazás (single-page application, SPA), az implicit engedélyezési folyamat megfelelő megoldást jelent:
- A webalkalmazás átirányítja a felhasználót az identitásszolgáltatóhoz (ebben az esetben a Microsoft Entra-azonosítóhoz).
- A felhasználó megadja a hitelesítő adatait.
- Az identitásszolgáltató visszairányítja a webalkalmazáshoz egy hozzáférési jogkivonattal.
- A webalkalmazás egy kérést küld a webes API felé, és a hozzáférési jogkivonatot az engedélyezési fejlécbe foglalja.
A függvényalkalmazások úgy is konfigurálhatók, hogy kód nélkül hitelesítsék a felhasználókat. További információkért lásd: Hitelesítés és engedélyezés az Azure App Service-ben.
Az engedélyezéshez azonban általában üzleti logikára van szükség. A Microsoft Entra ID támogatja a jogcímalapú hitelesítést. Ebben a modellben a felhasználó identitását az identitásszolgáltatótól származó jogcímek készlete képviseli. Jogcím lehet a felhasználóval kapcsolatos bármely információ, például a neve vagy az e-mail-címe.
A hozzáférési jogkivonat a felhasználói jogcímek alkészletét tartalmazza. Ezek között szerepelnek azok az alkalmazás-szerepkörök is, amelyekhez a felhasználó hozzá van rendelve.
A függvény principal
paramétere egy ClaimsPrincipal objektum, amely tartalmazza a hozzáférési jogkivonat jogcímeit. Mindegyik jogcím a jogcímtípus és a jogcímérték kulcs/érték párja. Az alkalmazás ezekkel engedélyezi a kérést.
A következő kiterjesztésmetódus teszteli, hogy egy ClaimsPrincipal
objektum tartalmaz-e szerepköröket. A false
értéket adja vissza, ha valamelyik megadott szerepkör hiányzik. Ha hamis (false) értéket ad vissza, akkor a függvény HTTP 401 (Jogosulatlan) hibaüzenetet ad ki.
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;
}
}
}
Az ebben az alkalmazásban való hitelesítésre és engedélyezésre vonatkozó további információért tekintse meg a referenciaarchitektúra biztonsági szempontokkal foglalkozó szakaszát.
Következő lépések
Miután megismerkedett a referenciamegoldás működésével, megismerheti a hasonló megoldásokra vonatkozó ajánlott eljárásokat és javaslatokat.
- Kiszolgáló nélküli eseménybetöltési megoldás esetén lásd : Kiszolgáló nélküli eseményfeldolgozás az Azure Functions használatával.
- Kiszolgáló nélküli webalkalmazások esetén lásd : Kiszolgáló nélküli webalkalmazás az Azure-ban.
Az Azure Functions csak egy Azure számítási lehetőség. A számítási technológia kiválasztásával kapcsolatos segítségért lásd: Azure számítási szolgáltatás kiválasztása az alkalmazáshoz.
Kapcsolódó erőforrások
- A kiszolgáló nélküli megoldások helyszíni és felhőbeli fejlesztésével kapcsolatos részletes vitaért olvassa el a Kiszolgáló nélküli alkalmazások: Architektúra, minták és Azure-implementáció című témakört.
- Az eseményvezérelt architektúrastílusról további információkat is olvashat.