Dela via


Skapa en enkel datadriven CRUD-mikrotjänst

Dricks

Det här innehållet är ett utdrag från eBook, .NET Microservices Architecture for Containerized .NET Applications, tillgängligt på .NET Docs eller som en kostnadsfri nedladdningsbar PDF som kan läsas offline.

.NET Microservices Architecture for Containerized .NET Applications eBook cover thumbnail.NET Microservices Architecture for Containerized .NET Applications eBook cover thumbnail.

I det här avsnittet beskrivs hur du skapar en enkel mikrotjänst som utför crud-åtgärder (create, read, update och delete) på en datakälla.

Utforma en enkel CRUD-mikrotjänst

Ur designsynpunkt är den här typen av containerbaserad mikrotjänst mycket enkel. Kanske är problemet att lösa enkelt, eller kanske är implementeringen bara ett konceptbevis.

Diagram som visar ett enkelt internt designmönster för CRUD-mikrotjänster.

Bild 6-4. Intern design för enkla CRUD-mikrotjänster

Ett exempel på den här typen av enkel dataenhetstjänst är katalogmikrotjänsten från exempelprogrammet eShopOnContainers. Den här typen av tjänst implementerar alla funktioner i ett enda ASP.NET Core Web API-projekt som innehåller klasser för datamodellen, dess affärslogik och dess dataåtkomstkod. Den lagrar även sina relaterade data i en databas som körs i SQL Server (som en annan container i utvecklings-/testsyfte), men kan också vara en vanlig SQL Server-värd, enligt bild 6–5.

Diagram som visar en datadriven/CRUD-mikrotjänstcontainer.

Bild 6-5. Enkel datadriven/CRUD-mikrotjänstdesign

Det föregående diagrammet visar den logiska katalogmikrotjänsten, som innehåller dess katalogdatabas, som kan finnas eller inte finns i samma Docker-värd. Att ha databasen i samma Docker-värd kan vara bra för utveckling, men inte för produktion. När du utvecklar den här typen av tjänst behöver du bara ASP.NET Core och ett API för dataåtkomst eller ORM som Entity Framework Core. Du kan också generera Swagger-metadata automatiskt via Swashbuckle för att ge en beskrivning av vad din tjänst erbjuder, enligt beskrivningen i nästa avsnitt.

Observera att det är bra att köra en databasserver som SQL Server i en Docker-container för utvecklingsmiljöer, eftersom du kan ha alla dina beroenden igång utan att behöva etablera en databas i molnet eller lokalt. Den här metoden är praktisk när du kör integreringstester. För produktionsmiljöer rekommenderas dock inte att köra en databasserver i en container, eftersom du vanligtvis inte får hög tillgänglighet med den metoden. För en produktionsmiljö i Azure rekommenderar vi att du använder Azure SQL DB eller någon annan databasteknik som kan ge hög tillgänglighet och hög skalbarhet. För en NoSQL-metod kan du till exempel välja CosmosDB.

Genom att redigera Dockerfile och docker-compose.yml metadatafiler kan du konfigurera hur avbildningen av containern ska skapas – vilken basavbildning den ska använda, samt designinställningar som interna och externa namn och TCP-portar.

Implementera en enkel CRUD-mikrotjänst med ASP.NET Core

Om du vill implementera en enkel CRUD-mikrotjänst med hjälp av .NET och Visual Studio börjar du med att skapa ett enkelt ASP.NET Core Web API-projekt (körs på .NET så att det kan köras på en Linux Docker-värd), enligt bild 6–6.

Skärmbild av Visual Studios som visar hur projektet har konfigurerats.

Bild 6-6. Skapa ett ASP.NET Core Web API-projekt i Visual Studio 2019

Om du vill skapa ett ASP.NET Core Web API-projekt väljer du först ett ASP.NET Core-webbprogram och väljer sedan API-typen. När du har skapat projektet kan du implementera dina MVC-styrenheter på samma sätt som i andra webb-API-projekt med hjälp av Entity Framework-API:et eller annat API. I ett nytt webb-API-projekt kan du se att det enda beroende du har i mikrotjänsten finns på ASP.NET Core. Internt refererar det till Entity Framework och många andra .NET NuGet-paket inom beroendet Microsoft.AspNetCore.All , enligt bild 6–7.

