Is it safe to publish Domain Event before persisting the Aggregate?
Asked Answered
K

4

19

In many different projects I have seen 2 different approaches of raising Domain Events.

  1. Raise Domain Event directly from aggregate. For example imagine you have Customer aggregate and here is a method inside it:

    public virtual void ChangeEmail(string email)
    {
        if(this.Email != email)
        {
            this.Email = email;
            DomainEvents.Raise<CustomerChangedEmail>(new CustomerChangedEmail(email));
        }
    }
    

    I can see 2 problems with this approach. The first one is that the event is raised regardless of whether the aggregate is persisted or not. Imagine if you want to send an email to a customer after successful registration. An event "CustomerChangedEmail" will be raised and some IEmailSender will send the email even if the aggregate wasn't saved. The second problem with the current implementation is that every event should be immutable. So the question is how can I initialize its "OccuredOn" property? Only inside aggregate! Its logical, right! It forces me to pass ISystemClock (system time abstraction) to each and every method on aggregate! Whaaat??? Don't you find this design brittle and cumbersome? Here is what we'll come up with:

    public virtual void ChangeEmail(string email, ISystemClock systemClock)
    {
        if(this.Email != email)
        {
            this.Email = email;
            DomainEvents.Raise<CustomerChangedEmail>(new CustomerChangedEmail(email, systemClock.DateTimeNow));
        }
    }
    
  2. The second approach is to go what Event Sourcing pattern recommends to do. On each and every aggregate, we define a (List) list of uncommited events. Please payAttention that UncommitedEvent is not a domain Event! It doesn't even has OccuredOn property. Now, when ChangeEmail method is called on Customer Aggregate, we don't raise anything. We just save the event to uncommitedEvents collection which exists on our aggregate. Like this:

    public virtual void ChangeEmail(string email)
    {
        if(this.Email != email)
        {
            this.Email = email;
            UncommitedEvents.Add(new CustomerChangedEmail(email));
        }
    }
    

So, when does the actual domain event is raised??? This responsibility is delegated to persistence layer. In ICustomerRepository we have access to ISystemClock, because we can easily inject it inside repository. Inside Save() method of ICustomerRepository we should extract all uncommitedEvents from Aggregate and for each of them create a DomainEvent. Then we set up OccuredOn property on newly created Domain Event. Then, IN ONE TRANSACTION we save the aggregate and publish ALL domain events. This way we'll be sure that all events will will raised in transnational boundary with aggregate persistence.
What I don't like about this approach? I don't want to create 2 different types for the same event, i.e for CustomerChangedEmail behavior I should have CustomerChangedEmailUncommited type and CustomerChangedEmailDomainEvent. It would be nice to have just one type. Please share your experience regarding to this topic!

Klotz answered 16/4, 2017 at 10:56 Comment(10)
That's all very great, but I am afraid it is not a question, and so it will most probably be closed as off-topic.Capillary
The question is if it safe to publish Domain Event before persisting the Aggregate or not?Klotz
I have seen that even Vaughn Vernon (the author of famous Implementing Domain Driven Design book) uses the first approach, so I'm wondering. Maybe I'm missing smth and the first approach is perfectly safe and fine?Klotz
The first approach assumes the aggregate is modified in a transaction. So, the event is raised immediately, but it's handled only after transaction has commited. It's technically possible to configure your event handlers to fire only after transaction commits successfully. As to OccuredOn property - this can be set by some kind of centralized event factory (or even directly by domain publisher), so you don't have to duplicate your code in every aggregate.Croce
@Mike Wojtyna, can you please provide an example of how to configure event handlers to fire only after transaction commits successfully? Nice catch though, I didn't think about it. Regarding to "OccuredOn", how can it be set by centralized event factory if the event itself was already created inside aggregate and it is immutable?Klotz
@Klotz I can give you an example for Spring Framework. Take a look here: spring.io/blog/2015/02/11/… (search for "Transaction bound events"). Basially you annotate your event handlers with @TransactionalEventListener. I'm sure it's very similar in C# world. You can search for Jimmy Bogard's articles - he's the guy who writes a lot about DDD in C#/.net context. Oh, and regarding factory - you can use factory inside your domain entities (factory can be a domain concept).Croce
@Mike Wojtyna, could you elaborate a bit on centralized event factory? As far as I'm concerned the app layer flow should be like this: 1) Get Aggregate from repository. 2) Mutate its state with the help of domain services. (Double Dispatch pattern, the one I shared when describing the first approach). 3) Persist the aggregate. If double dispatch is not enough and aggregate needs a service for most of its behavior then probably it's modeled incorrectly.Klotz
@Klotz IMO there's nothing wrong in using a domain factory in your aggregate root. It's definitely a better solution than using a ISystemClock explicitly, which is clearly not part of your domain. Therefore, you can create a factory which adheres to your domain language. This way, your event factory is explicitly part of your domain and hides all the infrastructure details (like system clock) from your domain entities. You inject your domain factory when creating your domain entities. BTW, Vaughn Vernon included a chapter about factory methods in his book - goo.gl/u9cwdO.Croce
@Mike Wojtyna, You inject your domain factory when creating your domain entities. Sorry, I can't understand how I can inject a factory inside aggregate...Klotz
@Klotz Simply pass a factory in a constructor when rehydrating aggregate entity from db. If you are not creating aggregates manually in your repository (e.g. when using the same class for db mapping/domain entity), then you can use @Configurable annotation and AspectJ in Spring to inject your factory into aggregate entities. I'm sure there's a similar solution for C#.Croce
G
5

