Is it safe to publish a domain event before saving the aggregate?

In many different projects, I saw two different approaches to creating domain events.

  • Raise a domain event directly from the collection. For example, imagine that you have a Customer aggregate and there 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 see two problems with this approach. The first is that the event occurs regardless of whether the collection is stored or not. Imagine if you want to send an email to a client after successful registration. The "CustomerChangedEmail" event will be raised and some IEmailSender will send an email even if the unit has not been saved. The second problem with the current implementation is that each event must be immutable. So the question is, how can I initialize the "OccuredOn" property? Only inside the unit! This is logical, right! This forces me to pass an ISystemClock (system abstraction of time) for each method together! Whaaat ??? Don't you find this design fragile and bulky? Here's what we 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));
        }
    }
    
  • - , Event Sourcing. () . , , UncommitedEvent ! OccuredOn. , ChangeEmail Customer, . uncomminedEvents, . :

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

, ? . ICustomerRepository ISystemClock, . Save() ICustomerRepository UncommendedEvents Aggregate DomainEvent. OccuredOn . ON ON TRANSACTION . , , .
? 2 , CustomerChangedEmail CustomerChangedEmailUncommited CustomerChangedEmailDomainEvent. . , !

+7
4

:)

:

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;
}

, , , DomainEvents. , ES.

/: . , ES . - , . , , .

, , , - . Customer DateEMailChangedSent, null, .

:

  • , .
  • (DateEMailChangedSent null)
  • (1)
  • SendEMailChangedCommand (2)
  • Commit transaction (3)

, , ( 2PC), .

, , DateEMailChangedSent , , :

(1) , , , .
(2) - - , , , . (3) , , , , DateEMailChangedSent null, , .

SendEMailChangedCommand DateEMailChangedSent, null, , . , , , ( ).

, :)

+3

, .

, , DomainEventDispatcher ( ), . , , , AggregateRoot. , releaseEvents(), , EventBus.

, occurredOn.

, , .

, , , (UUID/GUID), , .. .

, , / HTTP, .

, , . - , , . .

+1

, . , , , . , - , .

, , OUTBOX, ,

0

.

. ; .

. Udi Dahan , , :

, , , - , , SMTP -.

, , , , .

, . , , "OccuredOn"? ! , ! ISystemClock ( ) !

- .

, , -

. , .

- , , - . , factory , factory ( ).

? .

- factory .

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

  • factory
  • factory .

ON ON TRANSACTION . , , .

As a rule, most people try to avoid two-stage fixation, where possible.

Therefore, the publication is usually not part of the transaction, but stored separately. See Greg Young on Polyglot Data . The main stream is that subscribers pull events from the record book. In this design, the push model is optimization of delays.

-1
source

Source: https://habr.com/ru/post/1674915/


All Articles