Události domény: Návrh a implementace
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.
Události domény můžete použít k explicitní implementaci vedlejších účinků změn v rámci vaší domény. Jinými slovy a použití terminologie DDD použijte události domény k explicitní implementaci vedlejších efektů napříč několika agregacemi. Volitelně můžete pro lepší škálovatelnost a menší dopad na zámky databáze použít konečnou konzistenci mezi agregacemi ve stejné doméně.
Co je událost domény?
Událost je něco, co se stalo v minulosti. Událost domény je něco, co se stalo v doméně, o které chcete, aby ostatní části stejné domény (v procesu) věděly. Oznámené části obvykle reagují na události nějakým způsobem.
Důležitou výhodou událostí domény je, že vedlejší účinky lze vyjádřit explicitně.
Pokud například právě používáte Entity Framework a musí existovat reakce na nějakou událost, pravděpodobně byste kódoval cokoli, co potřebujete blízko k tomu, co událost aktivuje. Takže pravidlo se s kódem sloučí implicitně a musíte se podívat na kód, abyste si snad uvědomili, že pravidlo je tam implementované.
Na druhou stranu použití událostí domény znamená, že koncept je explicitní, protože existuje DomainEvent
aspoň jedna DomainEventHandler
.
Například v aplikaci eShop při vytvoření objednávky se uživatel stane kupující, takže OrderStartedDomainEvent
je vyvolána a zpracována v ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler
aplikaci , takže základní koncept je zřejmé.
Stručně řečeno, doménové události vám pomůžou explicitně vyjádřit pravidla domény na základě všudypřítomného jazyka poskytovaného odborníky na doménu. Události domény také umožňují lepší oddělení obav mezi třídami ve stejné doméně.
Je důležité zajistit, aby se stejně jako u databázové transakce úspěšně dokončily všechny operace související s událostí domény nebo žádné z nich.
Události domény se podobají událostem ve stylu zasílání zpráv s jedním důležitým rozdílem. Při použití skutečného zasílání zpráv, řazení zpráv do fronty, zprostředkovatelů zpráv nebo sběrnice service bus pomocí AMQP se zpráva vždy odesílá asynchronně a komunikuje mezi procesy a počítači. To je užitečné pro integraci více ohraničených kontextů, mikroslužeb nebo dokonce různých aplikací. U událostí domény ale chcete vyvolat událost z operace domény, kterou právě používáte, ale chcete, aby v rámci stejné domény došlo k jakýmkoli vedlejším účinkům.
Události domény a jejich vedlejší účinky (akce aktivované poté, které jsou spravovány obslužnými rutinami událostí) by se měly objevit téměř okamžitě, obvykle v procesu a ve stejné doméně. Proto můžou být události domény synchronní nebo asynchronní. Události integrace by ale měly být vždy asynchronní.
Události domény a události integrace
Séanticky, události domény a integrace jsou stejné: oznámení o něčem, co se právě stalo. Jejich implementace se ale musí lišit. Události domény jsou pouze zprávy odeslané do dispečeru událostí domény, který je možné implementovat jako mediátor v paměti na základě kontejneru IoC nebo jakékoli jiné metody.
Na druhou stranu účelem událostí integrace je šíření potvrzených transakcí a aktualizací do dalších subsystémů, ať už se jedná o jiné mikroslužby, ohraničené kontexty nebo dokonce externí aplikace. Proto by k nim mělo dojít pouze v případě, že je entita úspěšně zachována, jinak se jedná o situaci, kdy k celé operaci nikdy nedošlo.
Jak už bylo zmíněno dříve, události integrace musí být založeny na asynchronní komunikaci mezi několika mikroslužbami (jinými ohraničenými kontexty) nebo dokonce externími systémy nebo aplikacemi.
Proto rozhraní sběrnice událostí potřebuje určitou infrastrukturu, která umožňuje komunikaci mezi procesy a distribuovanou komunikaci mezi potenciálně vzdálenými službami. Může být založená na sběrnici komerční služby, frontách, sdílené databázi používané jako poštovní schránku nebo na jakémkoli jiném distribuovaném a ideálně nabízeném systému zasílání zpráv.
Události domény jako upřednostňovaný způsob aktivace vedlejších efektů napříč několika agregacemi ve stejné doméně
Pokud spuštění příkazu souvisejícího s jednou agregační instancí vyžaduje spuštění dalších pravidel domény u jedné nebo více dalších agregací, měli byste navrhnout a implementovat tyto vedlejší účinky, které mají být aktivovány událostmi domény. Jak je znázorněno na obrázku 7–14 a jako jeden z nejdůležitějších případů použití by se měla událost domény použít k šíření změn stavu napříč několika agregacemi v rámci stejného doménového modelu.
Obrázek 7–14 Události domény pro vynucení konzistence mezi více agregacemi ve stejné doméně
Obrázek 7–14 ukazuje, jak je dosaženo konzistence mezi agregacemi událostmi domény. Když uživatel zahájí objednávku, agregace objednávky odešle OrderStarted
událost domény. Událost domény OrderStarted zpracovává agregace kupujícího k vytvoření objektu Kupujícího v objednávce mikroslužby na základě původních informací o uživateli z mikroslužby identity (s informacemi poskytnuta v příkazu CreateOrder).
Alternativně můžete mít agregovaný kořenový odběr událostí vyvolaných členy jejích agregací (podřízených entit). Každá podřízená entita OrderItem může například vyvolat událost, když je cena položky vyšší než konkrétní částka nebo když je částka položky produktu příliš vysoká. Agregační kořen pak může tyto události přijmout a provést globální výpočet nebo agregaci.
Je důležité pochopit, že tato komunikace založená na událostech není implementována přímo v rámci agregací; potřebujete implementovat obslužné rutiny událostí domény.
Zpracování událostí domény je problém aplikace. Vrstva doménového modelu by se měla soustředit pouze na logiku domény – věci, kterým by odborník na doménu rozuměl, a ne na aplikační infrastrukturu, jako jsou obslužné rutiny a akce trvalosti vedlejších účinků pomocí úložišť. Úroveň aplikační vrstvy je proto tam, kde byste měli mít obslužné rutiny událostí domény, které aktivují akce při vyvolání události domény.
Události domény se dají použít také k aktivaci libovolného počtu akcí aplikace a co je důležitější, musí být otevřené, aby se toto číslo v budoucnu zvýšilo odděleným způsobem. Například při spuštění objednávky můžete chtít publikovat událost domény, aby se tyto informace rozšířily do jiných agregací nebo dokonce za účelem vyvolání akcí aplikace, jako jsou oznámení.
Klíčovým bodem je otevřený počet akcí, které se mají spustit, když dojde k události domény. Nakonec se akce a pravidla v doméně a aplikaci zvětšují. Složitost nebo početakcích new
Tato změna by mohla vést k novým chybám a tento přístup se také týká principu Open/Closed z SOLID. Nejen to, původní třída, která orchestrovala operace, se zvětšovala a zvětšovala, což jde proti principu jednotné odpovědnosti (SRP).
Na druhou stranu, pokud používáte události domény, můžete vytvořit jemně odstupňovanou a oddělenou implementaci oddělením odpovědností pomocí tohoto přístupu:
- Odeslat příkaz (například CreateOrder).
- Přijme příkaz v obslužné rutině příkazu.
- Proveďte transakci s jednou agregací.
- (Volitelné) Vyvolání událostí domény pro vedlejší účinky (například OrderStartedDomainEvent).
- Zpracování událostí domény (v rámci aktuálního procesu), které spustí otevřený počet vedlejších účinků v několika agregacích nebo akcích aplikace. Příklad:
- Ověřte nebo vytvořte kupujícího a způsob platby.
- Vytvořte a odešlete související událost integrace do sběrnice událostí, která rozšíří stavy mezi mikroslužby nebo aktivuje externí akce, jako je odeslání e-mailu kupujícímu.
- Zpracování jiných vedlejších účinků.
Jak je znázorněno na obrázku 7–15 počínaje stejnou událostí domény, můžete zpracovat několik akcí souvisejících s jinými agregacemi v doméně nebo dalšími akcemi aplikace, které potřebujete provést napříč mikroslužbami, které se připojují k událostem integrace a sběrnici událostí.
Obrázek 7–15 Zpracování více akcí na doménu
Ve vrstvě aplikace může existovat několik obslužných rutin pro stejnou událost domény. Jedna obslužná rutina dokáže vyřešit konzistenci mezi agregacemi a jinou obslužnou rutinou může publikovat událost integrace, takže s ní můžou dělat jiné mikroslužby. Obslužné rutiny událostí jsou obvykle v aplikační vrstvě, protože pro chování mikroslužby použijete objekty infrastruktury, jako jsou úložiště nebo rozhraní API aplikace. V tom smyslu jsou obslužné rutiny událostí podobné obslužné rutině příkazů, takže obě jsou součástí aplikační vrstvy. Důležitým rozdílem je, že příkaz by měl být zpracován pouze jednou. Událost domény může být zpracována nulakrát nebo nkrát , protože ji může přijímat více příjemců nebo obslužných rutin událostí s jiným účelem pro každou obslužnou rutinu.
Počet otevřených obslužných rutin na událost domény umožňuje přidat tolik pravidel domény, kolik potřebujete, aniž by to ovlivnilo aktuální kód. Například implementace následujícího obchodního pravidla může být stejně snadná jako přidání několika obslužných rutin událostí (nebo dokonce jenom jednoho):
Když celková částka zakoupená zákazníkem v obchodě překročí libovolný počet objednávek 6 000 USD, uplatní se na každou novou objednávku 10 % slevy a upozorní zákazníka e-mailem na tuto slevu pro budoucí objednávky.
Implementace událostí domény
V jazyce C# je událost domény jednoduše strukturou nebo třídou uchovávání dat, jako je DTO, se všemi informacemi souvisejícími s tím, co se právě stalo v doméně, jak je znázorněno v následujícím příkladu:
public class OrderStartedDomainEvent : INotification
{
public string UserId { get; }
public string UserName { get; }
public int CardTypeId { get; }
public string CardNumber { get; }
public string CardSecurityNumber { get; }
public string CardHolderName { get; }
public DateTime CardExpiration { get; }
public Order Order { get; }
public OrderStartedDomainEvent(Order order, string userId, string userName,
int cardTypeId, string cardNumber,
string cardSecurityNumber, string cardHolderName,
DateTime cardExpiration)
{
Order = order;
UserId = userId;
UserName = userName;
CardTypeId = cardTypeId;
CardNumber = cardNumber;
CardSecurityNumber = cardSecurityNumber;
CardHolderName = cardHolderName;
CardExpiration = cardExpiration;
}
}
Jedná se v podstatě o třídu, která obsahuje všechna data související s událostí OrderStarted.
Z hlediska všudypřítomného jazyka domény, protože událost je něco, co se stalo v minulosti, název třídy události by měl být reprezentován jako minulý čas sloveso, například OrderStartedDomainEvent nebo OrderShippedDomainEvent. Takto se událost domény implementuje v mikroslužbě objednávání v eShopu.
Jak už jsme uvedli dříve, důležitou vlastností událostí je, že protože událost je něco, co se stalo v minulosti, nemělo by se měnit. Proto musí být neměnnou třídou. V předchozím kódu vidíte, že vlastnosti jsou jen pro čtení. Neexistuje způsob, jak objekt aktualizovat, můžete hodnoty nastavit pouze při jeho vytvoření.
Je důležité zde zdůraznit, že pokud by se události domény zpracovávaly asynchronně, pomocí fronty, která vyžadovala serializaci a deserializaci objektů událostí, vlastnosti by musely být "private set" místo jen pro čtení, takže deserializátor by mohl přiřadit hodnoty při vyřazení z fronty. Nejedná se o problém v mikroslužbě Ordering, protože pub/sub domény je implementovaná synchronně pomocí MediatR.
Vyvolání událostí domény
Další otázkou je, jak vyvolat událost domény, aby dosáhla souvisejících obslužných rutin událostí. Můžete použít více přístupů.
Udi Dahan původně navrhl (například v několika souvisejících příspěvcích, například Domain Events – Take 2) pomocí statické třídy pro správu a vyvolávání událostí. To může zahrnovat statickou třídu s názvem DomainEvents, která by vyvolala události domény okamžitě při volání pomocí syntaxe jako DomainEvents.Raise(Event myEvent)
. Jimmy Bogard napsal blogový příspěvek (posílení vaší domény: Domain Events), který doporučuje podobný přístup.
Pokud je však třída událostí domény statická, odešle také obslužné rutiny okamžitě. Díky tomu je testování a ladění obtížnější, protože obslužné rutiny událostí s logikou vedlejších účinků se spustí okamžitě po vyvolání události. Při testování a ladění se jen chcete zaměřit na to, co se děje v aktuálních agregačních třídách; Nechcete být náhle přesměrováni na jiné obslužné rutiny událostí pro vedlejší účinky související s jinými agregacemi nebo logikou aplikace. Proto se vyvinuly další přístupy, jak je vysvětleno v další části.
Odložený přístup k vyvolání a odesílání událostí
Místo okamžitého odeslání do obslužné rutiny události domény je lepší přidat události domény do kolekce a pak tyto události domény odeslat přímo před nebo přímo po potvrzení transakce (stejně jako u SaveChanges v EF). (Tento přístup popsal Jimmy Bogard v tomto příspěvku .Lepší vzor událostí domény.)
Rozhodnutí, zda odesíláte události domény přímo před nebo vpravo po potvrzení transakce je důležité, protože určuje, zda budete zahrnout vedlejší účinky jako součást stejné transakce nebo v různých transakcích. V druhém případě musíte řešit konečnou konzistenci napříč více agregacemi. Toto téma je popsáno v další části.
Odložený přístup je to, co eShop používá. Nejprve přidáte události, které probíhají ve vašich entitách, do kolekce nebo seznamu událostí na entitu. Tento seznam by měl být součástí objektu entity nebo ještě lépe součástí základní třídy entity, jak je znázorněno v následujícím příkladu základní třídy Entity:
public abstract class Entity
{
//...
private List<INotification> _domainEvents;
public List<INotification> DomainEvents => _domainEvents;
public void AddDomainEvent(INotification eventItem)
{
_domainEvents = _domainEvents ?? new List<INotification>();
_domainEvents.Add(eventItem);
}
public void RemoveDomainEvent(INotification eventItem)
{
_domainEvents?.Remove(eventItem);
}
//... Additional code
}
Když chcete vyvolat událost, stačí ji přidat do kolekce událostí z kódu v libovolné metodě agregační kořenové entity.
Následující kód, který je součástí kořenového adresáře agregace objednávky v eShopu, ukazuje příklad:
var orderStartedDomainEvent = new OrderStartedDomainEvent(this, //Order object
cardTypeId, cardNumber,
cardSecurityNumber,
cardHolderName,
cardExpiration);
this.AddDomainEvent(orderStartedDomainEvent);
Všimněte si, že jediná věc, kterou AddDomainEvent metoda dělá, je přidání události do seznamu. Ještě není odeslána žádná událost a ještě není vyvolána žádná obslužná rutina události.
Ve skutečnosti chcete události odeslat později, když potvrdíte transakci do databáze. Pokud používáte Entity Framework Core, znamená to v metodě SaveChanges ef DbContext, jako v následujícím kódu:
// EF Core DbContext
public class OrderingContext : DbContext, IUnitOfWork
{
// ...
public async Task<bool> SaveEntitiesAsync(CancellationToken cancellationToken = default(CancellationToken))
{
// Dispatch Domain Events collection.
// Choices:
// A) Right BEFORE committing data (EF SaveChanges) into the DB. This makes
// a single transaction including side effects from the domain event
// handlers that are using the same DbContext with Scope lifetime
// B) Right AFTER committing data (EF SaveChanges) into the DB. This makes
// multiple transactions. You will need to handle eventual consistency and
// compensatory actions in case of failures.
await _mediator.DispatchDomainEventsAsync(this);
// After this line runs, all the changes (from the Command Handler and Domain
// event handlers) performed through the DbContext will be committed
var result = await base.SaveChangesAsync();
}
}
Pomocí tohoto kódu odešlete události entity do příslušných obslužných rutin událostí.
Celkový výsledek je, že jste oddělili vyvolání události domény (jednoduché přidání do seznamu v paměti) od jeho odeslání do obslužné rutiny události. Kromě toho můžete v závislosti na tom, jaký druh dispečera používáte, odesílat události synchronně nebo asynchronně.
Mějte na paměti, že hranice transakcí přicházejí do významné hry zde. Pokud vaše jednotka práce a transakce může zahrnovat více než jednu agregaci (jako při použití EF Core a relační databáze), může to fungovat dobře. Pokud ale transakce nemůže zahrnovat agregace, musíte implementovat další kroky pro dosažení konzistence. To je další důvod, proč trvalost není univerzální; závisí na používaném systému úložiště.
Jedna transakce mezi agregacemi a konečnou konzistencí napříč agregacemi
Otázka, zda provést jednu transakci napříč agregacemi a spoléhat se na konečnou konzistenci těchto agregací, je problematické. Mnoho autorů DDD, jako je Eric Evans a Vaughn Vernon, obhajuje pravidlo, že jedna transakce = jedna agregace, a proto argumentuje konečnou konzistenci napříč agregacemi. Například v jeho knize Domain-Driven Design Eric Evans říká toto:
U všech pravidel, která zahrnují agregace, se neočekává, že bude vždy aktuální. Prostřednictvím zpracování událostí, dávkového zpracování nebo jiných mechanismů aktualizace je možné během určité doby vyřešit jiné závislosti. (strana 128)
Vaughn Vernon říká v efektivním agregačním návrhu následující . Část II: Spolupráce agregací:
Proto pokud spuštění příkazu v jedné agregační instanci vyžaduje, aby se další obchodní pravidla spouštěla u jedné nebo více agregací, použijte konečnou konzistenci [...] Existuje praktický způsob, jak podporovat konečnou konzistenci v modelu DDD. Agregační metoda publikuje událost domény, která je včas doručena jednomu nebo více asynchronním odběratelům.
Tento důvod je založený na přijetí jemně odstupňovaných transakcí místo transakcí, které pokrývají mnoho agregací nebo entit. Myšlenka spočívá v tom, že v druhém případě bude počet zámků databáze podstatný v rozsáhlých aplikacích s vysokými potřebami škálovatelnosti. Přijetí skutečnosti, že vysoce škálovatelné aplikace nemusí mít okamžitou transakční konzistenci mezi více agregacemi, pomáhá přijmout koncept konečné konzistence. Firmy často nevyžadují atomické změny a v každém případě je odpovědností odborníků na doménu, aby řekli, jestli konkrétní operace potřebují atomické transakce, nebo ne. Pokud operace vždy potřebuje atomické transakce mezi více agregacemi, můžete se zeptat, jestli by agregace měla být větší nebo nebyla správně navržena.
Jiní vývojáři a architekti, jako je Jimmy Bogard, jsou ale v pořádku s tím, že pro stejný původní příkaz pokrývají jednu transakci napříč několika agregacemi, ale pouze v případě, že tyto další agregace souvisejí s vedlejšími účinky stejného původního příkazu. Například v modelu lepších událostí domény Bogard říká toto:
Obvykle chci vedlejší účinky události domény dojít v rámci stejné logické transakce, ale ne nutně ve stejném rozsahu zvýšení doménové události [...] Těsně před potvrzením transakce odešleme události do příslušných obslužných rutin.
Pokud odesíláte události domény přímo před potvrzením původní transakce, je to proto, že chcete vedlejší účinky těchto událostí zahrnout do stejné transakce. Například pokud EF DbContext SaveChanges metoda selže, transakce vrátí zpět všechny změny, včetně výsledku operací vedlejšího efektu implementovaných souvisejícími obslužnými rutinami událostí domény. Důvodem je to, že obor životnosti DbContext je ve výchozím nastavení definovaný jako "vymezený". Objekt DbContext se proto sdílí napříč více objekty úložiště, které se vytvářejí v rámci stejného oboru nebo grafu objektu. To se shoduje s oborem HttpRequest při vývoji webových rozhraní API nebo aplikací MVC.
Ve skutečnosti mohou být oba přístupy (jedna atomická transakce i konečná konzistence) správné. Ve skutečnosti závisí na vašich požadavcích na doménu nebo firmu a na tom, co vám říkají odborníci na doménu. Záleží také na tom, jak škálovatelná je služba (podrobnější transakce mají menší dopad na zámky databáze). A záleží na tom, kolik investic jste ochotni do kódu provést, protože konečná konzistence vyžaduje složitější kód, aby bylo možné detekovat nekonzistence napříč agregacemi a nutnost implementovat kompenzační akce. Mějte na paměti, že pokud potvrdíte změny původní agregace a potom se události odesílají, pokud dojde k problému a obslužné rutiny událostí nemohou potvrdit jejich vedlejší účinky, budete mít nekonzistence mezi agregacemi.
Způsob, jak umožnit kompenzační akce, by bylo uložit události domény do dalších databázových tabulek, aby mohly být součástí původní transakce. Potom můžete mít dávkový proces, který zjistí nekonzistence a spouští kompenzační akce porovnáním seznamu událostí s aktuálním stavem agregací. Kompenzační akce jsou součástí komplexního tématu, které bude vyžadovat hloubkovou analýzu z vaší strany, která zahrnuje diskuzi s obchodními uživateli a odborníky na doménu.
V každém případě můžete zvolit požadovaný přístup. Počáteční odložený přístup , který vyvolává události před potvrzením, takže použijete jednu transakci, je nejjednodušší přístup při použití EF Core a relační databáze. Implementace a platnost je v mnoha obchodních případech jednodušší. Jedná se také o přístup použitý v objednávce mikroslužeb v eShopu.
Ale jak tyto události skutečně odesíláte do příslušných obslužných rutin událostí? _mediator
Jaký objekt vidíte v předchozím příkladu? Musí to být v souladu s technikami a artefakty, které používáte k mapování mezi událostmi a jejich obslužnými rutinami událostí.
Dispečer událostí domény: mapování z událostí na obslužné rutiny událostí
Jakmile budete moct události odeslat nebo publikovat, potřebujete nějaký druh artefaktu, který událost publikuje, aby ho každá související obslužná rutina mohla získat a zpracovat vedlejší účinky na základě této události.
Jedním z přístupů je skutečný systém zasílání zpráv nebo dokonce sběrnice událostí, pravděpodobně založený na sběrnici service bus na rozdíl od událostí v paměti. V prvním případě by však skutečné zasílání zpráv bylo příliš efektivní pro zpracování událostí domény, protože stačí tyto události zpracovat v rámci stejného procesu (to znamená ve stejné doméně a aplikační vrstvě).
Přihlášení k odběru událostí domény
Při použití MediatR musí každá obslužná rutina události použít typ události, který je k dispozici v obecném parametru INotificationHandler
rozhraní, jak je vidět v následujícím kódu:
public class ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler
: INotificationHandler<OrderStartedDomainEvent>
Na základě vztahu mezi událostí a obslužnou rutinou události, kterou lze považovat za odběr, může artefakt MediatR zjistit všechny obslužné rutiny událostí pro každou událost a aktivovat každý z těchto obslužných rutin událostí.
Zpracování událostí domény
Nakonec obslužná rutina události obvykle implementuje kód aplikační vrstvy, který používá úložiště infrastruktury k získání požadovaných dalších agregací a ke spuštění logiky domény s vedlejším účinkem. Následující kód obslužné rutiny události domény v eShopu ukazuje příklad implementace.
public class ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler
: INotificationHandler<OrderStartedDomainEvent>
{
private readonly ILogger _logger;
private readonly IBuyerRepository _buyerRepository;
private readonly IOrderingIntegrationEventService _orderingIntegrationEventService;
public ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler(
ILogger<ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler> logger,
IBuyerRepository buyerRepository,
IOrderingIntegrationEventService orderingIntegrationEventService)
{
_buyerRepository = buyerRepository ?? throw new ArgumentNullException(nameof(buyerRepository));
_orderingIntegrationEventService = orderingIntegrationEventService ?? throw new ArgumentNullException(nameof(orderingIntegrationEventService));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public async Task Handle(
OrderStartedDomainEvent domainEvent, CancellationToken cancellationToken)
{
var cardTypeId = domainEvent.CardTypeId != 0 ? domainEvent.CardTypeId : 1;
var buyer = await _buyerRepository.FindAsync(domainEvent.UserId);
var buyerExisted = buyer is not null;
if (!buyerExisted)
{
buyer = new Buyer(domainEvent.UserId, domainEvent.UserName);
}
buyer.VerifyOrAddPaymentMethod(
cardTypeId,
$"Payment Method on {DateTime.UtcNow}",
domainEvent.CardNumber,
domainEvent.CardSecurityNumber,
domainEvent.CardHolderName,
domainEvent.CardExpiration,
domainEvent.Order.Id);
var buyerUpdated = buyerExisted ?
_buyerRepository.Update(buyer) :
_buyerRepository.Add(buyer);
await _buyerRepository.UnitOfWork
.SaveEntitiesAsync(cancellationToken);
var integrationEvent = new OrderStatusChangedToSubmittedIntegrationEvent(
domainEvent.Order.Id, domainEvent.Order.OrderStatus.Name, buyer.Name);
await _orderingIntegrationEventService.AddAndSaveEventAsync(integrationEvent);
OrderingApiTrace.LogOrderBuyerAndPaymentValidatedOrUpdated(
_logger, buyerUpdated.Id, domainEvent.Order.Id);
}
}
Předchozí kód obslužné rutiny události domény se považuje za kód aplikační vrstvy, protože používá úložiště infrastruktury, jak je vysvětleno v další části vrstvy trvalosti infrastruktury. Obslužné rutiny událostí můžou také používat jiné součásti infrastruktury.
Události domény můžou generovat události integrace, které se publikují mimo hranice mikroslužeb.
Nakonec je důležité zmínit, že můžete někdy chtít rozšířit události napříč několika mikroslužbami. Toto šíření je událost integrace a může být publikována prostřednictvím sběrnice událostí z jakékoli konkrétní obslužné rutiny události domény.
Závěry o událostech domény
Jak je uvedeno, použijte události domény k explicitní implementaci vedlejších účinků změn v rámci vaší domény. Pokud chcete použít terminologii DDD, použijte události domény k explicitní implementaci vedlejších efektů napříč jednou nebo několika agregacemi. Kromě toho a pro lepší škálovatelnost a menší dopad na zámky databáze používejte konečnou konzistenci mezi agregacemi ve stejné doméně.
Referenční aplikace používá MediatR k synchronnímu šíření událostí domény napříč agregacemi v rámci jedné transakce. Můžete ale také použít implementaci AMQP, jako je RabbitMQ nebo Azure Service Bus , k asynchronnímu šíření událostí domény pomocí konečné konzistence, ale jak je uvedeno výše, musíte zvážit potřebu kompenzačních akcí v případě selhání.
Další materiály
Greg Young. Co je událost domény?
https://cqrs.files.wordpress.com/2010/11/cqrs_documents.pdf#page=25Jan Stenberg. Události domény a konečná konzistence
https://www.infoq.com/news/2015/09/domain-events-consistencyJimmy Bogard. Model lepších událostí domény
https://lostechies.com/jimmybogard/2014/05/13/a-better-domain-events-pattern/Vaughn Vernon. Účinná agregační návrhová část II: Spolupráce agregací
https://dddcommunity.org/wp-content/uploads/files/pdf_articles/Vernon_2011_2.pdfJimmy Bogard. Posílení domény: Události domény
https://lostechies.com/jimmybogard/2010/04/08/strengthening-your-domain-domain-events/Udi Dahan. Vytvoření plně zapouzdřených doménových modelů
https://udidahan.com/2008/02/29/how-to-create-fully-encapsulated-domain-models/Udi Dahan. Události domény – Take 2
https://udidahan.com/2008/08/25/domain-events-take-2/Udi Dahan. Doménové události – záchrana
https://udidahan.com/2009/06/14/domain-events-salvation/Cesar de la Torre. Události domény a události integrace v architektuře DDD a mikroslužeb
https://devblogs.microsoft.com/cesardelatorre/domain-events-vs-integration-events-in-domain-driven-design-and-microservices-architectures/