Skärmbild av VS som visar NuGet-beroenden för Catalog.Api.

Bild 6-7. Beroenden i en enkel CRUD Web API-mikrotjänst

API-projektet innehåller referenser till Microsoft.AspNetCore.App NuGet-paket, som innehåller referenser till alla viktiga paket. Det kan även innehålla några andra paket.

Implementera CRUD-webb-API-tjänster med Entity Framework Core

Entity Framework (EF) Core är en lätt, utökningsbar och plattformsoberoende version av den populära Entity Framework-dataåtkomsttekniken. EF Core är en objektrelationsmappare (ORM) som gör det möjligt för .NET-utvecklare att arbeta med en databas med hjälp av .NET-objekt.

Katalogmikrotjänsten använder EF och SQL Server-providern eftersom databasen körs i en container med SQL Server för Linux Docker-avbildningen. Databasen kan dock distribueras till valfri SQL Server, till exempel Windows lokalt eller Azure SQL DB. Det enda du behöver ändra är anslutningssträng i mikrotjänsten ASP.NET webb-API.

Datamodellen

Med EF Core utförs dataåtkomst med hjälp av en modell. En modell består av entitetsklasser (domänmodell) och en härledd kontext (DbContext) som representerar en session med databasen, så att du kan fråga efter och spara data. Du kan generera en modell från en befintlig databas, koda en modell manuellt för att matcha din databas eller använda EF-migreringsteknik för att skapa en databas från din modell med hjälp av kod-första metoden (som gör det enkelt att utveckla databasen när din modell ändras över tid). För katalogmikrotjänsten har den senaste metoden använts. Du kan se ett exempel på entitetsklassen CatalogItem i följande kodexempel, som är en enkel POCO-entitetsklass (Plain Old Class Object).

public class CatalogItem
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public decimal Price { get; set; }
    public string PictureFileName { get; set; }
    public string PictureUri { get; set; }
    public int CatalogTypeId { get; set; }
    public CatalogType CatalogType { get; set; }
    public int CatalogBrandId { get; set; }
    public CatalogBrand CatalogBrand { get; set; }
    public int AvailableStock { get; set; }
    public int RestockThreshold { get; set; }
    public int MaxStockThreshold { get; set; }

    public bool OnReorder { get; set; }
    public CatalogItem() { }

    // Additional code ...
}

Du behöver också en DbContext som representerar en session med databasen. För katalogmikrotjänsten härleds klassen CatalogContext från basklassen DbContext, som du ser i följande exempel:

public class CatalogContext : DbContext
{
    public CatalogContext(DbContextOptions<CatalogContext> options) : base(options)
    { }
    public DbSet<CatalogItem> CatalogItems { get; set; }
    public DbSet<CatalogBrand> CatalogBrands { get; set; }
    public DbSet<CatalogType> CatalogTypes { get; set; }

    // Additional code ...
}

Du kan ha ytterligare DbContext implementeringar. I exempeldatabasen Catalog.API-mikrotjänst finns det till exempel ett andra DbContext namn CatalogContextSeed där exempeldata fylls i automatiskt första gången den försöker komma åt databasen. Den här metoden är användbar för demodata och för automatiserade testscenarier.

DbContextI använder OnModelCreating du metoden för att anpassa objekt-/databasentitetsmappningar och andra utökningspunkter för EF.

Köra frågor mot data från webb-API-kontrollanter

Instanser av dina entitetsklasser hämtas vanligtvis från databasen med hjälp av Language-Integrated Query (LINQ), som du ser i följande exempel:

[Route("api/v1/[controller]")]
public class CatalogController : ControllerBase
{
    private readonly CatalogContext _catalogContext;
    private readonly CatalogSettings _settings;
    private readonly ICatalogIntegrationEventService _catalogIntegrationEventService;

    public CatalogController(
        CatalogContext context,
        IOptionsSnapshot<CatalogSettings> settings,
        ICatalogIntegrationEventService catalogIntegrationEventService)
    {
        _catalogContext = context ?? throw new ArgumentNullException(nameof(context));
        _catalogIntegrationEventService = catalogIntegrationEventService
            ?? throw new ArgumentNullException(nameof(catalogIntegrationEventService));

        _settings = settings.Value;
        context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
    }

