Megosztás a következőn keresztül:


Tárolóként futó adatbázis-kiszolgáló használata

Tipp.

Ez a tartalom egy részlet a .NET-alkalmazásokhoz készült .NET-alkalmazásokhoz készült eBook, .NET Microservices Architecture című eBookból, amely elérhető a .NET Docs-on vagy egy ingyenesen letölthető PDF-fájlként, amely offline módban is olvasható.

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

Adatbázisait (SQL Server, PostgreSQL, MySQL stb.) használhatja hagyományos önálló kiszolgálókon, helyszíni fürtökön vagy a felhőbeli PaaS-szolgáltatásokban, például az Azure SQL DB-ben. A fejlesztési és tesztelési környezetek esetében azonban kényelmes, ha az adatbázisok tárolóként futnak, mivel nincs külső függősége, és egyszerűen a docker-compose up parancs futtatása indítja el az egész alkalmazást. Az adatbázisok tárolóként való használata az integrációs tesztekhez is nagyszerű, mivel az adatbázis a tárolóban van elindítva, és mindig ugyanazokkal a mintaadatokkal van feltöltve, így a tesztek kiszámíthatóbbak lehetnek.

Az eShopOnContainersben van egy , a docker-compose.yml fájlban definiált tárolósqldata, amely egy SQL Server for Linux-példányt futtat az sql-adatbázisokkal az összes olyan mikroszolgáltatáshoz, amelynek szüksége van rá.

A mikroszolgáltatások egyik kulcsfontosságú pontja, hogy minden mikroszolgáltatás a kapcsolódó adataival rendelkezik, ezért saját adatbázissal kell rendelkeznie. Az adatbázisok azonban bárhol lehetnek. Ebben az esetben mind ugyanabban a tárolóban vannak, hogy a Docker memóriaigénye a lehető legalacsonyabb legyen. Ne feledje, hogy ez egy elég jó megoldás a fejlesztéshez és talán a teszteléshez, de nem éles környezetben.

A mintaalkalmazás SQL Server-tárolója a következő YAML-kóddal van konfigurálva a docker-compose.yml fájlban, amely futtatáskor docker-compose uplesz végrehajtva. Vegye figyelembe, hogy a YAML-kód összesített konfigurációs információkat tartalmaz az általános docker-compose.yml fájlból és a docker-compose.override.yml fájlból. (Általában elkülönítené a környezeti beállításokat az SQL Server rendszerképéhez kapcsolódó alap- vagy statikus információktól.)

  sqldata:
    image: mcr.microsoft.com/mssql/server:2017-latest
    environment:
      - SA_PASSWORD=Pass@word
      - ACCEPT_EULA=Y
    ports:
      - "5434:1433"

Hasonló módon a következő parancs futtathatja a docker run tárolót ahelyett, hogy használnadocker-compose:

docker run -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=Pass@word' -p 5433:1433 -d mcr.microsoft.com/mssql/server:2017-latest

Ha azonban egy többtárolós alkalmazást, például az eShopOnContainerst helyez üzembe, akkor kényelmesebb a docker-compose up parancs használata, hogy az az alkalmazáshoz szükséges összes tárolót üzembe helyezze.

Amikor először indítja el ezt az SQL Server-tárolót, a tároló inicializálja az SQL Servert a megadott jelszóval. Miután az SQL Server tárolóként fut, frissítheti az adatbázist úgy, hogy bármilyen rendszeres SQL-kapcsolaton keresztül csatlakozik, például az SQL Server Management Studióból, a Visual Studióból vagy a C# kódból.

Az eShopOnContainers alkalmazás inicializálja az egyes mikroszolgáltatás-adatbázisokat mintaadatokkal úgy, hogy az indításkor adatokat ad meg az adatokkal, ahogyan az a következő szakaszban található.

Ha az SQL Server tárolóként fut, az nem csak olyan bemutatókhoz hasznos, amelyek esetében előfordulhat, hogy nem fér hozzá az SQL Server egy példányához. Mint már említettük, fejlesztési és tesztelési környezetekhez is kiválóan alkalmas, így könnyen futtathat integrációs teszteket tiszta SQL Server-rendszerképből és ismert adatokból új mintaadatok bevetésével.

További erőforrások

Üzembe helyezés tesztadatokkal a webalkalmazás indításakor

Ha adatokat szeretne hozzáadni az adatbázishoz az alkalmazás indításakor, a web API-projekt osztályában az alábbihoz hasonló kódot adhat hozzá a Program metódushozMain:

