Slide 1

Slide 1 text

A Deep Dive into
 Spring Application Events Oliver Drotbohm ƀ odrotbohm@vmware.com / odrotbohm

Slide 2

Slide 2 text

fundamentals Programming model transactions Consistency & error scenarios architecture Components & relationships 2

Slide 3

Slide 3 text

fundamentals Programming model 3

Slide 4

Slide 4 text

ApplicationEventPublisher ApplicationListener @EventListener @TransactionalEventListener Programming model 4

Slide 5

Slide 5 text

Application events fundamentals DEMO 5

Slide 6

Slide 6 text

Application Events
 with Spring Data 6

Slide 7

Slide 7 text

Application events with Spring (Data) • Powerful mechanism to publish events in Spring applications • Application event – either a general object or extending ApplicationEvent • ApplicationEventPublisher – injectable to manually invoke event publication • Spring Data event support • Spring Data’s focus: aggregates and repositories • Domain-Driven Design aggregates produce application events • AbstractAggregateRoot – base class to easily capture events and get them published on CrudRepository.save(…) invocations. • No dependency to infrastructure APIs 7

Slide 8

Slide 8 text

8 class Order { private List events; @DomainEvents Collection events() { … } @AfterDomainEventPublication void reset() { … } } Ag#$eg&'( a+c-./0at23
 ev256 7n36an92: Met<=> t? e@A=s( 'Cem Wip2 (HeIt3 &J($
 puL0McN'i=I

Slide 9

Slide 9 text

Spring Data provided base class 9 class Order extends AbstractAggregateRoot { Order complete() { registerEvent(OrderCompleted.of(this)); return this; } } Met<=> t? OePMs'($ ev256.
 Sup2O +lN3: taT2: cNOe =U (xWo:-r(.

Slide 10

Slide 10 text

10 @Component class OrderManagement { private final OrderRepository orders; @Transactional void completeOrder(Order order) {
 orders.save(order.complete()); } } No r2[($en92: t? SW$in#
 in[$&s'$/c'u$2 AP]s

Slide 11

Slide 11 text

Intermediate summary Events for Bounded Context interaction Spring’s application events are a very light-weight way to implement those domain events. Spring Data helps to easily expose them from aggregate roots. The overall pattern allows loosely coupled interaction between Bounded Contexts so that the system can be extended and evolved easily. Externalize events if needed Depending on the integration mechanism that’s been selected we can now write separate components to translate those JVM internal events into the technology of choice (JMS, AMQP, Kafka) to notify third- party systems. 11

Slide 12

Slide 12 text

transactions Consistency & error scenarios 14

Slide 13

Slide 13 text

15 @EventListener @EventListener … @TransactionalEventListener @TransactionalEventListener … @Transactional ƻ
 Event ƻ
 Event Commit 1 2 3 4 5 6 7 8 Consistency boundary Spring bean Legend

Slide 14

Slide 14 text

Application events in a Spring application 1. We enter a transactional method Business code is executed and might trigger state changes on aggregates. 2. That transactional method produces application events In case the business code produces application events, standard events are published directly. For each transactional event listener registered a transaction synchronization is registered, so that the event will eventually be published on transaction completion (by default on transaction commit). 3. Event listeners are triggered By default, event listeners are synchronously invoked, which means they participate in the currently running transactions. This allows listeners to abort the overall transaction and ensure strong consistency. Alternatively, listeners can be executed asynchronously using @Async. They then have to take care of their transactional semantics themselves and errors will not break the original transaction. 16

Slide 15

Slide 15 text

Application events in a Spring application 4. Service execution proceeds once event delivery is completed Once all standard event listeners have been invoked, the business logic is executed further. More events can be published, further state changes can be created. 5. The transaction finishes Once the transactional method is done, the transaction is completed. Usually all pending changes (created by the main business code or the synchronous event listeners) are written to the database. In case inconsistencies or connection problems, the transaction rolls back. 6. Transactional event listeners are triggered Listeners annotated with @TransactionalEventListener are triggered when the transaction commits, which means they can rely on the business operation the event has been triggered from having succeeded. This allows the listeners to read committed data. Listeners can be invoked asynchronously using @Async in case the functionality to be invoked might be long-running (e.g. sending an email). 17

Slide 16

Slide 16 text

Error scenarios 18

Slide 17

Slide 17 text

What if the
 service fails? 19 !

Slide 18

Slide 18 text

20 @EventListener @EventListener … @TransactionalEventListener @TransactionalEventListener … @Transactional ƻ
 Event ƻ
 Event Commit Consistency boundary Spring bean Legend "#

Slide 19

Slide 19 text

21 @EventListener @EventListener … @TransactionalEventListener @TransactionalEventListener … @Transactional ƻ
 Event ƻ
 Event Commit Consistency boundary Spring bean Legend Rollback! $ "#

Slide 20

Slide 20 text

What if an event
 listener fails? 22 !

Slide 21

Slide 21 text

23 @EventListener @EventListener … @TransactionalEventListener @TransactionalEventListener … @Transactional ƻ
 Event ƻ
 Event Commit Consistency boundary Spring bean Legend "#

Slide 22

Slide 22 text

24 @EventListener @EventListener … @TransactionalEventListener @TransactionalEventListener … @Transactional ƻ
 Event ƻ
 Event Commit Consistency boundary Spring bean Legend "# Rollback! $

Slide 23

Slide 23 text

What if a transactional
 event listener fails? 25 !

Slide 24

Slide 24 text

26 @EventListener @EventListener … @TransactionalEventListener @TransactionalEventListener … @Transactional ƻ
 Event ƻ
 Event Commit Consistency boundary Spring bean Legend "#

Slide 25

Slide 25 text

26 @EventListener @EventListener … @TransactionalEventListener @TransactionalEventListener … @Transactional ƻ
 Event ƻ
 Event Commit Consistency boundary Spring bean Legend 
 Publication lost! % "#

Slide 26

Slide 26 text

Transactional application events DEMO 27

Slide 27

Slide 27 text

Application events – Error Scenarios The service fails Only standard event listeners up until the failure will have been executed. Assuming the already triggered event listeners also execute transactional logic, the local transaction is rolled back and the system is still in a strongly consistent state. Transactional event listeners are not invoked in the first place. A synchronous event listener fails In case a normal event listener fails the entire transaction will roll back. This enables strong consistency between the event producer and the listeners registered but also bears the risk of supporting functionality interfering with the primary one, causing the latter to fail for less important reasons. The tradeoff here could be to move to a transactional event listener and embrace eventual consistency. 28

Slide 28

Slide 28 text

Application events – Error Scenarios A transactional event listener fails In case a transactional event lister fails or the application crashes while transactional event listeners are executed, the event is lost and functionality might not have been invoked. Other transactional event listeners will still be triggered. BONUS: An asynchronous event listener fails The event is lost but the primary functionality can still succeed as the event is handled in a separate thread. Retry mechanisms can (should?) be deployed in case some form of recovery is needed. 29

Slide 29

Slide 29 text

Event Publication Registry 30

Slide 30

Slide 30 text

31 @TransactionalEventListener @TransactionalEventListener … @TransactionalEventListener … @TransactionalEventListener …

Slide 31

Slide 31 text

31 @TransactionalEventListener @TransactionalEventListener … @TransactionalEventListener … @TransactionalEventListener …

Slide 32

Slide 32 text

31 @TransactionalEventListener @TransactionalEventListener … ƻ
 Event @TransactionalEventListener … @TransactionalEventListener … Transaction Commit

Slide 33

Slide 33 text

31 @TransactionalEventListener @TransactionalEventListener … ƻ
 Event @TransactionalEventListener … @TransactionalEventListener … Transaction Commit

Slide 34

Slide 34 text

31 @TransactionalEventListener @TransactionalEventListener … ƻ
 Event @TransactionalEventListener … @TransactionalEventListener … Transaction Commit

Slide 35

Slide 35 text

31 @TransactionalEventListener @TransactionalEventListener … ƻ
 Event @TransactionalEventListener … @TransactionalEventListener … Transaction Commit

Slide 36

Slide 36 text

31 @TransactionalEventListener @TransactionalEventListener … ƻ
 Event @TransactionalEventListener … @TransactionalEventListener … Transaction Commit

Slide 37

Slide 37 text

31 @TransactionalEventListener @TransactionalEventListener … ƻ
 Event @TransactionalEventListener … @TransactionalEventListener … Transaction Commit

Slide 38

Slide 38 text

31 @TransactionalEventListener @TransactionalEventListener … ƻ
 Event @TransactionalEventListener … @TransactionalEventListener … Transaction Commit

Slide 39

Slide 39 text

31 @TransactionalEventListener @TransactionalEventListener … ƻ
 Event @TransactionalEventListener … @TransactionalEventListener … Transaction Commit

Slide 40

Slide 40 text

31 @TransactionalEventListener @TransactionalEventListener … ƻ
 Event @TransactionalEventListener … @TransactionalEventListener … Transaction Commit

Slide 41

Slide 41 text

Event Publication Registry 1. Write application event publication log for transactional listeners On application event publication a log entry is written for every event and transactional event listener interested in it. That way, the transaction remembers which events have to be properly handled and in case listener invocations fail or the application crashes events can be re-published. 2. Transaction listeners are decorated to register successful completion Transactional event listeners are decorated with an interceptor that marks the log entry for the listener invocation on successful listener completion. When all listeners were handled, the log only contains publication logs for the ones that failed. 3. Incomplete publications can be retried Either periodically or during application restarts. 32

Slide 42

Slide 42 text

Event publication registry DEMO 33

Slide 43

Slide 43 text

architecture Components & relationships 36

Slide 44

Slide 44 text

Orders 37 Inventory Component Dependency Type dependency

Slide 45

Slide 45 text

38 Orders Inventory Component Dependency Type dependency 
 Event Event publication

Slide 46

Slide 46 text

39

Slide 47

Slide 47 text

Imperative style 1. Spring bean invocations across Bounded Contexts The OrderManagement actively invokes other Spring Beans to trigger peripheral functionality. By that, it becomes a spot of gravity that is likely to become more complex over time and pollute the transaction (i.e. it's doing too much). There's no explicit representation of the business incident "completion of an order" except the method name. 2. Testing focussed on explicit interaction Unit testing usually works with mocks and requires complex setup of expected behavior and verifications. For integration tests, dependency beans need to be available, too, which makes it hard to test the module in isolation. 3. Post-commit functionality hard to integrate As the entire method is running inside a transaction, it becomes harder to integrate logic that is supposed to run after the transaction has committed, for example sending a confirmation email. Especially interactions with external resources that can take a while should be held outside the main transaction. 40

Slide 48

Slide 48 text

Event based implementation 1. Less knowledge in the order module and explicit integration point Introducing the OrderCompleted event allows the order module to get rid of the knowledge about which peripheral functionality to integrate and thus allows for easier extension. Whether that downstream functionality is executed synchronously as part of the main transaction, asynchronously or after the transaction is now determined by the event listener. 2. Testing shifts to verification of events published The reduced number of dependencies of a component significantly eases testing as these dependencies don't have to be mocked and prepared (in the case of a unit test) nor do they have to be available as Spring bean in an integration test. In the former case, Modulith's PublishedEvents API allows to write readable assertions on the events that were produced during the test execution. The latter enables Modulith's support for @ModuleTest to integration test a single module. 41

Slide 49

Slide 49 text

Resources 44 Sample code Project website on GitHub Moduliths Spring Boot extension to support building modular monoliths, incl. support for events, testing etc., Project website on GitHub Spring Domain Events Prototype implementation of an event publication registry, likely to be integrated into Moduliths, Project website on GitHub Refactoring to a System of Systems Video on YouTube

Slide 50

Slide 50 text

Thank you! 45 Oliver Drotbohm ƀ odrotbohm@vmware.com / odrotbohm