    // GET api/v1/[controller]/items[?pageSize=3&pageIndex=10]
    [HttpGet]
    [Route("items")]
    [ProducesResponseType(typeof(PaginatedItemsViewModel<CatalogItem>), (int)HttpStatusCode.OK)]
    [ProducesResponseType(typeof(IEnumerable<CatalogItem>), (int)HttpStatusCode.OK)]
    [ProducesResponseType((int)HttpStatusCode.BadRequest)]
    public async Task<IActionResult> ItemsAsync(
        [FromQuery]int pageSize = 10,
        [FromQuery]int pageIndex = 0,
        string ids = null)
    {
        if (!string.IsNullOrEmpty(ids))
        {
            var items = await GetItemsByIdsAsync(ids);

            if (!items.Any())
            {
                return BadRequest("ids value invalid. Must be comma-separated list of numbers");
            }

            return Ok(items);
        }

        var totalItems = await _catalogContext.CatalogItems
            .LongCountAsync();

        var itemsOnPage = await _catalogContext.CatalogItems
            .OrderBy(c => c.Name)
            .Skip(pageSize * pageIndex)
            .Take(pageSize)
            .ToListAsync();

        itemsOnPage = ChangeUriPlaceholder(itemsOnPage);

        var model = new PaginatedItemsViewModel<CatalogItem>(
            pageIndex, pageSize, totalItems, itemsOnPage);

        return Ok(model);
    }
    //...
}
Spara data

Data skapas, tas bort och ändras i databasen med hjälp av instanser av dina entitetsklasser. Du kan lägga till kod som följande hårdkodade exempel (i det här fallet falska data) till dina webb-API-kontrollanter.

var catalogItem = new CatalogItem() {CatalogTypeId=2, CatalogBrandId=2,
                                     Name="Roslyn T-Shirt", Price = 12};
_context.Catalog.Add(catalogItem);
_context.SaveChanges();
Beroendeinmatning i ASP.NET Core- och Web API-styrenheter

I ASP.NET Core kan du använda beroendeinmatning (DI) direkt. Du behöver inte konfigurera en IoC-container (Inversion of Control) från tredje part, även om du kan ansluta din önskade IoC-container till ASP.NET Core-infrastrukturen om du vill. I det här fallet innebär det att du direkt kan mata in nödvändig EF DBContext eller ytterligare lagringsplatser via styrenhetskonstruktorn.

I klassen CatalogController som nämndes tidigare CatalogContext matas (som ärver från DbContext) typen in tillsammans med de andra nödvändiga objekten CatalogController() i konstruktorn.

En viktig konfiguration som ska konfigureras i webb-API-projektet är dbContext-klassregistreringen i tjänstens IoC-container. Du gör vanligtvis det i filen Program.cs genom att anropa builder.Services.AddDbContext<CatalogContext>() metoden, som du ser i följande förenklade exempel:

// Additional code...

builder.Services.AddDbContext<CatalogContext>(options =>
{
    options.UseSqlServer(builder.Configuration["ConnectionString"],
    sqlServerOptionsAction: sqlOptions =>
    {
        sqlOptions.MigrationsAssembly(
            typeof(Program).GetTypeInfo().Assembly.GetName().Name);

        //Configuring Connection Resiliency:
        sqlOptions.
            EnableRetryOnFailure(maxRetryCount: 5,
            maxRetryDelay: TimeSpan.FromSeconds(30),
            errorNumbersToAdd: null);
    });

    // Changing default behavior when client evaluation occurs to throw.
    // Default in EFCore would be to log warning when client evaluation is done.
    options.ConfigureWarnings(warnings => warnings.Throw(
        RelationalEventId.QueryClientEvaluationWarning));
});

Viktigt!

Microsoft rekommenderar att du använder det säkraste tillgängliga autentiseringsflödet. Om du ansluter till Azure SQL är hanterade identiteter för Azure-resurser den rekommenderade autentiseringsmetoden.

Ytterligare resurser

Db-anslutningssträng- och miljövariablerna som används av Docker-containrar

