Návrh vrstvy trvalosti infrastruktury

Komponenty trvalosti dat poskytují přístup k datům hostovaným v rámci hranic mikroslužby (to znamená databáze mikroslužby). Obsahují skutečnou implementaci komponent, jako jsou úložiště a třídy Unit of Work, jako jsou vlastní Entity Framework (EF). DbContext EF DbContext implementuje vzory úložiště i unit of work.

Model úložiště

Úložiště jsou třídy nebo komponenty, které zapouzdřují logiku požadovanou pro přístup ke zdrojům dat. Centralizují běžné funkce přístupu k datům a poskytují lepší udržovatelnost a oddělení infrastruktury nebo technologie používané pro přístup k databázím z vrstvy doménového modelu. Pokud používáte Object-Relational Mapper (ORM), jako je Entity Framework, je kód, který je nutné implementovat, díky technologii LINQ a silnému psaní zjednodušen. Díky tomu se můžete soustředit na logiku trvalosti dat a ne na vnitřní instalaci přístupu k datům.

Model Úložiště je dobře zdokumentovaný způsob práce se zdrojem dat. V knize Patterns of Enterprise Application Architecturepopisuje Martin Fowler úložiště takto:

Úložiště provádí úlohy prostředníka mezi vrstvami doménového modelu a mapováním dat, a to podobným způsobem jako sada doménových objektů v paměti. Klientské objekty deklarativně sestavují dotazy a odesílají je do úložišť pro odpovědi. Úložiště koncepčně zapouzdřuje sadu objektů uložených v databázi a operace, které je možné s nimi provádět, a poskytuje tak způsob, který je blíže vrstvě trvalosti. Úložiště také podporují účel oddělení, jasně a v jednom směru, závislosti mezi pracovní doménou a přidělením nebo mapováním dat.

Definování jednoho úložiště na agregaci

Pro každou agregovanou nebo agregovanou kořenovou třídu byste měli vytvořit jednu třídu úložiště. V mikroslužbách založených na vzorech Domain-Driven Design (DDD) by jediným kanálem, který byste měli použít k aktualizaci databáze, by měla být úložiště. Je to proto, že mají relaci 1:1 s agregačním kořenem, který řídí invarianty agregace a transakční konzistenci. Dotazování databáze prostřednictvím jiných kanálů (stejně jako při přístupu CQRS) je v pořádku, protože dotazy nemění stav databáze. Nicméně transakční oblast (to znamená, že aktualizace) musí být vždy řízena úložišti a agregačními kořeny.

Úložiště v podstatě umožňuje naplnit data v paměti, která pochází z databáze ve formě doménových entit. Jakmile jsou entity v paměti, můžete je změnit a pak je prostřednictvím transakcí zachovat zpět do databáze.

Jak jsme uvedli dříve, pokud používáte model architektury CQS/CQRS, počáteční dotazy se provádějí pomocí vedlejších dotazů z doménového modelu, a to jednoduchými příkazy SQL pomocí Dapperu. Tento přístup je mnohem flexibilnější než úložiště, protože se můžete dotazovat na libovolné tabulky, které potřebujete, a tyto dotazy nejsou omezeny pravidly z agregovaných tabulek. Tato data se předá do prezentační vrstvy nebo klientské aplikace.

Pokud uživatel provede změny, data, která se mají aktualizovat, pochází z klientské aplikace nebo prezentační vrstvy do aplikační vrstvy (například ze služby webového rozhraní API). Když obdržíte příkaz v obslužné rutině příkazu, použijete úložiště k získání dat, která chcete aktualizovat z databáze. Aktualizujete ho v paměti daty předaly pomocí příkazů a pak přidáte nebo aktualizujete data (entity domény) v databázi prostřednictvím transakce.

Je důležité znovu zdůraznit, že byste měli definovat pouze jedno úložiště pro každý agregovaný kořen, jak je znázorněno na obrázku 7–17. Pokud chcete dosáhnout cíle agregovaného kořenového adresáře zachovat transakční konzistenci mezi všemi objekty v rámci agregace, nikdy byste neměli vytvořit úložiště pro každou tabulku v databázi.

Diagram znázorňující vztahy mezi doménou a jinou infrastrukturou

Obrázek 7–17. Relace mezi úložišti, agregacemi a databázovými tabulkami

Výše uvedený diagram znázorňuje vztahy mezi vrstvami domény a infrastruktury: Agregace kupujícího závisí na rozhraních IBuyerRepository a Order Aggregate, která závisí na rozhraních IOrderRepository. Tato rozhraní jsou implementovaná ve vrstvě Infrastruktury odpovídajícími úložišti, která závisí na objektu UnitOfWork, která také přistupují k tabulkám v datové vrstvě.

Vynucení jednoho agregovaného kořenového adresáře na úložiště

Může být užitečné implementovat návrh úložiště takovým způsobem, že vynucuje pravidlo, že úložiště by měly mít pouze agregované kořeny. Můžete vytvořit obecný nebo základní typ úložiště, který omezuje typ entit, se kterou pracuje, aby se zajistilo, že mají IAggregateRoot rozhraní značek.

Proto každá třída úložiště implementovaná ve vrstvě infrastruktury implementuje vlastní kontrakt nebo rozhraní, jak je znázorněno v následujícím kódu:

namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Repositories
{
    public class OrderRepository : IOrderRepository
    {
      // ...
    }
}

Každé konkrétní rozhraní úložiště implementuje obecné rozhraní IRepository:

public interface IOrderRepository : IRepository<Order>
{
    Order Add(Order order);
    // ...
}

