###### tags: `DDD` `CQRS` # Chi deve pubblicare gli eventi (changes) di un aggregato? Ci siamo fatti questo domanda mentre studiavamo con @MatteoPierro e @n2WDxYDNRoyZVJ_ckz3KhQ il capitolo 4 "Architecture" di "Implementing Domain-Driven Design" di Vaughn Vernon. Abbiamo provato a pensare a tutte le opzioni possibili per pubblicare eventi di dominio. ## Pubblicazione degli eventi **nel repository** (o **nell'event store**) quando si fa la `save()` dell'aggregato (come si fa in ["Exploring CQRS and EventSourcing"](https://www.microsoft.com/en-us/download/details.aspx?id=34774)) * +1 non posso dimenticarmelo (è bundled nel repo) * -1 ma è nascosta, troppo implicita, effetto collaterale non atteso (`least suprise principle`) * -1 due motivi diversi di cambiamento che stanno assieme E in questo caso dove controllo la transazionalità? Nel repository. ## Pubblicazione degli eventi **nell'aggregato** (come fa [IDDD](https://github.com/VaughnVernon/IDDD_Samples)) (vedi ad es https://github.com/xpepper/IDDD_Samples/blob/master/iddd_agilepm/src/main/java/com/saasovation/agilepm/domain/model/product/backlogitem/BacklogItem.java) * -1 rischio di avere gli eventi pubblicati ma non persistiti * -1 aggregato con side effect inaspettati (a parte cambiare sè stesso) * -1 inconsistenza nel caso di pubblicazione di più eventi in azioni diverse sullo stesso aggregato (vedi p.32 di IDDD) * -1 due motivi diversi di cambiamento (logiche di dominio e logiche di pubblicazione dell'evento) che stanno assieme ![](https://i.imgur.com/QNfSEj0.jpg) _(p.121 di IDDD)_ E dove controllo la **consistenza transazionale**? Nel chiamante (nel command handler / application service / use case). ## Pubblicazione degli eventi **nel command handler** (come fa [Marco Heimeshoff](https://twitter.com/heimeshoff)) * +1: più esplicito, aggregato senza side effect * -1: rischio di modificare l'aggregato, di salvare l'aggregato ma di non pubblicare gli eventi * -1 devi garantire l'ordine tra le due operazioni di `save` e `publish` E dove controllo la transazionalità? Nel chiamante (nel command handler) # Codebase examples * https://github.com/xpepper/cqrs-journey * https://github.com/VaughnVernon/IDDD_Samples * https://github.com/gregoryyoung/m-r/ * https://github.com/pdincau/briscola * https://github.com/eventuate-tram/eventuate-tram-examples-customers-and-orders ## Code examples ### IDDD p.32 ![](https://i.imgur.com/deUuekA.png) ### Marco Heimeshoff's DDD course ![](https://i.imgur.com/xpvSUMj.png) ### Exploring CQRS and ES ![](https://i.imgur.com/0OcHxT8.png) ... ![](https://i.imgur.com/obxAHsi.png) # Alcuni riferimenti bibliografici * "Implementing DDD" di Vaughn Vernon * "Exploring CQRS and EventSourcing" * altre persone che la pensano come noi... https://blog.jayway.com/2013/06/20/dont-publish-domain-events-return-them/ * ...e altre invece fanno come fa Vernon in IDDD: https://udidahan.com/2009/06/14/domain-events-salvation/ # Dubbi e considerazioni * dipende da cosa stai usando: se usi ES l'evento è collegato all'aggregato, quindi dovrebbe essere l'aggregato a lanciarlo? * a seconda che stai facendo EventSourcing, solo CQRS o anche solo DDD in una architettura event-driven, il modo in cui pubblichi gli eventi di dominio potrebbe cambiare. * EventSourcing alla fine può essere ridotto a “il modo in cui salviamo e leggiamo i nostri oggetti di dominio” ? E se è così è un dettaglio nascosto dietro un repository di qualche tipo. * CQRS essenzialmente è un bel modo per separare due concerns che possono essere molto diversi (e cambiare in modo diverso, e scalare anche in modo diverso), che aiuta ad avere un read model “disaccoppiato” con il write model, e.g. per poter creare delle view molto liberamente senza dover mettere mani sul modo in cui sono rappresentati i dati nel write model. * Quindi mi aspetto che sia ES che CQRS non entrino troppo nel linguaggio di dominio e nel modo in cui sono modellati gli oggetti di dominio. Potenzialmente guardando gli oggetti di dominio potrei non sapere se dietro le quinte uso ES. # Code examples ```ruby= class RevokeAnOrder def initialize(repository, events_publisher) @repository = repository @events_publisher = events_publisher end def call(order_id) within_transation do order = @repository.find(order_id) order.revoke() if order.changed? @repository.save(order) @events_publisher.publish(order.changes) end end end end ``` Greg Young e IDDD: L'incremento della "revision" (per risolvere con optimistic locking la concorrenza di scritture) avviene nella persistenza (`@repository.save(order)`). Q: Il numero di revisione e quello usato dai client per "ordinare" gli eventi sono la stessa cosa? No ```csharp= public class CustomerApplicationService { IEventStore _eventStore; IPricingService _pricingService; public CustomerApplicationService(IEventStore eventStore, IPricingService pricing) { _eventStore = eventStore; _pricingService = pricing; } public void LockForAccountOverdraft(CustomerId customerId, string comment) { var stream = _eventStore.LoadEventStream(customerId); var customer = new Customer(stream.Events); customer.LockForAccountOverdraft(comment, _pricingService); _eventStore.AppendToStream(customerId, stream.Version, customer.Changes); } public void LockCustomer(CustomerId customerId, string reason) { var stream = _eventStore.LoadEventStream(customerId); var customer = new Customer(stream.Events); customer.Lock(reason); _eventStore.AppendToStream( customerId, stream.Version, customer.Changes); } // other methods on this application service } ``` ### Come fare per evitare che l'initial revision "entri" nell'aggregato ma rimanga un dettaglio dell'infrastruttura di persistenza? ```ruby= class EventSourcedOrderRepository @xxx<Int, Int> # ⚠️ this should be thread-safe! def .find(order_id) event_stream = EventStream.load("order", order_id) @xxx[order_id] = event_stream.revision Order.load_from(event_stream) end def .save(order) initial_revision = @xxx[order.id] # check for conflicts ... # unique constraint on (aggregate id, revision) end end ``` Alternativa: `Order` (che usa il service) e subclass `RevisionedOrder` che usa solo il `repository` e che avrebbe in pancia anche il revision. :warning: you should use the same instance at the service level! (e.g. you should mutate the instance instead of creating a new instance from the original one). ```ruby= class EventSourcedOrderRepository def .find(order_id) event_stream = EventStream.load("order", order_id) RevisionedOrder.load_from(event_stream) # will also set the revision in the order end def .save(order) revisioned_order = (RevisionedOrder) order # check on revision_order.initial_revision_number # unique constraint on (aggregate id, revision) end private class RevisionedOrder < Order @initial_revision_number end end ``` ## 8 Feb 2024 @MatteoPierro: oggi ho visto la registrazione di un workshop di Vladik Khononov . l'autore di Learning Domain-Driven Design e una slide mi ha particolarmente colpito. E' relativa alla discussione che abbiamo avuto su chi debba gestire la versione dell'aggregato ```csharp= public interface SomeDBCampaignRepository : ICampaignRepository { Campaign Load(CampaignId id) { var events = DB.LoadEvents(id); return new Campaign(events); } ... void Save(Campaign campaign) { var expectedVersion = campaign.Version; DB.AppendEvents(campaign.DomainEvents.Since(expectedVersion), expectedVersion); campaign.Version = campaign.DomainEvents.Count(); } } ``` ```csharp= public interface SomeDBCampaignRepository : ICampaignRepository { ... void Save(Campaign campaign) { var expectedVersion = campaign.Version; DB.Save(campaign, expectedVersion); campaign.Version = campaign.Version + 1; campaign.UnpublishedDomainEvents.Reset(); // Assuming they were published in line } } ``` @MatteoPierro: anche la pubblicazione degli eventi viene fatto dal repository in maniera indiretta. Nel senso che secondo lui la pubblicazione va sempre fatto con l'outbox pattern. Dunque il repo piu' che pubblicare li salva temporaneamente su una tabella di appoggio @n2WDxYDNRoyZVJ_ckz3KhQ: Qui ancora non mi convince. La pubblicazione degli eventi (indipendentemente dal pattern usato, outbox, o altri) non mi sembra qualcosa da accoppiare al sistema di persistenza. Come già discusso, è anche possibile persistere l aggregato non ES ma cmq pubblicare gli eventi (a.k.a. "changes"). @MatteoPierro: SI e' quello che fa nella seconda slide. l'aggregato non e' ES ma pubblica domain events tramite l'outbox. @n2WDxYDNRoyZVJ_ckz3KhQ: ah ecco