Du kan använda inställningarna för ASP.NET Core och lägga till en ConnectionString-egenskap i din settings.json-fil enligt följande exempel:

{
    "ConnectionString": "Server=tcp:127.0.0.1,5433;Initial Catalog=Microsoft.eShopOnContainers.Services.CatalogDb;User Id=sa;Password=[PLACEHOLDER]",
    "ExternalCatalogBaseUrl": "http://host.docker.internal:5101",
    "Logging": {
        "IncludeScopes": false,
        "LogLevel": {
            "Default": "Debug",
            "System": "Information",
            "Microsoft": "Information"
        }
    }
}

Den settings.json filen kan ha standardvärden för egenskapen ConnectionString eller någon annan egenskap. Dessa egenskaper åsidosätts dock av värdena för miljövariabler som du anger i filen docker-compose.override.yml när du använder Docker.

Från dina docker-compose.yml- eller docker-compose.override.yml-filer kan du initiera dessa miljövariabler så att Docker konfigurerar dem som os-miljövariabler åt dig, som du ser i följande docker-compose.override.yml -fil (anslutningssträng och andra rader radbryts i det här exemplet, men den skulle inte omslutas i din egen fil).

# docker-compose.override.yml

#
catalog-api:
  environment:
    - ConnectionString=Server=sqldata;Database=Microsoft.eShopOnContainers.Services.CatalogDb;User Id=sa;Password=[PLACEHOLDER]
    # Additional environment variables for this service
  ports:
    - "5101:80"

Viktigt!

Microsoft rekommenderar att du använder det säkraste tillgängliga autentiseringsflödet. Om du ansluter till Azure SQL är hanterade identiteter för Azure-resurser den rekommenderade autentiseringsmetoden.

De docker-compose.yml filerna på lösningsnivå är inte bara mer flexibla än konfigurationsfiler på projekt- eller mikrotjänstnivå, utan också säkrare om du åsidosätter miljövariablerna som deklareras i docker-compose-filerna med värden som angetts från dina distributionsverktyg, till exempel från Azure DevOps Services Docker-distributionsuppgifter.

Slutligen kan du hämta det värdet från koden med hjälp builder.Configuration["ConnectionString"]av , som du ser i ett tidigare kodexempel.

Men för produktionsmiljöer kanske du vill utforska ytterligare sätt att lagra hemligheter som anslutningssträng. Ett utmärkt sätt att hantera programhemligheter är att använda Azure Key Vault.

Azure Key Vault hjälper till att lagra och skydda kryptografiska nycklar och hemligheter som används av dina molnprogram och tjänster. En hemlighet är allt du vill ha strikt kontroll över, till exempel API-nycklar, anslutningssträng, lösenord osv. och strikt kontroll omfattar användningsloggning, förfallotid, hantering av åtkomst, bland annat.

Azure Key Vault tillåter en detaljerad kontrollnivå för användningen av programhemligheter utan att någon behöver informera någon om dem. Hemligheterna kan till och med roteras för förbättrad säkerhet utan att störa utveckling eller åtgärder.

Program måste registreras i organisationens Active Directory, så att de kan använda Key Vault.

Mer information finns i dokumentationen om Key Vault-begrepp.

Implementera versionshantering i ASP.NET webb-API:er

När affärskraven ändras kan nya samlingar av resurser läggas till, relationerna mellan resurserna ändras och strukturen för data i resurser kan ändras. Att uppdatera ett webb-API för att hantera nya krav är en relativt enkel process, men du måste tänka på vilka effekter sådana ändringar kommer att ha på klientprogram som använder webb-API:et. Även om utvecklaren som utformar och implementerar ett webb-API har fullständig kontroll över det API:et, har utvecklaren inte samma kontroll över klientprogram som kan skapas av tredjepartsorganisationer som arbetar via fjärranslutning.

Versionshantering gör det möjligt för ett webb-API att ange de funktioner och resurser som det exponerar. Ett klientprogram kan sedan skicka begäranden till en viss version av en funktion eller resurs. Det finns flera metoder för att implementera versionshantering:

  • URI-versionshantering
  • Versionshantering av frågesträngar
  • Rubrikversionshantering