I am not a proponent of either of the two techniques you present :)

Nowadays I favour returning an event or response object from the domain:

public CustomerChangedEmail ChangeEmail(string email)
{
    if(this.Email.Equals(email))
    {
        throw new DomainException("Cannot change e-mail since it is the same.");
    }

    return On(new CustomerChangedEmail { EMail = email});
}

public CustomerChangedEmail On(CustomerChangedEmail customerChangedEmail)
{
    // guard against a null instance
    this.EMail = customerChangedEmail.EMail;

    return customerChangedEmail;
}

In this way I don't need to keep track of my uncommitted events and I don't rely on a global infrastructure class such as DomainEvents. The application layer controls transactions and persistence in the same way it would without ES.

As for coordinating the publishing/saving: usually another layer of indirection helps. I must mention that I regard ES events as different from system events. System events being those between bounded contexts. A messaging infrastructure would rely on system events as these would usually convey more information than a domain event.

Usually when coordinating things such as sending of e-mails one would make use of a process manager or some other entity to carry state. You could carry this on your Customer with some DateEMailChangedSent and if null then sending is required.

The steps are:

  • Begin Transaction
  • Get event stream
  • Make call to change e-mail on customer, adding even to event stream
  • record e-mail sending required (DateEMailChangedSent back to null)
  • Save event stream (1)
  • Send a SendEMailChangedCommand message (2)
  • Commit transaction (3)

There are a couple of ways to do that message sending part that may include it in the same transaction (no 2PC) but let's ignore that for now.

Assuming that previously we had sent an e-mail our DateEMailChangedSent has a value before we start we may run into the following exceptions:

(1) If we cannot save the event stream then here's no problem since the exception will rollback the transaction and the processing would occur again.
(2) If we cannot send the message due to some messaging failure then there's no problem since the rollback will set everything back to before we started. (3) Well, we've sent our message so an exception on commit may seem like an issue but remember that we could not set our DateEMailChangedSent back to null to indicate that we require a new e-mail to be sent.