public static int Main(string[] args)
{
    var configuration = GetConfiguration();

    Log.Logger = CreateSerilogLogger(configuration);

    try
    {
        Log.Information("Configuring web host ({ApplicationContext})...", AppName);
        var host = CreateHostBuilder(configuration, args);

        Log.Information("Applying migrations ({ApplicationContext})...", AppName);
        host.MigrateDbContext<CatalogContext>((context, services) =>
        {
            var env = services.GetService<IWebHostEnvironment>();
            var settings = services.GetService<IOptions<CatalogSettings>>();
            var logger = services.GetService<ILogger<CatalogContextSeed>>();

            new CatalogContextSeed()
                .SeedAsync(context, env, settings, logger)
                .Wait();
        })
        .MigrateDbContext<IntegrationEventLogContext>((_, __) => { });

        Log.Information("Starting web host ({ApplicationContext})...", AppName);
        host.Run();

        return 0;
    }
    catch (Exception ex)
    {
        Log.Fatal(ex, "Program terminated unexpectedly ({ApplicationContext})!", AppName);
        return 1;
    }
    finally
    {
        Log.CloseAndFlush();
    }
}

A migrálások alkalmazásakor és az adatbázis üzembe helyezésekor fontos kifogás tapasztalható a tároló indításakor. Mivel előfordulhat, hogy az adatbázis-kiszolgáló valamilyen okból nem érhető el, az újrapróbálkozásokat úgy kell kezelnie, hogy a kiszolgáló elérhető legyen. Ezt az újrapróbálkozási logikát a MigrateDbContext() bővítménymetódus kezeli, ahogyan az a következő kódban is látható:

public static IWebHost MigrateDbContext<TContext>(
    this IWebHost host,
    Action<TContext,
    IServiceProvider> seeder)
      where TContext : DbContext
{
    var underK8s = host.IsInKubernetes();

    using (var scope = host.Services.CreateScope())
    {
        var services = scope.ServiceProvider;

        var logger = services.GetRequiredService<ILogger<TContext>>();

        var context = services.GetService<TContext>();

        try
        {
            logger.LogInformation("Migrating database associated with context {DbContextName}", typeof(TContext).Name);

            if (underK8s)
            {
                InvokeSeeder(seeder, context, services);
            }
            else
            {
                var retry = Policy.Handle<SqlException>()
                    .WaitAndRetry(new TimeSpan[]
                    {
                    TimeSpan.FromSeconds(3),
                    TimeSpan.FromSeconds(5),
                    TimeSpan.FromSeconds(8),
                    });

                //if the sql server container is not created on run docker compose this
                //migration can't fail for network related exception. The retry options for DbContext only
                //apply to transient exceptions
                // Note that this is NOT applied when running some orchestrators (let the orchestrator to recreate the failing service)
                retry.Execute(() => InvokeSeeder(seeder, context, services));
            }

            logger.LogInformation("Migrated database associated with context {DbContextName}", typeof(TContext).Name);
        }
        catch (Exception ex)
        {
            logger.LogError(ex, "An error occurred while migrating the database used on context {DbContextName}", typeof(TContext).Name);
            if (underK8s)
            {
                throw;          // Rethrow under k8s because we rely on k8s to re-run the pod
            }
        }
    }

    return host;
}

Az egyéni CatalogContextSeed osztály következő kódja tölti ki az adatokat.

public class CatalogContextSeed
{
    public static async Task SeedAsync(IApplicationBuilder applicationBuilder)
    {
        var context = (CatalogContext)applicationBuilder
            .ApplicationServices.GetService(typeof(CatalogContext));
        using (context)
        {
            context.Database.Migrate();
            if (!context.CatalogBrands.Any())
            {
                context.CatalogBrands.AddRange(
                    GetPreconfiguredCatalogBrands());
                await context.SaveChangesAsync();
            }
            if (!context.CatalogTypes.Any())
            {
                context.CatalogTypes.AddRange(
                    GetPreconfiguredCatalogTypes());
                await context.SaveChangesAsync();
            }
        }
    }

    static IEnumerable<CatalogBrand> GetPreconfiguredCatalogBrands()
    {
        return new List<CatalogBrand>()
       {
           new CatalogBrand() { Brand = "Azure"},
           new CatalogBrand() { Brand = ".NET" },
           new CatalogBrand() { Brand = "Visual Studio" },
           new CatalogBrand() { Brand = "SQL Server" }
       };
    }

    static IEnumerable<CatalogType> GetPreconfiguredCatalogTypes()
    {
        return new List<CatalogType>()
        {
            new CatalogType() { Type = "Mug"},
            new CatalogType() { Type = "T-Shirt" },
            new CatalogType() { Type = "Backpack" },
            new CatalogType() { Type = "USB Memory Stick" }
        };
    }
}

