2020-06-12

Declarative Spring Application Events

Spring allows us to publish ApplicationEvent instances using the managed ApplicationEventPublisher instance.

Each such ApplicationEvent can be consumed by the interested parties by annotating methods with @EventListener or @TransactionalEventListener.

Let’s consider the following example: during development and testing, each time we get an UserEvent event, we want to log it.

public class UserEvent extends ApplicationEvent {
    // ...
}

public class UserLoginEvent extends UserEvent {
    // ...
}

public class UserRegisterEvent extends UserEvent {
    // ...
}

public class UserLoginService {
    private ApplicationEventPublisher applicationEventPublisher;
    //...

    public UserLoginEvent login(String loginToken) {
        UserLoginEvent userEvent; //  =  ...
        applicationEventPublisher.publish(userEvent);
        return userEvent;
    }
}

@Component
@Profile("dev")
public class UserEventLogger {
    @EventListener
    public void onUserEvent(UserEvent userEvent) {
        LOGGER.info("User event {}", userEvent);
    }
}

From a consumer perspective, it seems very loose-coupling.

But how about the producer?

Every class that is interested in publishing application events needs an ApplicationEventPublisher instance. And even though Spring provides one out-of-the-box, it’s still another dependency to add to all the classes that need it. Maybe it’s not so demanding, but how about having more classes publishing events? Is it worth doing the same work everywhere?

public class UserRegistrationService {
    private ApplicationEventPublisher applicationEventPublisher;

    public UserRegisterEvent register(UserRegistrationData data) {
        // ...
        UserRegisterEvent userEvent; // = ...
        applicationEventPublisher.publish(userEvent);
        return userEvent;
    }
}

How about having a corresponding declarative mechanism for publishing application events the same way we have for consuming them?

For this, we need a way to identify those methods interested in publishing events. And what could be more useful to get from a method signature than the return value?

So we need to identify some (public) non-void methods that can return … anything. Not so suitable for interfaces, right?

Here’s where AOP might help us.

Let’s keep it exactly the same as Spring already allows methods to declare themselves event consumers; let EventPublisher be the equivalent annotation. In this case:

public class UserLoginService {
    // ...

    @EventPublisher
    public UserLoginEvent login(String loginToken) {
        UserLoginEvent userEvent; // = ...
        return userEvent;
    }
}

public class UserRegistrationService {
    // ...

    @EventPublisher
    public UserRegisterEvent register(UserRegistrationData data) {
        // ...
        UserRegisterEvent userEvent; // = ...
        return userEvent;
    }
}

Now we still need to inject the ApplicationEventPublisher, but it’s going to happen in a single place: that is an aspect that is executed after each @EventPublisher that returns a subclass of ApplicationEvent.

public class EventPublisherAspect {
    private ApplicationEventPublisher applicationEventPublisher;

    @AfterReturning("@annotation(EventPublisher) && execution(ApplicationEvent+ *(..)")
    public void publishApplicationEvent(ApplicationEvent event) {
        applicationEventPublisher.publish(event);
    }
}