Event Flow is a lightweight Java framework for building event-driven applications. It provides the structural backbone for publishing, routing, and processing events — so you can focus on business logic instead of wiring infrastructure.
- Features
- Architecture
- Modules
- Installation
- Quick Start
- Core Components
- Usage Examples
- Configuration
- Interaction Diagrams
- Flexible Routing — Event channels with configurable transports
- Multiple Transports — LocalQueue (in-JVM) and Apache Kafka out of the box, with extension points for custom transports
- Annotation-Based — Event handling via
@EventListener - Interface-Based — Event handling via
EventSubscriberinterface - POJO/Record Events — Support for plain Java objects without
Eventinterface - Idempotency — Event deduplication based on UID
- Transactional Publishing — Send events after transaction commit
- Structured Logging — Decorators for publisher and dispatcher with machine-parseable JSON output
- Retry Mechanism — Exponential backoff with configurable parameters
- Extensible Serialization — JSON and MessagePack with support for custom formats
┌─────────────────────────────────────────────────────────────────────────┐
│ Event Flow Architecture │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌─────────────────────────────────────────┐ │
│ │ Service │────────▶│ Event Channels │ │
│ │ (Publisher) │ │ ┌─────────────┐ ┌─────────────────┐ │ │
│ └──────────────┘ │ │ Internal │ │ External │ │ │
│ │ │ (in-JVM) │ │ (Kafka) │ │ │
│ │ └──────┬──────┘ └────────┬────────┘ │ │
│ │ │ │ │ │
│ └─────────┼───────────────── ┼────────────┘ │
│ │ │ │
│ ┌─────────▼───────────────── ▼────────────┐ │
│ │ Outgoing Event Transports │ │
│ │ ┌─────────────┐ ┌─────────────────┐ │ │
│ │ │ LocalQueue │ │ Kafka │ │ │
│ │ │ Queue │ │ Producer │ │ │
│ │ └─────────────┘ └─────────────────┘ │ │
│ └─────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
│
│ External Communication
│ (Network / Message Broker)
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ External Event Flow (Kafka) │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────┐ │
│ │ Incoming Event Transports │ │
│ │ ┌─────────────┐ ┌─────────────────┐ │ │
│ │ │ LocalQueue │ │ Kafka │ │ │
│ │ │ Queue │ │ Consumer │ │ │
│ │ └─────────────┘ └─────────────────┘ │ │
│ └─────────┬─────────────────┬─────────────┘ │
│ │ │ │
│ ┌─────────▼─────────────────▼────────────┐ │
│ │ Event Dispatcher │ │
│ │ │ │
│ └─────────┬─────────────────┬────────────┘ │
│ │ │ │
│ ┌─────────▼─────────────────▼────────────┐ │
│ │ Handler Registry │ │
│ │ ┌─────────────┐ ┌─────────────────┐ │ │
│ │ │ Annotation │ │ Interface │ │ │
│ │ │ Based │ │ Based │ │ │
│ │ └─────────────┘ └─────────────────┘ │ │
│ └─────────┬─────────────────┬────────────┘ │
│ │ │ │
│ ┌─────────▼─────────────────▼────────────┐ │
│ │ Event Handlers │ │
│ │ (@EventListener / EventSubscriber) │ │
│ └────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────────────────────────────┐
│ Microservice A (Event Producer) │
│ │
│ ┌──────────┐ ┌───────────┐ ┌──────────┐ ┌──────────────────────┐ │
│ │ Service │──▶│ Publisher │──▶│ Channel │──▶│ KafkaOutTransport │ │
│ └──────────┘ └───────────┘ └──────────┘ └──────────┬───────────┘ │
└────────────────────────────────────────────────────────────┼───────────────┘
│ produce()
▼
═══════════════════════════════════════════════════
Apache Kafka — Topic: "events"
═══════════════════════════════════════════════════
┃ ┃
consume() ┃ consume() ┃
│ ┃ │ ┃
▼ ┃ ▼ ┃
┌─────────────────────────────────────┐ ┌─────────────────────────────────────┐
│ Microservice B (Consumer) │ │ Microservice C (Consumer) │
│ │ │ │
│ ┌──────────────────┐ │ │ ┌──────────────────┐ │
│ │ KafkaInTransport │ │ │ │ KafkaInTransport │ │
│ └──────────────────┘ │ │ └──────────────────┘ │
│ │ │ │ │ │
│ ┌────────▼─────────┐ │ │ ┌────────▼─────────┐ │
│ │ EventDispatcher │ │ │ │ EventDispatcher │ │
│ └────────┬─────────┘ │ │ └────────┬─────────┘ │
│ │ dispatch() │ │ │ dispatch() │
│ │ │ │ │ │
│ ┌────────▼─────────┐ │ │ ┌────────▼────────┐ │
│ │ HandlerRegistry │ │ │ │ HandlerRegistry │ │
│ └────────┬─────────┘ │ │ └────────┬────────┘ │
│ │ getHandlers() │ │ │ getHandlers() │
│ ▼ │ │ ▼ │
│ ┌──────────────────┐ │ │ ┌──────────────────┐ │
│ │ Handler 1 │ │ │ │ Handler 2 │ │
│ │ Handler 2 │ │ │ │ Handler 3 │ │
│ └──────────────────┘ │ │ └──────────────────┘ │
└─────────────────────────────────────┘ └─────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ Single Application │
│ │
│ ┌──────────┐ ┌───────────┐ ┌──────────┐ ┌──────────────────────┐ │
│ │ Service │──▶│ Publisher │──▶│ Channel │──▶│LocalQueueOutTransport│ │
│ └──────────┘ └───────────┘ └──────────┘ └──────────┬───────────┘ │
│ │ offer(event) │
│ ▼ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ BlockingDeque<Event> │ │
│ │ (shared queue) │ │
│ └─┬─────────────────────────────────────────────────────┘ │
│ │ take(event) │
│ ▼ │
│ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────────┐ │
│ │ LocalQueueInTrans│──▶│ EventDispatcher │──▶│ EventHandlerRegistry │ │
│ └──────────────────┘ └────────┬─────────┘ └──────────┬───────────┘ │
│ │ dispatch() │ getHandlers() │
│ │ ▼ │
│ │ ┌──────────────────────┐ │
│ └─────────────▶│ Handlers │ │
│ async execute │ (virtual threads) │ │
│ └──────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
| Module | Description | Documentation |
|---|---|---|
| event-flow-core | Core module — framework-agnostic, pure Java 21+ | README |
| event-flow-spring | Spring Boot auto-configuration with YAML | README |
Add the dependency to your pom.xml:
<dependency>
<groupId>io.github.vovten</groupId>
<artifactId>event-flow</artifactId>
<version>1.1.0</version>
</dependency>For Spring Boot integration:
<dependency>
<groupId>io.github.vovten</groupId>
<artifactId>event-flow-spring</artifactId>
<version>1.1.0</version>
</dependency>implementation 'io.github.vovten:event-flow:1.1.0'
// For Spring Boot:
implementation 'io.github.vovten:event-flow-spring:1.1.0'- Java 21+
- Apache Kafka 3.6.0+ (optional, for external events)
Events can be defined in two ways:
a) Use the @Event annotation (recommended) — cleaner POJO/record, channels from annotation:
@Event(channels = InternalEventChannel.class)
public record OrderShipped(String orderId, String customerId) {}b) Implement the Event interface — full control over type and channels:
public record OrderCreatedEvent(String orderId, String customerId) implements Event {
@Override
public Class<?> type() {
return OrderCreatedEvent.class;
}
}Both approaches work with
publish(),prepare(), and handler registration. Recommended:@Eventannotation for cleaner code; useimplements Eventonly when you need full control.
// Create local queue transport for in-JVM event delivery
var transports = new LocalQueueTransportsBuilder()
.queueSize(1000)
.build();
// Create a channel for internal (in-application) events
// For external events (Kafka), use ExternalEventChannel with Kafka transports
EventChannel internalChannel = new InternalEventChannel(
List.of(transports.publisher())
);
// Create publisher (add externalChannel here for external events)
EventPublisher eventPublisher = EventPublisherBuilder.create(internalChannel)
.retryable(3, Duration.ofMillis(100), 2.0)
.buildAndLog();
// Create handler registry
EventHandlerRegistry handlerRegistry = EventHandlerRegistryBuilder.create()
.withAnnotationListeners()
.withInterfaceListeners()
.buildAndLog();
// Create dispatcher
EventDispatcher eventDispatcher = EventDispatcherBuilder.create()
.executor(Executors.newVirtualThreadPerTaskExecutor())
.handlerRegistry(handlerRegistry)
.transports(List.of(transports.dispatcher()))
.concurrencyLimit(100)
.buildAndLog();
// Start the dispatcher
eventDispatcher.start(eventDispatcher::dispatch);Handle the event directly:
public class OrderEventHandler {
@EventListener
public void handleOrderCreated(OrderCreatedEvent event) {
System.out.println("Order created: " + event.orderId());
}
}
// Register the handler
handlerRegistry.register(new OrderEventHandler());Or receive the full Envelope with metadata (eventId, processId, occurredAt, etc.):
public class OrderEnvelopeHandler {
@EventListener(OrderShipped.class)
public void handleOrderShipped(Envelope<OrderShipped> envelope) {
OrderShipped event = envelope.payload();
System.out.println("Order " + event.orderId()
+ " processed with id " + envelope.eventId());
}
}
// Register the envelope handler
handlerRegistry.register(new OrderEnvelopeHandler());When the handler parameter is
Envelope<T>, the@EventListenerannotation must specify the payload type explicitly (e.g.,@EventListener(OrderShipped.class)). This is especially useful for POJO/record events annotated with@Event— they are automatically wrapped in anEnvelopeupon publishing.
public class OrderService {
private final EventPublisher eventPublisher;
public OrderService(EventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
}
public void createOrder(String customerId) {
// Business logic...
eventPublisher.publish(new OrderCreatedEvent("order-123", customerId));
}
}Base interface for all events. Defines event type and publication channels.
public interface Event {
Class<?> type();
default List<Class<? extends EventChannel>> channels() {
return List.of(InternalEventChannel.class);
}
default String asJson() {
return EventUtils.toJson(this);
}
}TraceableEvent — extends Event with tracing fields: eventId (UUID), processId (correlation), occurredAt (timestamp).
Wrapper for domain events that adds technical metadata. Automatically captures:
eventId(UUID) — unique event identifierprocessId(UUID) — correlation ID (e.g., saga ID)occurredAt(Instant) — event timestampmetadata(Map) — custom key-value pairspayload— the actual domain object
The envelope implements Event interface, so it passes through existing transport infrastructure.
Creating Envelopes:
// Auto-generated metadata
Envelope<OrderCreatedEvent> envelope = Envelope.of(new OrderCreatedEvent("123"));
// With custom processId (correlation)
UUID processId = UUID.fromString("...");
Envelope<OrderCreatedEvent> envelope = Envelope.of(new OrderCreatedEvent("123"), processId);
// With explicit channels
Envelope<OrderCreatedEvent> envelope = Envelope.of(
new OrderCreatedEvent("123"),
List.of(ExternalEventChannel.class)
);Channels from @Event Annotation:
POJO/record classes can use the @Event annotation to specify default channels:
@Event(channels = ExternalEventChannel.class)
public record OrderCreatedEvent(String orderId) {}A channel defines event delivery routes through transports.
public interface EventChannel {
String name();
List<OutTransport> transports();
CompletableFuture<SendResults> send(Event event);
}Built-in Channels:
InternalEventChannel— for internal in-application deliveryExternalEventChannel— for external delivery to other applications/microservices
Publishes events to configured channels.
Creating via Builder:
EventPublisher publisher = EventPublisherBuilder.create(internalChannel, externalChannel)
.retryable(3, Duration.ofMillis(100), 2.0)
.build();EventPublisherBuilder — fluent builder for creating publishers with flexible configuration:
| Method | Description |
|---|---|
create(...) |
Create builder with event channels (required) |
retryable() |
Enable retry with default settings (3 attempts, 100ms initial delay, 2.0 multiplier) |
retryable(max, delay, multiplier) |
Enable retry with custom settings |
withDecorator(fn) |
Add custom decorator to the publisher chain |
build() |
Build the publisher |
buildAndLog() |
Build the publisher and log the configuration |
Delivers events from transports to handlers.
public interface EventDispatcher {
void dispatch(Event event);
void register(Object listener);
boolean isRegistered(Object listener);
void start(Consumer<Event> handler);
void stop();
}EventDispatcherBuilder — fluent builder for creating dispatchers:
| Method | Description |
|---|---|
executor(...) |
Configure ExecutorService (required) |
handlerRegistry(...) |
Handler registry (required) |
transports(...) |
List of incoming transports |
concurrencyLimit(n) |
Concurrency limiting via Semaphore |
idempotent() |
Enable idempotency (deduplication by UID) |
idempotent(maxSize, ttl) |
Enable idempotency with custom settings |
withDecorator(fn) |
Add custom decorator |
build() |
Build the dispatcher |
buildAndLog() |
Build the dispatcher and log the configuration |
Registry for discovering and managing event handlers.
public interface EventHandlerRegistry {
List<EventHandler> getHandlers(Event event);
void register(Object listener);
void unregister(Object listener);
boolean isRegistered(Object listener);
void merge(EventHandlerRegistry registry);
int handlerCount();
String name();
}Built-in implementations:
EventListenerRegistry— discovery via@EventListenerannotationEventSubscriberRegistry— discovery viaEventSubscriberinterfaceCompositeEventHandlerRegistry— combines multiple registries
EventHandlerRegistryBuilder — fluent builder for creating registries:
| Method | Description |
|---|---|
withAnnotationListeners() |
Enable discovery via @EventListener annotation |
withInterfaceListeners() |
Enable discovery via EventSubscriber interface |
withCustomRegistry(registry) |
Add a custom registry |
withDecorator(fn) |
Add a decorator |
build() |
Build the registry |
buildAndLog() |
Build the registry and log the configuration |
Transports for event delivery.
Incoming Transports (InTransport):
LocalQueueInTransport— receive from local queueKafkaInTransport— receive from Kafka topics
Outgoing Transports (OutTransport):
LocalQueueOutTransport— send to local queueKafkaOutTransport— send to Kafka topicBroadcastKafkaOutTransport— send to all Kafka topic partitions
Utility for creating paired incoming/outgoing transports based on a local queue:
var transports = new LocalQueueTransportsBuilder()
.queueSize(1000)
.build();
EventChannel channel = new InternalEventChannel(
List.of(transports.publisher())
);
EventDispatcher dispatcher = EventDispatcherBuilder.create()
.executor(Executors.newVirtualThreadPerTaskExecutor())
.handlerRegistry(handlerRegistry)
.transports(List.of(transports.dispatcher()))
.build();
dispatcher.start(dispatcher::dispatch);EventSerializer — serialization interface with magic byte prefix:
0x01— JSON0x02— MessagePack
EventSerializerFactory — factory with automatic format detection:
// Register a custom serializer
EventSerializerFactory.getInstance().register(new MyCustomSerializer());EventTypeRegistry — security whitelist for allowed event classes:
// Allow a package (default: io.github.vovten.eventflow.*)
EventTypeRegistry.allowPackage("com.example.events");
// Allow a specific class
EventTypeRegistry.allowClass(MyEvent.class);Event Flow provides multiple ways to publish events:
| Method | Envelope | Channels | Metadata | Message Size |
|---|---|---|---|---|
implements Event |
❌ No | From event | Minimal | ⚡ Smallest |
prepare().publish() |
✅ Auto | Custom | Custom | Medium |
Note: When POJO/record or
prepare()is used, anEnvelopeis automatically created wrapping the payload with additional metadata (eventId, processId, occurredAt). This increases message size but adds correlation/tracing capabilities.
The most convenient approach for most use cases:
// Define event with default channels via annotation
@Event(channels = {InternalEventChannel.class, ExternalEventChannel.class})
public record OrderCreatedEvent(long orderId) {}
// Publish with custom metadata (channels from annotation are used automatically)
eventPublisher.prepare(new OrderCreatedEvent(1))
.withProcessId(processId)
.publish();This gives you:
- Channels from
@Eventannotation (no need to specify in code) - Auto-generated eventId and occurredAt (timestamps)
- Custom metadata via builder (processId, etc.)
- Envelope for correlation/tracing
Implement Event interface for minimum overhead — no Envelope wrapper, smallest message size:
public record OrderCreatedEvent(String orderId, String email) implements Event {
@Override
public Class<?> type() {
return OrderCreatedEvent.class;
}
@Override
public List<Class<? extends EventChannel>> channels() {
return List.of(InternalEventChannel.class, ExternalEventChannel.class);
}
}
eventPublisher.publish(new OrderCreatedEvent("order-123", "user@example.com"));Use cases: High-throughput scenarios, microservice-to-microservice communication, Kafka topics.
Publish any Java object directly — Envelope is created automatically:
// Simple POJO with @Event annotation
@Event(channels = InternalEventChannel.class)
public record OrderCreated(String orderId, String email) {}
eventPublisher.publish(new OrderCreated("order-123", "user@example.com"));Envelope with auto-generated metadata:
eventId— random UUIDprocessId— nulloccurredAt— current timestampmetadata— empty- Channels —
InternalEventChannel(default)
Use the builder for custom metadata and channels — same Envelope is created internally:
eventPublisher.prepare(new OrderCreated("order-123", "user@example.com"))
.withMetadata("key1", "data1")
.withMetadata("key2", "data2")
.withChannels(InternalEventChannel.class, ExternalEventChannel.class)
.withProcessId(UUID.randomUUID())
.withOccurredAt(Instant.now())
.publish();Note: Channels specified via
withChannels()have priority over channels defined in@Eventannotation on the payload class.
Available builder methods:
withMetadata(key, value)— add single metadata entrywithMetadata(Map)— add multiple metadata entrieswithChannel(channel)— set single channel (convenience alias)withChannels(c1)— set one channelwithChannels(c1, c2)— set two channelswithChannels(c1, c2, c3)— set three channelswithChannels(List)— set arbitrary number of channelswithProcessId(UUID)— correlation ID (e.g., saga ID)withOccurredAt(Instant)— custom event timestamppublish()— send the event
Specify default channels on the POJO/record class:
@Event(channels = ExternalEventChannel.class)
public record OrderShipped(String orderId, Instant shippedAt) {}
@Event(channels = {InternalEventChannel.class, ExternalEventChannel.class})
public record OrderDelivered(String orderId, Instant deliveredAt) {}
eventPublisher.publish(new OrderShipped("order-123", Instant.now()));| Approach | Best For | Envelope | Message Size |
|---|---|---|---|
implements Event |
High throughput, Kafka, microservices | ❌ | Smallest |
publish(POJO/record) |
Simple notifications, internal events | ✅ | Medium |
publish(POJO/record) + @Event |
Default routing configuration | ✅ | Medium |
prepare().publish() |
Custom metadata, dynamic routing | ✅ | Medium |
public record UserRegisteredEvent(String userId, String email) implements Event {
@Override
public Class<?> type() {
return UserRegisteredEvent.class;
}
@Override
public List<Class<? extends EventChannel>> channels() {
return List.of(InternalEventChannel.class, ExternalEventChannel.class);
}
}public class NotificationEventSubscriber implements EventSubscriber {
@Override
public List<Class<? extends Event>> events() {
return List.of(UserRegisteredEvent.class);
}
@Override
public void onEvent(Event event) {
if (event instanceof UserRegisteredEvent e) {
sendWelcomeEmail(e.email());
}
}
private void sendWelcomeEmail(String email) {
// Email sending logic
}
}
// Register the handler
handlerRegistry.register(new NotificationEventSubscriber());Handlers can receive the entire Envelope including metadata:
public class OrderEventHandler {
@EventListener
public void handleOrder(Envelope<OrderPlacedEvent> envelope) {
// Access payload
OrderPlacedEvent event = envelope.payload();
// Access metadata
UUID eventId = envelope.eventId();
UUID processId = envelope.processId();
Instant occurredAt = envelope.occurredAt();
Map<String, String> metadata = envelope.metadata();
System.out.println("Processed: " + event.orderId());
}
}Note: When using Envelope as a handler parameter, you must specify the payload type in the annotation:
@EventListener(OrderPlacedEvent.class)
public void handleOrder(Envelope<OrderPlacedEvent> envelope) {
// Handle envelope
}// Outgoing transport
OutTransport kafkaOut = new KafkaOutTransport(
"localhost:9092", // bootstrap servers
"events" // topic
);
EventChannel externalChannel = new ExternalEventChannel(
List.of(kafkaOut)
);
// Incoming transport
InTransport kafkaIn = new KafkaInTransport(
"localhost:9092", // bootstrap servers
"events", // topics (comma-separated)
"event-dispatcher" // group.id
);
// Dispatcher with Kafka transport
EventDispatcher dispatcher = EventDispatcherBuilder.create()
.executor(Executors.newVirtualThreadPerTaskExecutor())
.handlerRegistry(handlerRegistry)
.transports(List.of(kafkaIn))
.build();
dispatcher.start(dispatcher::dispatch);EventDispatcher idempotentDispatcher = EventDispatcherBuilder.create()
.executor(Executors.newVirtualThreadPerTaskExecutor())
.handlerRegistry(handlerRegistry)
.transports(List.of(inTransport))
.idempotent(10000, Duration.ofMinutes(5)) // max 10000 entries, 5 min TTL
.build();For transactional publishing in Spring Boot applications, see event-flow-spring/README.md.
For detailed configuration examples, see:
- Event Flow Core — LocalQueue, Kafka, custom transports, serialization
- Event Flow Spring — YAML auto-configuration, transactional publishing, retry support
LocalQueue is a built-in in-JVM transport for internal event exchange. See event-flow-core/README.md for details.
Kafka transport for external event communication. See event-flow-core/README.md for configuration examples.
Implement OutTransport or InTransport interfaces to add custom transport types. See event-flow-core/README.md for an example.
┌─────────┐ ┌──────────────┐ ┌────────────┐ ┌───────────┐ ┌──────────┐ ┌───────────────┐
│ Service │ │EventPublisher│ │EventChannel│ │LocalQueue │ │Dispatcher│ │HandlerRegistry│
└────┬────┘ └──────┬───────┘ └─────┬──────┘ └─────┬─────┘ └────┬─────┘ └───────┬───────┘
│ │ │ │ │ │
│ publish(event)│ │ │ │ │
│──────────────▶│ │ │ │ │
│ │ send(event) │ │ │ │
│ │────────────────▶│ │ │ │
│ │ │ offer(event) │ │ │
│ │ │──────────────▶│ │ │
│ │ │ │ │ │
│ │ │ │ take(event) │ │
│ │ │ │─────────────▶│ │
│ │ │ │ │ │
│ │ │ │ │getHandlers(event)
│ │ │ │ │───────────────▶│
│ │ │ │ │◀───────────────│
│ │ │ │ │ [handlers] │
│ │ │ │ │ │
│ │ │ │ │ async execute │
│ │ │ │ │───────────────▶│ onEvent(event)
│ │ │ │ │ │
│◀──────────────│ (CompletableFuture) │ │ │
┌─────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────┐ ┌──────────────┐
│ Service │ │EventPublisher│ │KafkaOutTransp│ │ Kafka │ │KafkaInTransp │
└────┬────┘ └──────┬───────┘ └──────┬───────┘ └────┬─────┘ └──────┬───────┘
│ │ │ │ │
│ publish(event)│ │ │ │
│──────────────▶│ │ │ │
│ │ send(event) │ │ │
│ │─────────────────▶│ │ │
│ │ │ produce(event) │ │
│ │ │───────────────▶│ │
│ │ │ │ │
│ │ │ ack(offset) │ │
│ │ │◀───────────────│ │
│ │◀─────────────────│ │ │
│ │ SendResult │ │ │
│◀──────────────│ │ │ │
│ │ │ │ │
│ │ │ │ poll(event) │
│ │ │ │────────────────────▶│
│ │ │ │ │
│ │ │ │ event │
│ │ │ │◀────────────────────│
┌──────────────┐ ┌───────────────┐ ┌─────────────────────────────────────┐
│ Dispatcher │ │HandlerRegistry│ │ Event Handlers │
└──────┬───────┘ └───────┬───────┘ └──────────────┬──────────────────────┘
│ │ │
│ dispatch(event) │ │
│──────────────────▶│ │
│ │ │
│ │ getHandlers(event) │
│ │─────────────────────────▶│
│ │ │
│ │ [Handler1, Handler2, ...]│
│ │◀─────────────────────────│
│ │ │
│ handlers │ │
│◀──────────────────│ │
│ │ │
│ executor.execute(Handler1) │
│─────────────────────────────────────────────▶│ Handler1.onEvent()
│ │ │
│ executor.execute(Handler2) │
│─────────────────────────────────────────────▶│ Handler2.onEvent()
│ │ │
│ ... │ │
│ │ │
- Event Flow Core — Detailed core module documentation
- Event Flow Spring — Spring Boot auto-configuration
- Javadoc
- Source Code
- Usage Examples
- Fork the repository
- Create a branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is licensed under the Apache License 2.0 — see the LICENSE file for details.
- Vladimir Aleshkov (@vovten)