Another thing you donāt have to invent yourself when building a Spring application is in-process eventing.Ā There is a lot of literature out there about the architectural advantages of eventing and messaging using a broker such as JMS or AMQP.Ā Spring provides lightweight templates for publishing and subscribing with these brokers, but thatās not what I am going to talk about.Ā The architectural principals that you benefit from with using a message broker are decoupling (you never talk directly to the listener of your message) and asynchronous processing (you donāt block waiting for a response).Ā Without a message broker, itās still possible to benefit from these principals by simply plugging in to the Spring Containerās own publish and subscribe mechanism.
Why would you want to do container messaging versus broker messaging?Ā First of all itās never an either/or situation, you can leverage both, itās simply a matter of what you can do locally versus distributed and itās simple enough to mix and match.Ā Ā Letās take a common example use case for this: a user signs up for your web application.Ā This triggers all sorts of system activity, from creating an entry in a User table, to starting a trial or subscription, to sending a welcome email, or perhaps initiating an email confirmation process, or if you are āsocialā application pulling data from the connected social networks.Ā Imagine a UserController
kicking this off by accepting a registration request.Ā A ācoupledā approach would have this controller invoke each and every one of these processes in sequence, in a blocking or non-blocking fashion OR it could publish an event that a new user was created, and services dedicated to each function would pick up the message and act appropriately.Ā The difference between the two approaches is that the former is a God Object anti-pattern, and the latter is a nicely decoupled Pub/Sub pattern with a sprinkle of by Responsibility-driven design.Ā An added bonus is the ability to test all of these functions in isolation – the pub/sub mechanism simply binds them together at runtime.
Letās look a bit more in detail about how this works.Ā First you need to decide what events you want to publish.Ā In the example above, it makes sense to have a UserCreationEvent
with an attribute of the User, or the UniqueId of the user whatever you feel more comfortable with.Ā This object would extend Springās ApplicationEvent
which itself has an event timestamp attribute.Ā So the class might look like this:
public class UserCreationEvent extends ApplicationEvent { Ā Ā Ā Ā private User user; Ā Ā Ā public UserCreationEvent (Object source, User user) { Ā Ā Ā Ā Ā Ā Ā super(source); Ā Ā Ā Ā Ā Ā Ā this.user = user; Ā Ā Ā } Ā Ā Ā public User getUser() { Ā Ā Ā Ā Ā Ā Ā return user; Ā Ā Ā } }
To publish this event, in our case from the UserController
or better yet a UserService
that takes care of creating the user in the system, we can just wire up the ApplicationEventPublisher
:
@Autowired Ā private ApplicationEventPublisher publisher;
Then when weāve finished creating the user in the system, we can call
UserCreationEvent userCreationEvent = new UserCreationEvent (this, user); publisher.publishEvent(userCreationEvent );
Once the event is fired, the Spring container will invoke a callback on all registered listeners of that type of event.Ā So letās see how that part is set up.
A listener is simply a bean that implements ApplicationListener<YourEventType>
, so in our case ApplicationListener<UserCreationEvent >
.Ā Our listeners will all implement their own callback logic for the onApplicationEvent(UserCreationEventĀ event)
method.Ā If you are using Springās component scan capability this will be registered automatically.
@Component public class UserWelcomeEmailListener implements ApplicationListener<UserCreationEvent > { @Autowired private EmailService emailService; @Override public void onApplicationEvent(UserActionEvent event) { emailService.sendWelcomeEmail(event.getUser()); } }
Itās important to note a few things about the default event multicasting mechanism:
- It is all invoked on the same thread
- One listener can terminate broadcasting by throwing an exception, and thus no guaranteed delivery contract
You can address these limitations by making sure all long-running operations are executed on another thread (using @Async
for example) or plugging in your own (or supplied) implementation of the Multicaster by registering a bean that implements the interface with the name āapplicationEventMulticasterā.Ā One easy way is to extend SimpleApplicationEventMulticaster
and supply a threaded Executor
.
To avoid one listener spoiling the party, you can either wrap all logic within each listener in a try/catch/finally block or in your custom Multicaster
, wrap the calls to the handlers themselves in try/catch/finally blocks.
Another thing to be aware of as you think about this – if a failure in any of the processing that occurs after the event is published causes any inconsistency in the data or state of the User in our case, then you canāt do all this.Ā Each operation has to be able to deal with itās own failures, and recovery process.Ā In other words, donāt do anything that needs to be part of the whole ācreate userā transaction into this type of pattern, in that case you donāt have a decoupled process so itās better to not pretend you do.
As I mentioned before, there is no reason you canāt also leverage a distributed message broker in concert with Application events.Ā Simply have an application event listener publish the event to the broker (as sort of a relay).Ā In this way you get the benefit of both local and distributed messaging.Ā For example, imagine your billing service is another system that requires messages through RabbitMQ.Ā Create an ApplicationListener
, and post the appropriate message.Ā Youāve achieved decoupling within and between applications, and leveraged two types of messaging technologies for great justice.
@Component public class SubscriptionSignupListener implements ApplicationListener<UserCreationEvent > { @Autowired RabbitTemplate rabbitTemplate; @Override public void onApplicationEvent(UserActionEvent event) { SubscriptionMessage newSubMsg = new SubscriptionMessage(new Date(), SubscriptionMessage.TRIAL_START, event.getUser()); rabbitTemplate.convertAndSend(MqConstants.NEW_SUB_KEY, newSubMsg); } }
So what about using something like Reactor instead?Ā Again, itās not an either/or situation.Ā As Jon Brisbin notes in this article, Reactor is designed for “high throughput when performing reasonably small chunks of stateless, asynchronous processingā .Ā If your application or service has such processing, then by all means use that instead or in addition to ApplicationEvents
.Ā Reactor in fact includes a few Excecutor
implementations you can leverage so you can have your ApplicationEvent
cake and eat it too!