Návrh vrstvy trvalosti infrastruktury

Tip

Tento obsah je výňatek z eBooku, architektury mikroslužeb .NET pro kontejnerizované aplikace .NET, které jsou k dispozici na .NET Docs nebo jako zdarma ke stažení PDF, které lze číst offline.

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

Komponenty trvalosti dat poskytují přístup k datům hostovaným v rámci hranic mikroslužby (tedy databáze mikroslužby). Obsahují skutečnou implementaci komponent, jako jsou úložiště a třídy práce jednotek práce, jako jsou vlastní objekty Entity Framework (EF). DbContext EF DbContext implementuje vzory úložiště i jednotky práce.

Model úložiště

Model úložiště je model návrhu řízený doménou, který má zachovat obavy o trvalost mimo doménový model systému. Nejméně jedna abstrakce trvalosti – rozhraní – jsou definovány v doménovém modelu a tyto abstrakce mají implementace ve formě adaptérů specifických pro trvalost definované jinde v aplikaci.

Implementace úložiště jsou třídy, které zapouzdřují logiku potřebnou pro přístup ke zdrojům dat. Centralizuje běžné funkce přístupu k datům a poskytuje lepší udržovatelnost a oddělení infrastruktury nebo technologie používané pro přístup k databázím od doménového modelu. Pokud používáte objektově-relační mapovač (ORM), jako je Entity Framework, je kód, který musí být implementován, díky LINQ a silnému psaní. Díky tomu se můžete soustředit na logiku trvalosti dat a ne na instalatérní přístup k datům.

Model úložiště je dobře zdokumentovaný způsob práce se zdrojem dat. V knize Vzory architektury podnikových aplikací Martin Fowler popisuje úložiště následujícím způsobem:

Úložiště provádí úlohy zprostředkujícího mezi vrstvami doménového modelu a mapováním dat, které fungují podobným způsobem jako sada objektů domény v paměti. Klientské objekty deklarativní vytvářejí 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 operacích, které se s nimi dají 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ávislost mezi pracovní doménou a přidělením dat nebo mapováním.

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

Pro každou agregaci nebo agregační kořen byste měli vytvořit jednu třídu úložiště. Možná budete moct využít obecné typy jazyka C# ke snížení celkového počtu konkrétních tříd, které potřebujete zachovat (jak je znázorněno dále v této kapitole). V mikroslužbě založeném na vzorech DDD (Domain-Driven Design) by jediným kanálem, který byste měli použít k aktualizaci databáze, by měl být úložiště. Důvodem je to, že mají relaci 1:1 s agregačním kořenem, který řídí invarianty agregace a transakční konzistenci. Je v pořádku dotazovat se na databázi prostřednictvím jiných kanálů (jak můžete postupovat podle přístupu CQRS), protože dotazy nemění stav databáze. Transakční oblast (tj. aktualizace) však musí být vždy řízena úložišti a agregovanými kořeny.

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

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

Pokud uživatel provede změny, data, která se mají aktualizovat, pocházejí 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, pomocí úložišť získáte data, která chcete aktualizovat z databáze. Aktualizujete ji v paměti daty předanými příkazy 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řenový adresář, jak je znázorněno na obrázku 7–17. Chcete-li dosáhnout cíle agregovaného kořenového adresáře pro zachování transakční konzistence mezi všemi objekty v agregaci, nikdy byste neměli vytvořit úložiště pro každou tabulku v databázi.

Diagram showing relationships of domain and other infrastructure.

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éna a Infrastruktura: Agregace kupujícího závisí na IBuyerRepository a Agregaci objednávek závisí na rozhraních IOrderRepository, tato rozhraní jsou implementována ve vrstvě Infrastruktura odpovídajícími úložišti, která závisí na UnitOfWork, také v ní implementovaná, která přistupují k tabulkám v datové vrstvě.

Vynucování 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, které by mělo obsahovat pouze agregované kořeny. Můžete vytvořit obecný nebo základní typ úložiště, který omezuje typ entit, se kterými pracuje, aby se zajistilo, že mají IAggregateRoot rozhraní značek.

Každá třída úložiště implementovaná ve vrstvě infrastruktury tedy 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ší způsob, jak mít kód vynucovat konvenci, že každé úložiště souvisí s jednou agregací, 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 otestovat aplikaci pomocí testů jednotek. Mějte na paměti, že testy jednotek testuje pouze váš 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čujeme 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ě. Pomocí injektáže závislostí v kontrolerů webového rozhraní API můžete implementovat napodobená úložiště, která místo dat z databáze vrací falešná data. Tento oddělený přístup umožňuje vytvářet a spouštět testy jednotek, které se zaměřují na logiku aplikace bez nutnosti připojení k databázi.

