Event-Driven Side Effects

How ADR-001 established the event-driven pattern for cross-cutting concerns, including the mutable event pattern for synchronous results.

architecture spring-boot events adr

The Problem

As Vivolar grew, cross-cutting concerns started creeping in: audit logging, notifications, analytics, usage tracking. The natural tendency was to inject these dependencies directly into the services that triggered them. But this led to services with 6, 7, 8 constructor dependencies — a clear sign of mixed responsibilities.

ADR-001: Event-Driven Architecture

Architecture Decision Record 001 established a clear rule: new cross-cutting concerns must be implemented as event listeners, not as direct service-to-service calls.

Transaction Phases

Spring provides two types of event listeners, and choosing the right one matters:

@EventListener (default, in-transaction) — used when the publisher needs the result synchronously. The listener runs in the same transaction as the publisher, so if the listener fails, the whole transaction rolls back.

@TransactionalEventListener(phase = AFTER_COMMIT) — used when the side effect must not fire if the transaction rolls back. Perfect for audit logs, notifications, and usage tracking — you don’t want to log “item added” if the insert was rolled back.

The Mutable Event Pattern

One interesting challenge: what happens when a publisher needs a result from a synchronous listener? Spring events are fire-and-forget by default.

The solution is the mutable event pattern. Instead of using a Java record (immutable), we use a mutable class with a result field:

public class ProductResolvedEvent {
    private final String productName;
    private final Long householdId;
    private CategoryMatchResult result; // set by listener

    // constructor, getters, setter for result
}

The flow:

  1. Service creates the event and publishes it
  2. Synchronous listener processes it and calls event.setResult(...)
  3. Publisher reads event.getResult() after publishEvent() returns

This keeps the publisher decoupled from the listener while still getting the data it needs.

Event Catalog

ADR-001 includes an event catalog — a registry of all planned and implemented events in the system. Before adding a new event, developers check the catalog to see if an existing event already covers the use case. This prevents event proliferation and keeps the system coherent.

Results

After implementing ADR-001:

Lessons