Anteckning
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
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.
Använd domänhändelser för att uttryckligen implementera biverkningar av ändringar i din domän. Med andra ord, och med hjälp av DDD-terminologi, använder du domänhändelser för att uttryckligen implementera biverkningar i flera aggregeringar. Du kan också använda slutlig konsekvens mellan aggregeringar inom samma domän för bättre skalbarhet och mindre påverkan i databaslås.
Vad är en domänhändelse?
En händelse är något som har hänt tidigare. En domänhändelse är något som hände i domänen som du vill att andra delar av samma domän (pågående) ska känna till. De aviserade delarna reagerar vanligtvis på något sätt på händelserna.
En viktig fördel med domänhändelser är att biverkningar kan uttryckas explicit.
Om du till exempel bara använder Entity Framework och det måste finnas en reaktion på någon händelse, kodar du förmodligen det du behöver nära vad som utlöser händelsen. Så regeln kopplas, implicit, till koden, och du måste titta på koden för att förhoppningsvis inse att regeln implementeras där.
Å andra sidan gör användning av domänhändelser konceptet explicit, eftersom det finns en DomainEvent
och minst en DomainEventHandler
involverad.
I eShop-programmet blir användaren till exempel en köpare när en beställning skapas, så en OrderStartedDomainEvent
genereras och hanteras i ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler
, så det underliggande konceptet är uppenbart.
Kort uttryckt hjälper domänhändelser dig att uttryckligen uttrycka domänreglerna, baserat på det allestädes närvarande språk som tillhandahålls av domänexperterna. Domänhändelser möjliggör också en bättre uppdelning av problem mellan klasser inom samma domän.
Det är viktigt att se till att, precis som en databastransaktion, antingen alla åtgärder som är relaterade till en domänhändelse slutförs korrekt eller att ingen av dem gör det.
Domänhändelser liknar händelser i meddelandeformat, med en viktig skillnad. Med verkliga meddelanden, meddelandeköer, meddelandeköer eller en servicebuss med AMQP skickas alltid ett meddelande asynkront och kommuniceras mellan processer och datorer. Detta är användbart för att integrera flera avgränsade kontexter, mikrotjänster eller till och med olika program. Men med domänhändelser vill du skapa en händelse från den domänåtgärd som du för närvarande kör, men du vill att eventuella biverkningar ska inträffa inom samma domän.
Domänhändelserna och deras biverkningar (de åtgärder som utlöses efteråt och som hanteras av händelsehanterare) bör inträffa nästan omedelbart, vanligtvis i processen och inom samma domän. Domänhändelser kan därför vara synkrona eller asynkrona. Integreringshändelser bör dock alltid vara asynkrona.
Domänhändelser jämfört med integrationshändelser
Semantiskt är domän- och integrationshändelser samma sak: meddelanden om något som just hände. Genomförandet av dem måste dock vara annorlunda. Domänhändelser är bara meddelanden som skickas till en domänhändelseutskickare, som kan implementeras som en minnesintern medlare baserat på en IoC-container eller någon annan metod.
Å andra sidan är syftet med integreringshändelser att sprida bekräftade transaktioner och uppdateringar till ytterligare undersystem, oavsett om de är andra mikrotjänster, begränsade kontexter eller till och med externa program. Därför bör de bara inträffa om entiteten har sparats, annars är det som om hela åtgärden aldrig har inträffat.
Som tidigare nämnts måste integreringshändelser baseras på asynkron kommunikation mellan flera mikrotjänster (andra begränsade kontexter) eller till och med externa system/program.
Händelsebussgränssnittet behöver därför en infrastruktur som möjliggör kommunikation mellan processer och distribuerad kommunikation mellan potentiellt fjärranslutna tjänster. Den kan baseras på en kommersiell servicebuss, köer, en delad databas som används som postlåda eller något annat distribuerat och helst push-baserat meddelandesystem.
Domänhändelser som ett föredraget sätt att utlösa biverkningar i flera aggregeringar inom samma domän
Om körning av ett kommando som är relaterat till en aggregerad instans kräver att ytterligare domänregler körs på en eller flera ytterligare aggregeringar bör du utforma och implementera de biverkningarna som ska utlösas av domänhändelser. Som visas i bild 7–14 och som ett av de viktigaste användningsfallen bör en domänhändelse användas för att sprida tillståndsändringar över flera aggregeringar i samma domänmodell.
Bild 7-14. Domänhändelser för att framtvinga konsekvens mellan flera aggregeringar inom samma domän
Bild 7–14 visar hur konsekvens mellan aggregeringar uppnås med domänhändelser. När användaren initierar en order skickar orderaggregatet en OrderStarted
domänhändelse. Händelsen OrderStarted-domän hanteras av köparens aggregering för att skapa ett köparobjekt i beställningsmikrotjänsten, baserat på den ursprungliga användarinformationen från identitetsmikrotjänsten (med information som anges i kommandot CreateOrder).
Alternativt kan du låta den aggregerade roten prenumerera på händelser som genereras av medlemmar i dess aggregeringar (underordnade entiteter). Varje underordnad OrderItem-entitet kan till exempel skapa en händelse när artikelpriset är högre än ett visst belopp eller när produktartikelbeloppet är för högt. Den aggregerade roten kan sedan ta emot dessa händelser och utföra en global beräkning eller aggregering.
Det är viktigt att förstå att den här händelsebaserade kommunikationen inte implementeras direkt i aggregeringarna. du måste implementera domänhändelsehanterare.
Att hantera domänhändelserna är ett programproblem. Domänmodelllagret bör bara fokusera på domänlogik – saker som en domänexpert skulle förstå, inte programinfrastruktur som hanterare och beständighetsåtgärder med hjälp av lagringsplatser. Därför är programnivånivån där du bör ha domänhändelsehanterare som utlöser åtgärder när en domänhändelse aktiveras.
Domänhändelser kan också användas för att utlösa valfritt antal programåtgärder, och vad som är viktigare måste vara öppet för att öka antalet i framtiden på ett frikopplat sätt. När beställningen till exempel startas kanske du vill publicera en domänhändelse för att sprida informationen till andra aggregeringar eller till och med för att skapa programåtgärder som meddelanden.
Nyckelpunkten är det öppna antalet åtgärder som ska utföras när en domänhändelse inträffar. Slutligen kommer åtgärderna och reglerna i domänen och programmet att växa. Komplexiteten eller antalet biverkningsåtgärder när något händer kommer att växa, men om koden kopplades till "lim" (dvs. skapar specifika objekt med new
), skulle du varje gång du behövde lägga till en ny åtgärd också behöva ändra arbets- och testad kod.
Den här ändringen kan resultera i nya buggar och den här metoden strider även mot principen Öppna/Stängd från SOLID. Inte bara det, den ursprungliga klassen som orkestrerar åtgärderna skulle växa och växa, vilket strider mot principen om enskilt ansvar (SRP).
Om du å andra sidan använder domänhändelser kan du skapa en detaljerad och frikopplad implementering genom att separera ansvar med hjälp av den här metoden:
- Skicka ett kommando (till exempel CreateOrder).
- Ta emot kommandot i en kommandohanterare.
- Kör en enda aggregeringstransaktion.
- (Valfritt) Skapa domänhändelser för biverkningar (till exempel OrderStartedDomainEvent).
- Hantera domänhändelser (inom den aktuella processen) som kör ett öppet antal biverkningar i flera aggregeringar eller programåtgärder. Till exempel:
- Verifiera eller skapa köpare och betalningsmetod.
- Skapa och skicka en relaterad integrationshändelse till händelsebussen för att sprida tillstånd över mikrotjänster eller utlösa externa åtgärder som att skicka ett e-postmeddelande till köparen.
- Hantera andra biverkningar.
Som du ser i bild 7–15 kan du från och med samma domänhändelse hantera flera åtgärder relaterade till andra aggregeringar i domänen eller ytterligare programåtgärder som du behöver utföra mellan mikrotjänster som ansluter till integrationshändelser och händelsebussen.
Bild 7-15. Hantera flera åtgärder per domän
Det kan finnas flera hanterare för samma domänhändelse i programlagret, en hanterare kan lösa konsekvensen mellan aggregeringar och en annan hanterare kan publicera en integrationshändelse, så att andra mikrotjänster kan göra något med den. Händelsehanterarna finns vanligtvis i programskiktet eftersom du använder infrastrukturobjekt som lagringsplatser eller ett program-API för mikrotjänstens beteende. I det avseendet liknar händelsehanterare kommandohanterare, så båda är en del av programskiktet. Den viktiga skillnaden är att ett kommando endast ska bearbetas en gång. En domänhändelse kan bearbetas noll eller n gånger, eftersom den kan tas emot av flera mottagare eller händelsehanterare med olika syfte för varje hanterare.
Med ett öppet antal hanterare per domänhändelse kan du lägga till så många domänregler som behövs, utan att påverka aktuell kod. Det kan till exempel vara lika enkelt att implementera följande affärsregel som att lägga till några händelsehanterare (eller till och med bara en):
När det totala beloppet som köpts av en kund i butiken, över valfritt antal beställningar, överstiger 6 000 USD, tillämpar en rabatt på 10 % rabatt på varje ny beställning och meddelar kunden med ett e-postmeddelande om rabatten för framtida beställningar.
Implementera domänhändelser
I C# är en domänhändelse helt enkelt en datalagringsstruktur eller -klass, som en DTO, med all information som är relaterad till vad som just hände i domänen, som du ser i följande exempel:
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;
}
}
Det här är i princip en klass som innehåller alla data som är relaterade till händelsen OrderStarted.
När det gäller domänens allestädes närvarande språk, eftersom en händelse är något som hände tidigare, bör klassnamnet för händelsen representeras som ett tidigare tempusver, till exempel OrderStartedDomainEvent eller OrderShippedDomainEvent. Det är så domänhändelsen implementeras i beställningsmikrotjänsten i eShop.
Som tidigare nämnts är en viktig egenskap för händelser att eftersom en händelse är något som hände tidigare bör den inte ändras. Därför måste det vara en oföränderlig klass. Du kan se i föregående kod att egenskaperna är skrivskyddade. Det finns inget sätt att uppdatera objektet. Du kan bara ange värden när du skapar det.
Det är viktigt att markera här att om domänhändelser skulle hanteras asynkront, med hjälp av en kö som krävde serialisering och deserialisering av händelseobjekten, måste egenskaperna vara "privata uppsättningar" i stället för skrivskyddade, så deserialiseraren skulle kunna tilldela värdena vid dequeuing. Detta är inte ett problem i mikrotjänsten Ordering eftersom domänhändelsens pub/sub implementeras synkront med MediatR.
Skapa domänhändelser
Nästa fråga är hur du skapar en domänhändelse så att den når sina relaterade händelsehanterare. Du kan använda flera metoder.
Udi Dahan föreslogs ursprungligen (till exempel i flera relaterade inlägg, till exempel Domänhändelser – Ta 2) med hjälp av en statisk klass för att hantera och höja händelserna. Detta kan innehålla en statisk klass med namnet DomainEvents som skulle generera domänhändelser omedelbart när den anropas med hjälp av syntax som DomainEvents.Raise(Event myEvent)
. Jimmy Bogard skrev ett blogginlägg (Stärka din domän: Domänhändelser) som rekommenderar en liknande metod.
Men när domänhändelseklassen är statisk skickas den också till hanterare omedelbart. Detta gör det svårare att testa och felsöka eftersom händelsehanterarna med sidoeffektlogik körs omedelbart efter att händelsen har skapats. När du testar och felsöker vill du bara fokusera på vad som händer i de aktuella aggregerade klasserna. Du vill inte plötsligt omdirigeras till andra händelsehanterare för biverkningar relaterade till andra aggregeringar eller programlogik. Det är därför andra metoder har utvecklats, enligt beskrivningen i nästa avsnitt.
Den uppskjutna metoden för att generera och skicka händelser
I stället för att skicka till en domänhändelsehanterare omedelbart är en bättre metod att lägga till domänhändelserna i en samling och sedan skicka dessa domänhändelser direkt före eller direktefter att transaktionen har genomförts (som med SaveChanges i EF). (Detta tillvägagångssätt beskrevs av Jimmy Bogard i det här inlägget Ett bättre mönster för domänhändelser.)
Det är viktigt att avgöra om du skickar domänhändelserna direkt före eller direkt efter att transaktionen har genomförts, eftersom det avgör om du ska inkludera biverkningarna som en del av samma transaktion eller i olika transaktioner. I det senare fallet måste du hantera eventuell konsekvens mellan flera aggregeringar. Det här avsnittet beskrivs i nästa avsnitt.
Den uppskjutna metoden är vad eShop använder. Först lägger du till händelserna som händer i dina entiteter i en samling eller en lista över händelser per entitet. Listan bör vara en del av entitetsobjektet, eller ännu bättre, en del av din basentitetsklass, som du ser i följande exempel på entitetsbasklassen:
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
}
När du vill skapa en händelse lägger du bara till den i händelsesamlingen från kod på valfri metod i entiteten aggregate-root.
Följande kod, som är en del av orderaggregatroten på eShop, visar ett exempel:
var orderStartedDomainEvent = new OrderStartedDomainEvent(this, //Order object
cardTypeId, cardNumber,
cardSecurityNumber,
cardHolderName,
cardExpiration);
this.AddDomainEvent(orderStartedDomainEvent);
Observera att det enda som metoden AddDomainEvent gör är att lägga till en händelse i listan. Ingen händelse har skickats ännu och ingen händelsehanterare har anropats ännu.
Du vill faktiskt skicka händelserna senare när du checkar in transaktionen i databasen. Om du använder Entity Framework Core innebär det i metoden SaveChanges i DIN EF DbContext, som i följande kod:
// 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();
}
}
Med den här koden skickar du entitetshändelserna till respektive händelsehanterare.
Det övergripande resultatet är att du har frikopplat höjningen av en domänhändelse (ett enkelt tillägg i en lista i minnet) från att skicka den till en händelsehanterare. Beroende på vilken typ av avsändare du använder kan du dessutom skicka händelserna synkront eller asynkront.
Tänk på att transaktionsgränser spelar en viktig roll här. Om din arbets- och transaktionsenhet kan sträcka sig över mer än en aggregering (som när du använder EF Core och en relationsdatabas) kan detta fungera bra. Men om transaktionen inte kan sträcka sig över aggregeringar måste du implementera ytterligare steg för att uppnå konsekvens. Detta är en annan orsak till att envishets okunnighet inte är universell; det beror på vilket lagringssystem du använder.
Enskild transaktion mellan aggregeringar och eventuell konsekvens mellan aggregeringar
Frågan om huruvida en enskild transaktion ska utföras över aggregeringar jämfört med att förlita sig på slutlig konsekvens mellan dessa aggregat är kontroversiell. Många DDD-författare som Eric Evans och Vaughn Vernon förespråkar regeln att en transaktion = en aggregering och därför argumenterar för slutlig konsekvens mellan aggregat. I sin bok Domain-Driven Design säger Eric Evans till exempel följande:
Alla regler som sträcker sig över aggregeringar förväntas inte alltid vara uppdaterade. Genom händelsebearbetning, batchbearbetning eller andra uppdateringsmekanismer kan andra beroenden lösas inom en viss tid. (sida 128)
Vaughn Vernon säger följande i Effective Aggregate Design. Del II: Få aggregeringar att fungera tillsammans:
Om du kör ett kommando på en aggregerad instans kräver därför att ytterligare affärsregler körs på en eller flera aggregeringar, använder du slutlig konsekvens [...] Det finns ett praktiskt sätt att stödja eventuell konsekvens i en DDD-modell. En aggregeringsmetod publicerar en domänhändelse som levereras i tid till en eller flera asynkrona prenumeranter.
Den här logiken bygger på att omfatta detaljerade transaktioner i stället för transaktioner som omfattar många aggregeringar eller entiteter. Tanken är att i det andra fallet kommer antalet databaslås att vara betydande i storskaliga program med stora skalbarhetsbehov. Att ta till sig det faktum att mycket skalbara program inte behöver ha omedelbar transaktionskonsekvens mellan flera aggregeringar hjälper till att acceptera begreppet slutlig konsekvens. Atomiska förändringar behövs ofta inte av verksamheten, och det är i vilket fall som helst domänexperternas ansvar att säga om vissa åtgärder behöver atomiska transaktioner eller inte. Om en åtgärd alltid behöver en atomisk transaktion mellan flera aggregeringar kan du fråga dig om din aggregering ska vara större eller inte har utformats korrekt.
Men andra utvecklare och arkitekter som Jimmy Bogard är okej med att sträcka sig över en enda transaktion över flera aggregat , men bara när dessa ytterligare aggregat är relaterade till biverkningar för samma ursprungliga kommando. Till exempel, i Ett bättre domänhändelser mönster, säger Bogard detta:
Vanligtvis vill jag att biverkningarna av en domänhändelse ska inträffa inom samma logiska transaktion, men inte nödvändigtvis i samma omfattning för att höja domänhändelsen [...] Precis innan vi genomför transaktionen skickar vi våra händelser till respektive hanterare.
Om du skickar domänhändelserna direkt innan du genomför den ursprungliga transaktionen beror det på att du vill att biverkningarna av dessa händelser ska inkluderas i samma transaktion. Om metoden EF DbContext SaveChanges till exempel misslyckas återställer transaktionen alla ändringar, inklusive resultatet av eventuella sidoeffektåtgärder som implementerats av de relaterade domänhändelsehanterarna. Det beror på att DbContext-livslängdsomfånget som standard definieras som "scoped". Därför delas DbContext-objektet mellan flera lagringsplatsobjekt som instansieras inom samma omfång eller objektdiagram. Detta sammanfaller med HttpRequest-omfånget när du utvecklar webb-API eller MVC-appar.
Båda metoderna (enkel atomisk transaktion och slutlig konsekvens) kan faktiskt vara rätt. Det beror verkligen på din domän eller dina affärskrav och vad domänexperterna säger till dig. Det beror också på hur skalbar du behöver tjänsten för att vara (mer detaljerade transaktioner har mindre inverkan när det gäller databaslås). Och det beror på hur mycket investeringar du är villig att göra i din kod, eftersom eventuell konsekvens kräver mer komplex kod för att identifiera möjliga inkonsekvenser mellan aggregeringar och behovet av att implementera kompenserande åtgärder. Tänk på att om du checkar in ändringar i den ursprungliga aggregeringen och efteråt, när händelserna skickas, om det finns ett problem och händelsehanterarna inte kan genomföra sina biverkningar, kommer du att ha inkonsekvenser mellan aggregeringar.
Ett sätt att tillåta kompenserande åtgärder är att lagra domänhändelserna i ytterligare databastabeller så att de kan ingå i den ursprungliga transaktionen. Efteråt kan du ha en batchprocess som identifierar inkonsekvenser och kör kompenserande åtgärder genom att jämföra listan över händelser med det aktuella tillståndet för aggregeringarna. Kompensationsåtgärderna är en del av ett komplext ämne som kräver djupgående analys från din sida, vilket innefattar att diskutera det med företagsanvändaren och domänexperterna.
I vilket fall som helst kan du välja den metod du behöver. Men den första uppskjutna metoden – att höja händelserna innan du genomför, så att du använder en enda transaktion – är den enklaste metoden när du använder EF Core och en relationsdatabas. Det är enklare att implementera och vara giltig i många affärsfall. Det är också den metod som används i beställningsmikrotjänsten i eShop.
Men hur skickar du faktiskt dessa händelser till respektive händelsehanterare?
_mediator
Vilket objekt visas i föregående exempel? Det har att göra med de tekniker och artefakter som du använder för att mappa mellan händelser och deras händelsehanterare.
Domänhändelseutskickaren: mappa från händelser till händelsehanterare
När du har kunnat skicka eller publicera händelserna behöver du någon form av artefakt som publicerar händelsen, så att varje relaterad hanterare kan hämta den och bearbeta biverkningar baserat på den händelsen.
En metod är ett verkligt meddelandesystem eller till och med en händelsebuss, eventuellt baserat på en servicebuss i stället för minnesinterna händelser. Men för det första fallet skulle verkliga meddelanden vara overkill för bearbetning av domänhändelser, eftersom du bara behöver bearbeta dessa händelser inom samma process (det vill: inom samma domän- och programskikt).
Prenumerera på domänhändelser
När du använder MediatR måste varje händelsehanterare använda en händelsetyp som anges på den allmänna parametern i INotificationHandler
gränssnittet, som du kan se i följande kod:
public class ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler
: INotificationHandler<OrderStartedDomainEvent>
Baserat på relationen mellan händelse- och händelsehanterare, som kan betraktas som prenumerationen, kan MediatR-artefakten identifiera alla händelsehanterare för varje händelse och utlösa var och en av dessa händelsehanterare.
Hantera domänhändelser
Slutligen implementerar händelsehanteraren vanligtvis programlagerkod som använder infrastrukturlagringsplatser för att hämta de ytterligare aggregeringar som krävs och för att köra domänlogik med sidoeffekt. Följande kod för domänhändelsehanterare på eShop visar ett implementeringsexempel.
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);
}
}
Den tidigare domänhändelsehanterarkoden betraktas som kod för programlager eftersom den använder infrastrukturlagringsplatser, enligt beskrivningen i nästa avsnitt på infrastrukturpersistence-lagret. Händelsehanterare kan också använda andra infrastrukturkomponenter.
Domänhändelser kan generera integreringshändelser som publiceras utanför mikrotjänstgränserna
Slutligen är det viktigt att nämna att du ibland vill sprida händelser över flera mikrotjänster. Spridningen är en integrationshändelse och kan publiceras via en händelsebuss från en specifik domänhändelsehanterare.
Slutsatser om domänhändelser
Använd som sagt domänhändelser för att uttryckligen implementera biverkningar av ändringar i din domän. Om du vill använda DDD-terminologi använder du domänhändelser för att explicit implementera biverkningar i en eller flera aggregeringar. Dessutom, och för bättre skalbarhet och mindre påverkan på databaslås, använder du slutlig konsekvens mellan aggregeringar inom samma domän.
Referensappen använder MediatR för att sprida domänhändelser synkront över aggregeringar, inom en enda transaktion. Men du kan också använda vissa AMQP-implementeringar som RabbitMQ eller Azure Service Bus för att sprida domänhändelser asynkront, med hjälp av slutlig konsekvens, men som nämnts ovan måste du överväga behovet av kompenserande åtgärder vid fel.
Ytterligare resurser
Greg Young. Vad är en domänhändelse?
https://cqrs.files.wordpress.com/2010/11/cqrs_documents.pdf#page=25Jan Stenberg. Domänhändelser och slutlig konsekvens
https://www.infoq.com/news/2015/09/domain-events-consistencyJimmy Bogard. Ett bättre mönster för domänhändelser
https://lostechies.com/jimmybogard/2014/05/13/a-better-domain-events-pattern/Vaughn Vernon. Effektiv aggregeringsdesign del II: Få aggregeringar att fungera tillsammans
https://dddcommunity.org/wp-content/uploads/files/pdf_articles/Vernon_2011_2.pdfJimmy Bogard. Stärka din domän: Domänhändelser
https://lostechies.com/jimmybogard/2010/04/08/strengthening-your-domain-domain-events/Udi Dahan. Så här skapar du helt inkapslade domänmodeller
https://udidahan.com/2008/02/29/how-to-create-fully-encapsulated-domain-models/Udi Dahan. Domänhändelser – Ta 2
https://udidahan.com/2008/08/25/domain-events-take-2/Udi Dahan. Domänhändelser – Frälsning
https://udidahan.com/2009/06/14/domain-events-salvation/Cesar de la Torre. Domänhändelser jämfört med integrationshändelser i DDD- och mikrotjänstarkitekturer
https://devblogs.microsoft.com/cesardelatorre/domain-events-vs-integration-events-in-domain-driven-design-and-microservices-architectures/