The message handler for the SendEMailChangedCommand would check the DateEMailChangedSent and if not null it would simply return, acknowledging the message and it disappears. However, if it is null then it would send the mail either interacting with the e-mail gateway directly ot making use of some infrastructure service endpoint through messaging (I'd prefer that).

Well, that's my take on it anyway :)

Gadroon answered 17/4, 2017 at 7:59 Comment(13)
The only problem with this solution is that each time you invoke any domain method on your domain object, then you need to also remember about publishing appropriate events. That's the main reason behind the DomainEvents-like solutions. The problem is somewhat similar to persistence problem (whether to persist entities on domain methods or outside using repositories). However, in case of repositories the case is simple - we all agree we should use repositories outside of entities. Personally, I'm more inclined to emit events from application layer, as I believe it's not part of domain layer.Croce
Indeed Mike Wotjyna, we need to remember all kinds of things when programming :) --- I prefer having these wrapped up as application layer tasks that coordinate domain objects/services myself. Then they can be used at any integration point. The transaction scope and connections are still handled by the application layer though.Gadroon
@Eben Roux, thanks a lot for your answer! The approach you suggested is interesting, but I see 2 tiny problems. The first one is the return type. I want my methods to adhere Tell, Don't Ask principle. If my method changes aggregate's state it should be void. Feels like changing state + returning events violates SRP. And the second one is the mixture of business logic which resides inside aggregate's method and the logic of 'collecting' domain events could bring difficulties to understand the code. BTW, the logic of 'collecting' domain events should belong to infrastructure, what do you think?Klotz
@Eben Roux, what about 'OccuredOn' initialization? If aggregate mutates the state and returns domain events, Application Layer has no way to reset 'OccuredOn' property on them.Klotz
@DmitriBodiu, Regarding your first point: your domain is emitting the events either way. You aren't really asking for events. You are telling your domain to perform some action and the events happen to be a result. Having the domain return events seems a bit easier to test and makes it more apparent as to what is going on. Collecting events is indeed infrastructure. That is why I have the following in my Shuttle.Recall ES mechanism: var stream = eventStore.Get(id); stream.Add(aggregate.DoSomething()); eventStore.Save(stream);...Gadroon
@DmitriBodiu, Regarding you second point: The OccurredOn could be the EventDate and would be more on an infrastructure level; unless you really need it on your domain event in which case you can pass in the clock. It wouldn't necessarily be required for all and if it is it should be infrastructure, e.g.: see [EventEnvelope]github.com/Shuttle/Shuttle.Recall/blob/master/Shuttle.Recall/…). I'm not advocating you use Recall but just some ideas. I recently added an EventEnvelope to carry additional data external to the business requirements. HTHGadroon
@Eben Roux, I'm thinking about the case when an event should be collected during constructor call?Klotz
@Dmitri, that seems a bit of an odd thing to do (events in constructor). Usually events are in reaction to a command method... can you give an example of when one would do so? It should still be possible to get away with using a real method call though?Gadroon
@Elben, here is an example of Forum aggregate from 'Implementing Domain Driven Design' book. github.com/VaughnVernon/IDDD_Samples_NET/blob/master/…Klotz
@Elben, We could probably rewrite it to a factory method which calls constructor inside and collects the event afterwards, but then we have to change the signature to return Forum and List<DomainEvent> together...But that is ugly(Klotz
Interesting... I don't like this internal collecting of events and inheriting base classes is another thing I don't particularly fancy :) --- in this example it makes just as much sense to have the verbose constructor be a StartForum method that returns a ForumStarted event. I don't like the IDomainEvent marker interface either :) --- I like Vaughn, though! Very nice guy. That being said, I like to keep persistence out of the domain and domain events are, for me anyway, merely persistence artifacts as they relay state. Working on my own e-book BTW :DGadroon
@Elben, hmmm..I have User aggregate. public User(TenantId tenantId, UserName userName, Password password, IPasswordService passwordService) { if (passwordService == null) throw new ArgumentNullException(nameof(passwordService)); var hashedPassword = passwordService.GenerateHashedPassword(password); TenantId = tenantId; UserName = userName; HashedPassword = hashedPassword; Apply(new UserCreated(tenantId, userName, hashedPassword)); } How can it be rewritten to your approach?Klotz
I have something similar here: public Registered Register(string username, byte[] passwordHash, string registeredBy). I would not pass a service into the aggregate since the hashing should be done outside on the application layer. Design choice I guess but I don't like passing services, etc., into an AR. BTW, that code is still very much in development (18 Apr 2017) so don't read too much into it :)Gadroon
L
1

I have seen 2 different approaches of raising Domain Events.

Historically, there have been two different approaches. Evans didn't include domain events when describing the tactical patterns of domain-driven-design; they came later.

In one approach, Domain Events act as a coordination mechanism within a transaction. Udi Dahan wrote a number of posts describing this pattern, coming to the conclusion:

Please be aware that the above code will be run on the same thread within the same transaction as the regular domain work so you should avoid performing any blocking activities, like using SMTP or web services.

, the common alternative, is actually a very different animal, in so far as the events are written to the book of record, rather than merely being used to coordinate activities in the write model.

The second problem with the current implementation is that every event should be immutable. So the question is how can I initialize its "OccuredOn" property? Only inside aggregate! Its logical, right! It forces me to pass ISystemClock (system time abstraction) to each and every method on aggregate!

Of course - see John Carmack's plan files

If you don't consider time an input value, think about it until you do - it is an important concept

In practice, there are actually two important time concepts to consider. If time is part of your domain model, then it's an input.

