Publishing domain events from aggregate roots

June 9, 2017

Starting with Spring Data Ingalls release publishing domain events by aggregate roots becomes easier. Instead of leveraging Spring’s ApplicationEventPublisher you can use @DomainEvents annotation on a method of your aggregate root. Let’s look at an example.

class BankTransfer {
    Collection<Object> domainEvents() {
            // … return events you want to get published here
    void callbackMethod() {
           // … potentially clean up domain events list

The method annotated with @DomainEvents will be called if one of the save() methods of the Spring Data repository is called. The method can return a single event instance or a collection of events and cannot have any arguments. After all events have been published a method annotated with @AfterDomainEventsPublication is called.

Spring Data Commons provides a convenient base class (AbstractAggregateRoot) to help to register domain events and is using the publication mechanism implied by @DomainEvents and @AfterDomainEventsPublication

public class AbstractAggregateRoot {

	 * All domain events currently captured by the aggregate.
	@Getter(onMethod = @__(@DomainEvents)) //
	private transient final List<Object> domainEvents = new ArrayList<Object>();

	 * Registers the given event object for publication on a call to a Spring Data repository's save method.
	 * @param event must not be {@literal null}.
	 * @return
	protected <T> T registerEvent(T event) {
		Assert.notNull(event, "Domain event must not be null!");
		return event;

	 * Clears all domain events currently held. Usually invoked by the infrastructure in place in Spring Data
	 * repositories.
	public void clearDomainEvents() {

The @Getter(onMethod = @__(@DomainEvents)) Lombok construct makes sure that the @DomainEvents annotation is put on the generated getter method. Let’s modify the example to extend from AbstractAggregateRoot base class.

public class BankTransfer extends AbstractAggregateRoot {


    public BankTransfer complete() {
        id = UUID.randomUUID().toString();
        registerEvent(new BankTransferCompletedEvent(id));
        return this;

In the example the BankTransfer aggregate root registers a BankTransferCompletedEvent when its complete method is called. The client calls this method in a transactional context saving the BankTransfer also via Spring Data Repository abstraction, which triggers the publication of BankTransferCompletedEvent event.

public class BankTransferService {

    public String completeTransfer(BankTransfer bankTransfer) {


On the event listener side by using TransactionalEventListener the event handler can be bound to a phase of the transaction which published the event. The typical use case is to handle the event when the transaction completed successfully, which is the default setting for @TransactionalEventListener. It can be further customized via the phase attribute.

public class BankTransferProcessor {

    public void handleBankTransferCompletedEvent(BankTransferCompletedEvent event) {
        BankTransfer bankTransfer = repository.findById(event.getBankTransferId());
        bankTransfer =;"Starting to process bank transfer {}.", bankTransfer);

        try {
        } catch (InterruptedException e) {
            throw new RuntimeException(e);

        if (new Random().nextBoolean()) {
        } else {
        };"Finished processing bank transfer {}.", bankTransfer);


In the example the @Async is used to return immediately the client without waiting for the processing of the bank transfer.

$ echo '{
            "from":"DE89 3704 0044 0532 0130 00",
            "to":"HU42 1177 3016 1111 1018 0000 0000",
      }' | \
      http post :8080/bank-transfers
HTTP/1.1 201
Content-Type: application/json;charset=UTF-8
Date: Thu, 04 May 2017 13:54:12 GMT      
    "bankTransferId": "783b9b13-8424-4004-b59f-eef400d8a52c"

Retrieving the details of the bank transfer:

$ http :8080/bank-transfers/783b9b13-8424-4004-b59f-eef400d8a52c

HTTP/1.1 200
Content-Type: application/json;charset=UTF-8
Date: Thu, 04 May 2017 13:55:33 GMT
Transfer-Encoding: chunked

    "bankTransferId": "783b9b13-8424-4004-b59f-eef400d8a52c",
    "from": "DE89 3704 0044 0532 0130 00",
    "to": "HU42 1177 3016 1111 1018 0000 0000",  
    "amount": 100.25,
    "status": "COMPLETED"