New Eventuate platform APIs that support Async API: part 1 - events

I recently developed a plugin for SpringWolf that configures an Eventuate service to expose an /springwolf/docs endpoint that returns an Async API document describing the messages that the service sends and receives. While implementing the plugin, however, I discovered that the existing Eventuate APIs were not well suited to exposing Async API-style metadata. For example, the messages sent by the service — and the channels to which it sent them — were not explicitly defined; instead, these details were hidden within the code

This realization prompted the creation of a new set of Eventuate APIs for sending and receiving messages. The old APIs still work but only limited metadata can be generated from them. In this article, I’ll describe the new APIs for publishing and subscribing to events. A later article will describe the new APIs for commands and sagas. Let’s first look at the changes to event publishing.

Event publishing

Let’s first look at the old way of publishing events. After that I describe the new approach.

Original event publishing API

Previously, domain logic simply used the DomainEventPublisher to publish events:

public class CustomerService {

  private DomainEventPublisher domainEventPublisher;

  @Transactional
  public Customer createCustomer(String name, Money creditLimit) {
    ...
    domainEventPublisher.publish(Customer.class, customer.getId(), customerWithEvents.events);
    return customer;
  }

As a result, the channels and event types were not explicitly defined.

New event publishing API

The new approach consists of defining one or more DomainEventPublisherForAggregate @Beans. A DomainEventPublisherForAggregate publishes events for a specific aggregate. Although defining DomainEventPublisherForAggregates involves more code, it explicitly defines the channels and events. The Eventuate plugin for Spring Wolf uses the DomainEventPublisherForAggregate @Beans to generate an Async API document describing the published events and their channels.

Here’s the definition of a DomainEventPublisherForAggregate for the Customer aggregate:

public interface CustomerEventPublisher extends DomainEventPublisherForAggregate<Customer, Long, CustomerEvent> {
}

It’s injected into the CustomerService:

public class CustomerService {

  private final CustomerEventPublisher customerEventPublisher;

  public void reserveCredit(long orderId, long customerId, Money orderTotal) {

      CustomerCreditReservedEvent customerCreditReservedEvent =
              new CustomerCreditReservedEvent(customerId, orderId);

      customerEventPublisher.publish(customer, customerCreditReservedEvent);


Finally, here is the @Bean implementation of the CustomerEventPublisher:

@Component
public class CustomerEventPublisherImpl extends AbstractDomainEventPublisherForAggregateImpl<Customer, Long, CustomerEvent> implements CustomerEventPublisher {

    public CustomerEventPublisherImpl(DomainEventPublisher domainEventPublisher) {
        super(Customer.class, Customer::getId, domainEventPublisher, CustomerEvent.class);
    }
}

Let’s now look at the new event handling API.

Subscribing to events

Let’s first look at the old way of subscribing to events. After that I describe the new approach.

Original event handling API

Previously, event handlers were configured by defining a class that constructed DomainEventHandlers:

public class OrderEventConsumer {

  public DomainEventHandlers domainEventHandlers() {
    return DomainEventHandlersBuilder
            .forAggregateType("io.eventuate.examples.tram.ordersandcustomers.orders.domain.Order")
            .onEvent(OrderCreatedEvent.class, this::handleOrderCreatedEvent)
            ...
            .build();
  }

  public void handleOrderCreatedEvent(DomainEventEnvelope<OrderCreatedEvent> domainEventEnvelope) {
    ...

The DomainEventHandlers was then passed to a DomainEventDispatcherFactory:

public class CustomerConfiguration {

  @Bean
  public OrderEventConsumer orderEventConsumer(CustomerService customerService) {
    return new OrderEventConsumer(customerService);
  }

  @Bean
  public DomainEventDispatcher domainEventDispatcher(OrderEventConsumer orderEventConsumer, DomainEventDispatcherFactory domainEventDispatcherFactory) {
    return domainEventDispatcherFactory.make("orderServiceEvents", orderEventConsumer.domainEventHandlers());
  }

The Eventuate plugin for Spring Wolf can use the DomainEventDispatcher @Beans to generate an Async API document describing the subscribed to events and their channels. However, there’s no obvious way to customize the Async API document by, for example, specifying additional documentation.

New event handling API

The new approach is to define beans that have methods annotated with @EventuateDomainEventHandler. The annotation specifies the event handler’s subscriber ID and the channel. The event type is obtained from the method parameter.

@Component
public class OrderEventConsumer {
  private Logger logger = LoggerFactory.getLogger(getClass());

  private CustomerService customerService;

  public OrderEventConsumer(CustomerService customerService) {
    this.customerService = customerService;
  }

  @EventuateDomainEventHandler(subscriberId = "OrderEventConsumer", channel = "io.eventuate.examples.tram.ordersandcustomers.orders.domain.Order")
  public void handleOrderCreatedEvent(DomainEventEnvelope<OrderCreatedEvent> domainEventEnvelope) {
    OrderCreatedEvent event = domainEventEnvelope.getEvent();
    customerService.reserveCredit(Long.parseLong(domainEventEnvelope.getAggregateId()),
            event.orderDetails().customerId(), event.orderDetails().orderTotal());
  }

The Eventuate plugin for Spring Wolf can search the application context for @Beans that have methods annotated with @EventuateDomainEventHandler. Moreover, in the future, it will support additional annotations that allow you to customize the generated Async API document.

Show me the code

  • The Eventuate Spring Wolf repository contains some example Async API docs
  • The development branch of the eventuate-tram-examples-customers-and-orders repository contains an example application that uses the new APIs.

What’s next

A later post will describe the new APIs for sagas and command handlers.


Stay in touch
Copyright © 2025 Eventuate, Inc • All rights reserved.