Integrációs tesztek futtatásakor hasznos lehet, ha az integrációs tesztekkel összhangban álló adatokat hoz létre. A tesztelési környezetekhez nagyszerű, ha mindent az alapoktól tud létrehozni, beleértve a tárolón futó SQL Server-példányt is.

EF Core InMemory-adatbázis és tárolóként futó SQL Server

Egy másik jó választás a tesztek futtatásakor az Entity Framework InMemory adatbázis-szolgáltató használata. Ezt a konfigurációt a Web API-projekt indítási osztályának ConfigureServices metódusában adhatja meg:

public class Startup
{
    // Other Startup code ...
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddSingleton<IConfiguration>(Configuration);
        // DbContext using an InMemory database provider
        services.AddDbContext<CatalogContext>(opt => opt.UseInMemoryDatabase());
        //(Alternative: DbContext using a SQL Server provider
        //services.AddDbContext<CatalogContext>(c =>
        //{
            // c.UseSqlServer(Configuration["ConnectionString"]);
            //
        //});
    }

    // Other Startup code ...
}

Van azonban egy fontos fogás. A memórián belüli adatbázis nem támogatja az adott adatbázisra vonatkozó számos korlátozást. Hozzáadhat például egy egyedi indexet az EF Core-modell egy oszlopához, és írhat egy tesztet a memóriában lévő adatbázisra annak ellenőrzéséhez, hogy nem engedélyezi-e a duplikált érték hozzáadását. A memóriában lévő adatbázis használatakor azonban nem kezelheti az oszlopok egyedi indexeit. Ezért a memóriában lévő adatbázis nem ugyanúgy viselkedik, mint egy valódi SQL Server-adatbázis– nem emulál adatbázisspecifikus korlátozásokat.

Ennek ellenére a memórián belüli adatbázisok továbbra is hasznosak a teszteléshez és a prototípus-készítéshez. Ha azonban pontos integrációs teszteket szeretne létrehozni, amelyek figyelembe veszik egy adott adatbázis-implementáció viselkedését, akkor egy valós adatbázist kell használnia, például az SQL Servert. Ebből a célból az SQL Server tárolóban való futtatása nagyszerű választás és pontosabb, mint az EF Core InMemory adatbázis-szolgáltató.

Tárolóban futó Redis Cache-szolgáltatás használata

A Redist futtathatja egy tárolón, különösen fejlesztési és tesztelési, valamint megvalósíthatósági forgatókönyvek esetében. Ez a forgatókönyv kényelmes, mert minden függősége tárolókon futhat – nem csak a helyi fejlesztői gépeken, hanem a CI-/CD-folyamatok tesztelési környezeteiben is.

Ha azonban éles környezetben futtatja a Redist, jobb, ha olyan magas rendelkezésre állású megoldást keres, mint a Redis Microsoft Azure, amely PaaS-ként (szolgáltatásként nyújtott platformként) fut. A kódban csak módosítania kell a kapcsolati sztring.

A Redis Egy Docker-rendszerképet biztosít a Redishez. A rendszerkép a Docker Hubról érhető el az alábbi URL-címen:

https://hub.docker.com/_/redis/

Közvetlenül futtathat Egy Docker Redis-tárolót úgy, hogy végrehajtja a következő Docker CLI-parancsot a parancssorban:

docker run --name some-redis -d redis

A Redis-rendszerkép tartalmazza az expose:6379-et (a Redis által használt portot), így a standard tárolókapcsolatok automatikusan elérhetővé teszik a csatolt tárolók számára.

Az eShopOnContainersben a basket-api mikroszolgáltatás tárolóként futó Redis-gyorsítótárat használ. Ez basketdata a tároló a többtárolós docker-compose.yml fájl részeként van definiálva, ahogy az a következő példában is látható:

#docker-compose.yml file
#...
  basketdata:
    image: redis
    expose:
      - "6379"

Ez a kód a docker-compose.yml definiál egy tárolót, amely a Redis-rendszerkép alapján van elnevezve basketdata , és a 6379-ös portot belsőleg teszi közzé. Ez a konfiguráció azt jelenti, hogy csak a Docker-gazdagépen futó többi tárolóból lesz elérhető.

Végül a docker-compose.override.yml fájlban az basket-api eShopOnContainers minta mikroszolgáltatása határozza meg a Redis-tárolóhoz használandó kapcsolati sztring:

  basket-api:
    environment:
      # Other data ...
      - ConnectionString=basketdata
      - EventBusConnection=rabbitmq

Ahogy korábban említettük, a mikroszolgáltatás basketdata nevét a Docker belső hálózati DNS-ével oldja fel.