Lepším způsobem, jak zajistit, aby kód vynucuje konvenci, že každé úložiště souvisí s jednou agregaci, je implementovat obecný typ úložiště. Tímto způsobem je explicitní, že k cílení na konkrétní agregaci používáte úložiště. To lze snadno provést implementací obecného IRepository základního rozhraní jako v následujícím kódu:

public interface IRepository<T> where T : IAggregateRoot
{
    //....
}

Model Úložiště usnadňuje testování logiky aplikace.

Model Úložiště umožňuje snadno testovat aplikaci pomocí testů jednotek. Mějte na paměti, že testy jednotek testují pouze kód, ne infrastrukturu, takže abstrakce úložiště usnadňují dosažení tohoto cíle.

Jak je uvedeno v předchozí části, doporučuje se definovat a umístit rozhraní úložiště do vrstvy doménového modelu, aby aplikační vrstva, jako je mikroslužba webového rozhraní API, nezávisí přímo na vrstvě infrastruktury, ve které jste implementovali skutečné třídy úložiště. Tímto způsobem a použitím injektáže závislostí v kontrolerů webového rozhraní API můžete implementovat napodobená úložiště, která vracejí falešná data místo dat z databáze. Tento oddělený přístup umožňuje vytvářet a spouštět testy jednotek, které se zaměřují na logiku vaší aplikace bez nutnosti připojení k databázi.

Připojení k databázím selžou a co je důležitější, spouštění stovek testů proti databázi je špatné ze dvou důvodů. Zaprvé to může kvůli velkému počtu testů trvat dlouhou dobu. Za druhé se databázové záznamy můžou změnit a ovlivnit výsledky testů, aby nebyly konzistentní. Testování databáze není test jednotek, ale integrační test. Měli byste mít mnoho testů jednotek spuštěných rychle, ale méně integračních testů pro databáze.

Z hlediska oddělení obav pro testy jednotek funguje vaše logika na entitách domény v paměti. Předpokládá, že je doručila třída úložiště. Jakmile logika upraví entity domény, předpokládá, že třída úložiště je správně uloží. Důležitým bodem je vytvoření testů jednotek pro váš doménový model a jeho doménovou logiku. Agregované kořeny jsou hlavní hranice konzistence v DDD.

Úložiště implementovaná v kontejnerech eShopOnContainers spoléhají na implementaci DbContext úložiště EF Core Unit of Work pomocí sledování změn, takže tuto funkci duplikují.

Rozdíl mezi vzorem úložiště a starším vzorem třídy přístupu k datům (třída DAL)

Objekt přístupu k datům přímo provádí operace přístupu k datům a trvalosti úložiště. Úložiště označuje data operacemi, které chcete provést v paměti jednotky pracovního objektu (jako v EF při použití třídy ), ale tyto aktualizace se do databáze nesměrujte DbContext okamžitě.

Pracovní jednotka se označuje jako jedna transakce, která zahrnuje více operací vložení, aktualizace nebo odstranění. Jednoduše řečeno to znamená, že pro konkrétní akci uživatele, jako je registrace na webu, se všechny operace vložení, aktualizace a odstranění zpracovávají v rámci jedné transakce. Je to efektivnější než zpracování více databázových transakcí vícenásobným způsobem.

Tyto více operací trvalosti se provádějí později v jedné akci, když ji kód z aplikační vrstvy provede. Rozhodnutí o použití změn v paměti ve skutečném databázovém úložišti je obvykle založeno na vzoru Jednotky práce. V EF je vzor Unit of Work implementovaný jako DbContext .

V mnoha případech může tento model nebo způsob použití operací s úložištěm zvýšit výkon aplikace a snížit možnost nekonziferencí. Snižuje také blokování transakcí v databázových tabulkách, protože všechny zamýšlené operace jsou potvrzeny jako součást jedné transakce. To je efektivnější v porovnání s prováděním mnoha izolovaných operací s databází. Proto vybraný ORM může optimalizovat provádění proti databázi seskupením několika aktualizačních akcí v rámci stejné transakce, na rozdíl od mnoha malých a samostatných spuštění transakce.

Úložiště by neměla být povinná.

Vlastní úložiště jsou užitečná z výše uvedených důvodů, a to je přístup pro objednávání mikroslužby v eShopOnContainers. Není ale nezbytným vzorem implementace v návrhu DDD ani v obecném vývoji .NET.

Například Při poskytování přímé zpětné vazby pro tohoto průvodce uvedl:

Pravděpodobně to bude moje největší zpětná vazba. Ve skutečnosti nejste fandou úložišť, zejména proto, že skrývají důležité podrobnosti základního mechanismu trvalosti. To je důvod, proč pro příkazy také přejít na MediatR. Můžu využít všechen výkon vrstvy trvalosti a veškeré toto chování domény nasučit do agregovaných kořenů. Obvykle nechci napodobovat moje úložiště – stále potřebuji tento integrační test mít se skutečnou věcí. To, že jde o CQRS, znamená, že už ve skutečnosti nepotřebujeme žádná úložiště.

Úložiště můžou být užitečná, ale nejsou důležitá pro váš návrh DDD tak, jak je to s agregačním vzorem a doménovým modelem s bohatým obsahem. Proto použijte model Úložiště, nebo ne, jak to podle vás bude vhodné. V každém případě budete používat model úložiště vždy, když použijete EF Core, i když v tomto případě úložiště pokrývá celou mikroslužbu nebo ohraničené kontexty.

Další zdroje informací

Vzor úložiště

Model Unit of Work