Frågesträng och URI-versionshantering är de enklaste att implementera. Versionshantering av huvuden är en bra metod. Versionshantering av huvuden är dock inte lika explicit och enkelt som URI-versionshantering. Eftersom URL-versionshantering är det enklaste och mest explicita använder eShopOnContainers-exempelprogrammet URI-versionshantering.

Med URI-versionshantering, som i exempelprogrammet eShopOnContainers, varje gång du ändrar webb-API:et eller ändrar schemat för resurser, lägger du till ett versionsnummer till URI:n för varje resurs. Befintliga URI:er bör fortsätta att fungera som tidigare och returnera resurser som överensstämmer med schemat som matchar den begärda versionen.

Som du ser i följande kodexempel kan versionen anges med hjälp av routningsattributet i webb-API-kontrollanten, vilket gör versionen explicit i URI:n (v1 i det här fallet).

[Route("api/v1/[controller]")]
public class CatalogController : ControllerBase
{
    // Implementation ...

Den här versionsmekanismen är enkel och beror på vilken server som dirigerar begäran till rätt slutpunkt. Men för en mer avancerad versionshantering och den bästa metoden när du använder REST bör du använda hypermedia och implementera HATEOAS (Hypertext som motorn i programtillståndet).

Ytterligare resurser

Generera Swagger-beskrivningsmetadata från ditt ASP.NET Core-webb-API

Swagger är ett vanligt öppen källkod ramverk som backas upp av ett stort ekosystem med verktyg som hjälper dig att utforma, skapa, dokumentera och använda dina RESTful-API:er. Den håller på att bli standard för API:ernas beskrivningsmetadatadomän. Du bör inkludera Swagger-beskrivningsmetadata med alla typer av mikrotjänster, antingen datadrivna mikrotjänster eller mer avancerade domändrivna mikrotjänster (som beskrivs i följande avsnitt).

Kärnan i Swagger är Swagger-specifikationen, som är API-beskrivningsmetadata i en JSON- eller YAML-fil. Specifikationen skapar RESTful-kontraktet för ditt API och beskriver alla dess resurser och åtgärder i både mänskligt och maskinläsbart format för enkel utveckling, identifiering och integrering.

Specifikationen är grunden för OpenAPI-specifikationen (OAS) och utvecklas i en öppen, transparent och samarbetsinriktad community för att standardisera hur RESTful-gränssnitt definieras.

Specifikationen definierar strukturen för hur en tjänst kan identifieras och hur dess funktioner förstås. Mer information, inklusive en webbredigerare och exempel på Swagger-specifikationer från företag som Spotify, Uber, Slack och Microsoft, finns på Swagger-webbplatsen (https://swagger.io).

Varför använda Swagger?

De främsta orsakerna till att generera Swagger-metadata för dina API:er är följande.

Möjlighet för andra produkter att automatiskt använda och integrera dina API:er. Dussintals produkter och kommersiella verktyg och många bibliotek och ramverk stöder Swagger. Microsoft har produkter och verktyg på hög nivå som automatiskt kan använda Swagger-baserade API:er, till exempel följande:

Möjlighet att generera API-dokumentation automatiskt. När du skapar storskaliga RESTful-API:er, till exempel komplexa mikrotjänstbaserade program, måste du hantera många slutpunkter med olika datamodeller som används i nyttolasten för begäran och svar. Att ha rätt dokumentation och ha en solid API Explorer, som du får med Swagger, är nyckeln till framgång för ditt API och implementering av utvecklare.

Swaggers metadata är vad Microsoft Flow, PowerApps och Azure Logic Apps använder för att förstå hur du använder API:er och ansluter till dem.

Det finns flera alternativ för att automatisera generering av Swagger-metadata för ASP.NET Core REST API-program, i form av funktionella API-hjälpsidor, baserat på swagger-ui.

Förmodligen det bästa vet är Swashbuckle, som för närvarande används i eShopOnContainers och vi kommer att gå igenom i detalj i den här guiden men det finns också möjlighet att använda NSwag, som kan generera Typescript- och C#API-klienter, samt C#-kontrollanter, från en Swagger- eller OpenAPI-specifikation och även genom att skanna .dll som innehåller kontrollanterna, med hjälp av NSwagStudio.

Automatisera API Swagger-metadatagenerering med Swashbuckle NuGet-paketet

Att generera Swagger-metadata manuellt (i en JSON- eller YAML-fil) kan vara ett omständligt arbete. Du kan dock automatisera API-identifieringen av ASP.NET webb-API-tjänster med hjälp av Swashbuckle NuGet-paketet för att dynamiskt generera Swagger API-metadata.

Swashbuckle genererar automatiskt Swagger-metadata för dina ASP.NET webb-API-projekt. Det stöder ASP.NET Core Web API-projekt och traditionella ASP.NET webb-API och andra varianter, till exempel Azure API App, Azure Mobile App, Azure Service Fabric-mikrotjänster baserat på ASP.NET. Det stöder också vanligt webb-API som distribueras på containrar, som i för referensprogrammet.

Swashbuckle kombinerar API Explorer och Swagger eller swagger-ui för att ge dina API-konsumenter en omfattande identifierings- och dokumentationsupplevelse. Förutom sin Swagger-metadatageneratormotor innehåller Swashbuckle också en inbäddad version av swagger-ui, som den automatiskt kommer att fungera när Swashbuckle har installerats.

Det innebär att du kan komplettera ditt API med ett trevligt identifieringsgränssnitt som hjälper utvecklare att använda ditt API. Det kräver en liten mängd kod och underhåll eftersom det genereras automatiskt, så att du kan fokusera på att skapa ditt API. Resultatet för API Explorer ser ut som bild 6–8.

Skärmbild av Swagger API Explorer som visar eShopOContainers API.

Bild 6-8. Swashbuckle API Explorer baserat på Swagger-metadata – eShopOnContainers katalogmikrotjänst

Dokumentationen för Swashbuckle-genererade Swagger UI API innehåller alla publicerade åtgärder. API Explorer är inte det viktigaste här. När du har ett webb-API som kan beskriva sig självt i Swagger-metadata kan ditt API användas sömlöst från Swagger-baserade verktyg, inklusive kodgeneratorer för klientproxyklass som kan riktas mot många plattformar. Som nämnts genererar AutoRest till exempel automatiskt .NET-klientklasser. Men ytterligare verktyg som swagger-codegen finns också tillgängliga, som tillåter kodgenerering av API-klientbibliotek, serverstubbar och dokumentation automatiskt.

För närvarande består Swashbuckle av fem interna NuGet-paket under högnivåmetapackage Swashbuckle.AspNetCore för ASP.NET Core-program.

När du har installerat dessa NuGet-paket i ditt webb-API-projekt måste du konfigurera Swagger i klassen Program.cs , som i följande förenklade kod:

// Add framework services.

builder.Services.AddSwaggerGen(options =>
{
    options.DescribeAllEnumsAsStrings();
    options.SwaggerDoc("v1", new OpenApiInfo
    {
        Title = "eShopOnContainers - Catalog HTTP API",
        Version = "v1",
        Description = "The Catalog Microservice HTTP API. This is a Data-Driven/CRUD microservice sample"
    });
});

// Other startup code...

app.UseSwagger();

if (app.Environment.IsDevelopment())
{
    app.UseSwaggerUI(c =>
    {
        c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
    });
}

När detta är klart kan du starta programmet och bläddra bland följande Swagger JSON- och UI-slutpunkter med hjälp av URL:er som dessa:

  http://<your-root-url>/swagger/v1/swagger.json

  http://<your-root-url>/swagger/

Du såg tidigare det genererade användargränssnittet som skapats av Swashbuckle för en URL som http://<your-root-url>/swagger. I bild 6–9 kan du också se hur du kan testa valfri API-metod.

Skärmbild av Swagger-användargränssnittet som visar tillgängliga testverktyg.

Bild 6-9. Swashbuckle-användargränssnittet testar API-metoden Catalog/Items

Swagger UI API-informationen visar ett exempel på svaret och kan användas för att köra det verkliga API:et, vilket är bra för utvecklaridentifiering. Om du vill se Swagger JSON-metadata som genererats från mikrotjänsten eShopOnContainers (vilket är vad verktygen använder under), gör du en begäran http://<your-root-url>/swagger/v1/swagger.json med hjälp av Visual Studio Code: REST-klienttillägget.

Ytterligare resurser