Připojení iony databází můžou selhat a důležitější je, že spuštění stovek testů s databází je z dvou důvodů špatné. Za prvé může trvat delší dobu kvůli velkému počtu testů. Za druhé, záznamy databáze se můžou změnit a ovlivnit výsledky testů, zejména pokud jsou testy spuštěné paralelně, aby nemusely být konzistentní. Testy jednotek se obvykle můžou spouštět paralelně; integrační testy nemusí podporovat paralelní spouštění v závislosti na jejich implementaci. Testování v databázi není test jednotek, ale integrační test. Měli byste mít mnoho testů jednotek, které běží rychle, ale méně integračních testů v databázích.

Z hlediska oddělení obav z testů jednotek logika pracuje s entitami domény v paměti. Předpokládá, že třída úložiště je doručila. Jakmile logika upraví entity domény, předpokládá se, ž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 logiku domény. Agregační kořeny jsou hlavní hranice konzistence v DDD.

Úložiště implementovaná v eShopOnContainers využívají implementaci Modelu úložiště a jednotky práce EF Core pomocí sledování změn, takže tuto funkci nezduplikují.

Rozdíl mezi vzorem úložiště a starší třídou přístupu k datům (TŘÍDA DAL)

Typický objekt DAL přímo provádí operace přístupu k datům a trvalosti vůči úložišti, často na úrovni jedné tabulky a řádku. Jednoduché operace CRUD implementované se sadou tříd DAL často nepodporují transakce (i když to není vždy případ). Většina přístupů ke třídám DAL využívá minimální abstrakce, což vede k těsnému párování mezi třídami aplikace nebo BLL (Business Logic Layer), které volají objekty DAL.

Při použití úložiště se podrobnosti implementace trvalosti zapouzdřují mimo doménový model. Použití abstrakce poskytuje snadné rozšíření chování prostřednictvím vzorů, jako jsou dekorátory nebo proxy servery. Například průřezové aspekty, jako je ukládání do mezipaměti, protokolování a zpracování chyb, je možné použít tyto vzory místo pevně zakódovaného v samotném kódu přístupu k datům. Je také triviální podporovat více adaptérů úložiště, které se můžou používat v různých prostředích, od místního vývoje po sdílená přípravná prostředí až po produkční prostředí.

Implementace jednotky práce

Jednotka práce odkazuje na jednu transakci, která zahrnuje více operací vložení, aktualizace nebo odstranění. Jednoduše řečeno, znamená to, že pro konkrétní akci uživatele, jako je registrace na webu, všechny operace vložení, aktualizace a odstranění se zpracovávají v jedné transakci. To je efektivnější než zpracování více databázových operací chatovacím způsobem.

Tyto více operací trvalosti se provádí později v jedné akci, když kód z aplikační vrstvy příkazy. 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 se model práce jednotky práce implementuje pomocí a DbContext provádí se při volání .SaveChanges

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 nekonzistence. Také snižuje 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ší oproti provádění mnoha izolovaných operací s databází. Proto vybraný ORM může optimalizovat provádění s databází seskupením několika aktualizačních akcí ve stejné transakci, na rozdíl od mnoha malých a samostatných provádění transakcí.

Model práce jednotky práce lze implementovat s modelem úložiště nebo bez použití tohoto vzoru.

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

Vlastní úložiště jsou užitečná z důvodů uvedených výše a to je přístup k objednávání mikroslužeb v eShopOnContainers. Nejedná se ale o základní model implementace v návrhu DDD ani v obecném vývoji .NET.

Například Jimmy Bogard, když poskytuje přímou zpětnou vazbu pro tuto příručku, řekl následující:

To bude pravděpodobně moje největší zpětná vazba. Opravdu nejsem fanoušek úložišť, hlavně proto, že skryjí důležité podrobnosti o základním mechanismu trvalosti. Proto taky jdu pro MediatR pro příkazy. Můžu použít plnou sílu vrstvy trvalosti a nasdílím veškeré chování této domény do mých agregovaných kořenového adresáře. Obvykle nechci napodobení úložišť – stále potřebuji mít tento integrační test se skutečnou věcí. Přechod CQRS znamenal, že už jsme neměli žádnou potřebu úložišť.

Úložiště můžou být užitečná, ale nejsou důležitá pro návrh DDD způsobem, jakým je agregovaný vzor a bohatý doménový model. Proto použijte vzor úložiště nebo ne, jak vidíte.

Další materiály

Vzor úložiště

Model práce jednotky práce