Contact Us

If you still have questions or prefer to get help directly from an agent, please submit a request.
We’ll get back to you as soon as possible.

Please fill out the contact form below and we will reply as soon as possible.

  • Contact Us
  • Home
  • System Architecture

Event-Sourced Domain Model

Written by Oleksandr Sydorenko

Updated at May 5th, 2025

Contact Us

If you still have questions or prefer to get help directly from an agent, please submit a request.
We’ll get back to you as soon as possible.

Please fill out the contact form below and we will reply as soon as possible.

  • System Architecture
+ More

The original domain model maintains a state representation of its aggregates and emits select domain events. The event-sourced domain model uses domain events exclusively for modeling the aggregates’ lifecycles. All changes to an aggregate’s state have to be expressed as domain events.

Each operation on an event-sourced aggregate follows this script:

• Load the aggregate’s domain events.

• Reconstitute a state representation—project the events into a state representation that can be used to make business decisions.

• Execute the aggregate’s command to execute the business logic, and consequently, produce new domain events.

• Commit the new domain events to the event store.

Going back to the example of the Ticket aggregate from Chapter 6, let’s see how it would be implemented as an event-sourced aggregate.

The application service follows the script described earlier: it loads the relevant tick‐ et’s events, rehydrates the aggregate instance, calls the relevant command, and persists changes back to the database:

01 public class TicketAPI

02 {

03 private ITicketsRepository _ticketsRepository; 04 ...

05

06 public void RequestEscalation(TicketId id, EscalationReason reason) 07 {

08

var events = _ticketsRepository.LoadEvents(id);

09

var ticket = new Ticket(events);

10

var originalVersion = ticket.Version;

11

var cmd = new RequestEscalation(reason);

12

ticket.Execute(cmd);

13

_ticketsRepository.CommitChanges(ticket, originalVersion);

14

}

15

16 ...

17 }

The Ticket aggregate’s rehydration logic in the constructor (lines 27 through 31) instantiates an instance of the state projector class, TicketState, and sequentially calls its AppendEvent method for each of the ticket’s events:

18 public class Ticket

19 {

20 ...

21 private List<DomainEvent> _domainEvents = new List<DomainEvent>();

22 private TicketState _state;

23 ...

24

25 public Ticket(IEnumerable<IDomainEvents> events)

26 {

27 _state = new TicketState();

28 foreach (var e in events)

29 {

30 AppendEvent(e);

31 }

32 }

The AppendEvent passes the incoming events to the TicketState projection logic, thus generating the in-memory representation of the ticket’s current state:

33 private void AppendEvent(IDomainEvent @event)

34 {

35 _domainEvents.Append(@event);

36 // Dynamically call the correct overload of the "Apply" method.

37 ((dynamic)state).Apply((dynamic)@event);

38 }

Contrary to the implementation we saw in the previous chapter, the event-sourced aggregate’s RequestEscalation method doesn’t explicitly set the IsEscalated flag to true. Instead, it instantiates the appropriate event and passes it to the AppendEvent method (lines 43 and 44):

39 public void Execute(RequestEscalation cmd)

40 {

41 if (!_state.IsEscalated && _state.RemainingTimePercentage <= 0)

42 {

43 var escalatedEvent = new TicketEscalated(_id, cmd.Reason);

44 AppendEvent(escalatedEvent);

45 }

46 }

47

48 ...

49 }

All events added to the aggregate’s events collection are passed to the state projection logic in the TicketState class, where the relevant fields’ values are mutated according to the events’ data:

50 public class TicketState

51 {

52 public TicketId Id { get; private set; }

53 public int Version { get; private set; }

54 public bool IsEscalated { get; private set; }

55 ...

56 public void Apply(TicketInitialized @event)

57 {

58 Id = @event.Id;

59 Version = 0;

60 IsEscalated = false;

61 ....

62 }

63

64 public void Apply(TicketEscalated @event)

65 {

66 IsEscalated = true;

67 Version += 1;

68 }

69

70 ...

71 }

Now let’s look at some of the advantages of leveraging event sourcing when imple‐ menting complex business logic.

Why “Event-Sourced Domain Model”? I feel obliged to explain why I use the term event-sourced domain model rather than just event sourcing. Using events to represent state transitions—the event sourcing pattern—is possible with or without the domain model’s building blocks. Therefore, I prefer the longer term to explicitly state that we are using event sourcing to represent changes in the lifecycles of the domain model’s aggregates.

Was this article helpful?

Yes
No
Give feedback about this article

Related Articles

  • Discovering Domain Knowledge
  • Business Problems
  • Knowledge Discovery
  • Communication
  • What Is a Ubiquitous Language?

info@smartphonekey.com

  • Home
  • How It Works
  • Features
  • Residents and Tenants
  • Property Managers
  • Airbnb Hosts
  • Products
  • Blog
  • Guide for Usage and Installation
  • Our Team
  • Contact Us
  • Privacy Policy
  • Terms of Service
  • Facebook
  • Instagram
  • LinkedIn
© 2025, Smartphonekey.com Powered by Shopify
Expand