Skip to content

Example of handling domain events requires improvement. #43392

Open
@voroninp

Description

@voroninp

Type of issue

Missing information

Description

The section The deferred approach to raise and dispatch events is missing some vital details.

First, the comment in this code fragment is misleading:

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

This makes a single transaction including side effects from the domain event handlers that are using the same DbContext with Scope lifetime.

This statement is not correct because by default each call to SaveChanges is wrapped in its own transaction. The first handler to call SaveChanges will save both its own changes and the changes that lead to the event. Subsequent handlers will persist changes in separate transactions.
For this to work the Application layer must open and commit transaction; Only in this case all calls to SaveChanges will belong to it.

Second, handling domain events immediately after committing the transaction without an outbox should be a deliberate decision when risks are known and accepted, so it's worth mentioning at least.

Also, choosing for eventual consistency between aggregates effectively turns them into actors and somewhat blures the border between domain and integration events. IMO, the main distinction between domain and integration events is how much contract coupling is still fine. Domain events within single bounded context are safer regarding exposing more details. Sometimes single bounded context can be served by multiple physical nodes, so in-memory mediator won't work anyway, and proper message bus will be required.

Page URL

https://learn.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/domain-events-design-implementation#the-deferred-approach-to-raise-and-dispatch-events

Content source URL

https://github.com/dotnet/docs/blob/main/docs/architecture/microservices/microservice-ddd-cqrs-patterns/domain-events-design-implementation.md

Document Version Independent Id

e41af96d-f015-a8e0-4449-7a508d7bccc8

Article author

@jamesmontemagno

Metadata

  • ID: d432d5bb-24a7-3bec-1e17-910c8129226e
  • Service: dotnet-architecture
  • Sub-service: microservices

Related Issues

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions