Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Modeling Domain Events

DZone 's Guide to

Modeling Domain Events

Let's see how to model domain events.

· Integration Zone ·
Free Resource

Image title

Modeling domain events

Domain Events in a Complex Domain

In one of my previous posts, we discussed Domain and Integration events. In this post, we will look at modeling domain events in order to simplify the application.

You may also enjoy:  The Role of the Domain Model With CQRS/Event Sourcing

There are certain triggers that can lead to domain events.

  • A command from a user/system
  • The time like the End of the month or a duration
  • Event causality — One event leads to other events

For a complex domain, these triggers can lead to a large number of domain events. For example, for an online food delivery service , there can be events related to:

  • User Commands — AddFoodItem, RemoveFoodItem, UpdateQuantity, CancelOrder
  • System Commands — PaymentCompleted, OrderShipped, OrderDelivered
  • Duration Based — There could be hard limits within which an order needs to be prepared, picked and delivered to the customer. 
  • Event Causality Based — RestrauntOutOfService, ItemNotAvailable, DeliveryDelayed

As we can see, these are just some of the cases. Over time, this can lead to a lot of supporting classes aka Event Handlers. Additionally, the implementation can become overly complex based on figuring out the type of event at the application services layer. The above approach can also lead to a very large number of events per aggregate.

Simplifying the Domain Model

In order to mitigate the above issues, we had to simplify the Event domain model as well as and move away from having a large number of events per aggregate. If we look at the intent of having the events, we notice that these were related to transitions, which happen over the lifecycle of fulfilling an order. Since this ties in very well with the real world, we decided to model the events with the same perspective.

This led to modeling the DomainEvent as:

public abstract class DomainEvent implements Serializable{
    private static final long serialVersionUID = 1L;

    private @Getter String aggregateId;

    private @Getter String parentId;

    private @Getter Transitions transitions;


    protected DomainEvent(String aggregateId, String parentId) {
        setAggregateId(aggregateId);
        setParentId(parentId);
    }


    /**
     * @param aggregateId the aggregateId to set
     */
    private void setAggregateId(String aggregateId) {
        Objects.requireNonNull(aggregateId, "Aggregate Id can not be null");
        this.aggregateId = aggregateId;
    }

    /**
     * @param parentId the parentId to set
     */
    private void setParentId(String parentId) {
        this.parentId = parentId;
    }


    public void setTransitions(Transitions transitions) {
        this.transitions = transitions;
    }

 }

Transitions is a set of state changes for an aggregate.

@Getter
@Setter
public class Transitions implements Serializable {

    private List<Transitions> transitions;

}

The Transition class encapsulates the start and end states along with the quantity.

@Getter
@Setter
public class Transition implements Serializable {

    private State startState;
    private State endState;
    private Double quantity;
    private DateTime transitionDateTime;

}

The State is an enum with all the possible states.

Identifying Terminal Transitions

One of the key things was to identify terminal transitions for an event. For example, for an order with 2 food items, one of which is unavailable, the terminal events would be

  • 1 item has been delivered to the customer
  • 1 item has been canceled from the system, because of non-availability. 

We can use the Transitions in the event to create a Digraph. The vertex for the graph is the State and the edge was the quantity. Terminal transitions were the states which had an out-degree of 0.

Resolving Event Handlers in Application Services

In order to resolve which EventHandler to invoke, we go through the terminal states and invoked applicable EventHandlers. A factory was used to resolve the handler based on the start and end state of the transition.

public class OrderShippedService extends OrderService{
    private EventHandlerFactory eventHandlerFactory;
    public void orderShipped(OrderEvent event){

       List<Transition> terminalTransitions = getTerminalTransitions(event);
       for(Transition transition: transitions){
         EventHandler handler = eventHandlerFactory.getEventHandlerForTransition(transition.getStartState(),
                                transition.getEndState());
         handler.handle(event);
       } 
    }
 //Other methods
}

What Goes in the Event Handler

An EventHandler would implement a particular use case. It would invoke appropriate application services to perform some action.

For example, when a order is shipped, a notification is sent to the customer. Additionally, the tracking service needs to start tracking the order. 

public class OrderShippedEventHandler extends EventHandler<OrderEvent> {
   private NotificationService notificationService;
   private TrackingService trackingService; 

  public void handle(OrderEvent event){
     notificationService.notify(event);
     trackingService.track(event);

  }

}

This approach greatly simplified the Domain Event model and event handlers. It also simplified resolving Event Handlers at the application services layer.

Further Reading

Domain-Driven Design

Building Microservices Through Event-Driven Architecture, Part 2: Domain Objects and Business Rules

Topics:
integration ,domain model

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}