If time is just meta data that you are trying to preserve, then the aggregate doesn't necessarily need to know about it -- you can attach the meta data to the event elsewhere. One answer, for example, would be to use an instance of a factory to create the events, with the factory itself responsible for attaching the meta data (including the time).

How can it be achieved? An example of a code sample would help me a lot.

The most straight forward example is to pass the factory as an argument to the method.

public virtual void ChangeEmail(string email, EventFactory factory)
{
    if(this.Email != email)
    {
        this.Email = email;
        UncommitedEvents.Add(factory.createCustomerChangedEmail(email));
    }
}

And the flow in the application layer looks something like

  1. Create metadata from request
  2. Create the factory from the metadata
  3. Pass the factory as an argument.

Then, IN ONE TRANSACTION we save the aggregate and publish ALL domain events. This way we'll be sure that all events will will raised in transnational boundary with aggregate persistence.

As a rule, most people are trying to avoid two phase commit where possible.

Consequently, publish isn't usually part of the transaction, but held separately. See Greg Young's talk on Polyglot Data. The primary flow is that subscribers pull events from the book of record. In that design, the push model is a latency optimization.

Libriform answered 16/4, 2017 at 14:9 Comment(5)
thanks a lot for your answer! Can you please elaborate a bit on If time is just meta data that you are trying to preserve, then the aggregate doesn't necessarily need to know about it -- you can attach the meta data to the event elsewhere. One answer, for example, would be to use an instance of a factory to create the events, with the factory itself responsible for attaching the meta data (including the time) How can it be achieved? An example of a code sample would help me a lot.Klotz
Thanks a lot for your answer! Regarding to If time is just meta data that you are trying to preserve, then the aggregate doesn't necessarily need to know about it -- you can attach the meta data to the event elsewhere. How can I collect all events from aggregate in correct order? If I have a Collection<DomainEvent> on Aggregate and I want to raise (add to collection) an event from inner part of the aggregate? Imagine Order which has OrderItems. How can I raise an event from OrderItem class? I know this is totally different question...Klotz
but what I did is I added a collectioin<domainEvent> to each entity which resides inside aggregate. And then on Aggregate I have GetUncommitedEvents() method which concatinates the aggregate's events with all child's enitites events. And here I need OccuredOn on each event to order them accordingly. Here is the root of the problem)Klotz
Is storing events in the domain model to publish later actually called 'event sourcing'? Isn't event sourcing the pattern for storing events of state changes for the purpose of building objects based on those changes, and not just the process of storing events? Am I getting it wrong or are you using it loosely to explain something here? Just learning this stuff and its confusing me a lotUstkamenogorsk
"Is storing events in the domain model to publish later actually called 'event sourcing'?" No, it's more of a messaging thing -- look for "outbox pattern".Libriform
S
1

I tend to implement domain events using the second approach.

Instead of manually retrieving and then dispatching all events in the aggregate roots repository I have a simple DomainEventDispatcher(application layer) class which listens to various persistence events in the application. When an entity is added, updated or deleted it determines whether it is an AggregateRoot. If so, it calls releaseEvents() which returns a collection of domain events that then get dispatched using the application EventBus.

I don't know why you are focusing so much on the occurredOn property.

The domain layer is only concerned with the meat of the domain events such as aggregate root IDs, entity IDs and value object data.

At the application layer you can have an event envelope which can wrap any serialized domain event while giving it some meta data such as a unique ID (UUID/GUID), what aggregate root it came from, the time it occurred etc. This can be persisted to a database.

This meta data is useful in the application layer because you might be publishing these events to other applications using a message bus/event stream over HTTP and it allows each event to be uniquely identifiable.

Again, this meta data about the event generally makes no sense in the domain layer, only the application layer. The domain layer does not care or have any use for event IDs or the time they occurred but other applications which consume these events do. That's why this data is attached at the application layer.

Syndesmosis answered 19/4, 2017 at 14:35 Comment(0)
S
1

The way I would solve the sending of email problem is by decoupling the publishing of the event and the handling of the event through a messaging queue. This way you close the transaction after sending the event to the queue, and the sending of email, or other effects that cannot or should not be part of the original DB transaction, will happen shortly after, in a different transaction. The simplest way to do that, of course, is to have an event handler that publishes domain events onto the queue.

If you want to be extra sure that the domain events will be published to the queue when the transaction is committed, you can save the events to an OUTBOX table that will be committed with the transaction, and then have a thread read from the table and publish to the event queue

Setzer answered 22/5, 2019 at 20:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.