Event-Driven Architecture (EDA) is a design style in which the components of a system communicate by producing and consuming events, instead of invoking one another directly. This approach favors decoupling, scalability, and the ability to react in real time to what happens in the business. In this lesson we will lay the conceptual foundations you will need to understand the rest of the module: what an event is, how it differs from a command and a message, what role producers and consumers play, and what topologies exist to connect them.

Understanding these fundamentals well is essential because nearly all modern large-scale systems (banking, e-commerce, streaming, IoT) rely on events to integrate microservices without them knowing about each other.

Contents

  1. What is event-driven architecture?
  2. Event vs command vs message
  3. Producers and consumers
  4. Mediator topology
  5. Broker topology
  6. General event flow diagram
  7. Common mistakes and tips
  8. Exercises and solutions
  9. Conclusion

  1. What is event-driven architecture?

An event represents something that has already happened in the system and is relevant to the business. Examples: "OrderCreated", "PaymentConfirmed", "UserRegistered". What matters is the verb tense: the event is an accomplished, immutable fact.

In an event-driven architecture:

  • Components emit events when their state changes.
  • Other components react to those events without the emitter knowing who is listening.
  • Communication is typically asynchronous: the emitter does not wait for a response.

This contrasts with the traditional architecture based on synchronous calls (REST/RPC), where the client knows about and depends on the server.

Characteristic Synchronous call (REST/RPC) Event-driven architecture
Coupling Strong (client knows the server) Loose (emitter does not know the receiver)
Timing Blocking (waits for a response) Non-blocking (fire-and-forget)
Scalability Limited by the slowest component High (independent consumers)
Fault tolerance If the server goes down, the call fails The event can be processed later

  1. Event vs command vs message

These three terms are often confused. They are types of messages, but with different intentions.

Concept Intention Verb tense Direction Awaits a response?
Command Order that something happen Imperative: "CreateOrder" 1 emitter → 1 specific receiver Sometimes
Event Notify that something happened Past: "OrderCreated" 1 emitter → N unknown receivers No
Message Generic term encompassing both Depends

Let's look at a code example to nail down the difference between a command and an event:

// COMMAND: an instruction directed at a specific recipient.
// SOMEONE is expected to execute it. Name in the imperative.
public record CreateOrderCommand(
        String customerId,
        List<OrderLine> lines) {
}

// EVENT: the fact that the order has ALREADY been created.
// It is immutable, carries an identifier and a timestamp.
// Name in the past tense.
public record OrderCreatedEvent(
        String orderId,
        String customerId,
        BigDecimal total,
        Instant occurredAt) {
}

Detailed explanation of the snippet:

  • record is an immutable, concise Java class (since Java 16), ideal for representing data that does not change, such as events.
  • CreateOrderCommand uses a verb in the imperative because it expresses an intention that something happen. It is normally processed by a single responsible component.
  • OrderCreatedEvent uses the participle "Created" because it describes a past fact. It includes occurredAt (an Instant, a UTC timestamp) because every event must be orderable in time.
  • The event contains the already-calculated total: it transports the result, not the order to calculate it.

Practical rule: if you can reject the request, it is probably a command. If you are only reporting that something happened and no one can "refuse", it is an event.

  1. Producers and consumers

In EDA we distinguish two fundamental roles:

  • Producer (Producer / Publisher): the component that detects a change and emits the event. It does not know (nor care) who will consume it.
  • Consumer (Consumer / Subscriber): the component that receives the event and reacts (updates a database, sends an email, calls another service...).
// PRODUCER: publishes the event to a channel. It does not know the consumers.
@Service
public class OrderService {

    private final EventPublisher publisher; // abstraction of the channel

    public OrderService(EventPublisher publisher) {
        this.publisher = publisher;
    }

    public void createOrder(CreateOrderCommand cmd) {
        // 1. Business logic: persist the order
        String orderId = UUID.randomUUID().toString();
        // ... save to the database ...

        // 2. Publish the event describing what happened
        var event = new OrderCreatedEvent(
                orderId, cmd.customerId(), calculateTotal(cmd), Instant.now());
        publisher.publish("orders.created", event);
    }
}

Key points:

  • EventPublisher is an abstraction: it hides whether we use Kafka, RabbitMQ, etc. underneath. This allows changing the technology without touching the business logic.
  • After saving the order, the service publishes to the logical channel "orders.created". There is no reference to who is listening.
// CONSUMER: subscribes to the channel and reacts to the event.
@Component
public class CustomerNotifier {

    @EventListener("orders.created") // subscribes to the same channel
    public void onOrderCreated(OrderCreatedEvent event) {
        // Reaction: send a confirmation to the customer
        sendConfirmationEmail(event.customerId(), event.orderId());
    }
}
  • The CustomerNotifier consumer reacts by sending an email. Another consumer (for example, BillingService) could listen to the same event to issue an invoice. Adding new consumers does not require modifying the producer: that is where the power of EDA lies.

  1. Mediator topology

There are two major topologies for organizing the event flow. The first is the mediator topology.

Here, a central component (the mediator or orchestrator) receives an initial event, breaks it down into steps, and coordinates their execution by directing the workflow.

  • Useful when the process has several steps with ordering and conditional logic (for example, opening an insurance policy: validate data → rate → issue policy → charge).
  • The mediator knows the complete flow, which centralizes the coordination logic.
  • Drawback: the mediator can become a bottleneck or a single point of failure, and it couples the knowledge of the process into a single place.
flowchart LR
    A[Customer] -->|Initial event| M{Mediator / Orchestrator}
    M -->|step 1| S1[Validation Service]
    M -->|step 2| S2[Rating Service]
    M -->|step 3| S3[Issuance Service]
    S1 -.response.-> M
    S2 -.response.-> M
    S3 -.response.-> M

In the diagram, the mediator (M) is the one that decides the order and collects the responses from each service. The services do not know each other; they only talk to the mediator.

  1. Broker topology

The second is the broker topology (no mediator). There is no central coordinator: events flow freely through a broker (channel/queue) and each service reacts autonomously, and can in turn emit new events that trigger others.

  • Ideal for simple and highly decoupled flows.
  • Maximum scalability and resilience: there is no central point.
  • Drawback: the overall flow ends up "spread out" and is harder to visualize and debug (there is no single place to read the whole process).
flowchart LR
    P[Orders Service] -->|OrderCreated| B((Broker))
    B --> I[Inventory Service]
    B --> F[Billing Service]
    I -->|StockReserved| B
    F -->|InvoiceIssued| B
    B --> E[Shipping Service]

Here Inventory Service reacts to OrderCreated and, in turn, emits StockReserved, which triggers Shipping Service. The flow emerges from the sum of individual reactions, with no coordinator.

Criterion Mediator Broker
Flow control Centralized Distributed
Coupling Medium (everyone knows the mediator) Minimal
Process visibility High (a single place) Low (spread out)
Resilience Single point of failure Very high
Ideal cases Complex, conditional flows Simple, decoupled flows

  1. General event flow diagram

Combining the concepts, here is what the basic producer → channel → consumers flow looks like:

sequenceDiagram
    participant P as Producer
    participant C as Event channel
    participant C1 as Consumer 1 (Email)
    participant C2 as Consumer 2 (Billing)
    P->>C: publish(OrderCreatedEvent)
    C-->>C1: deliver event
    C-->>C2: deliver event
    Note over C1,C2: They process in parallel and<br/>independently

The producer publishes only once; the channel distributes the event to all interested subscribers, which work in parallel and without knowing about each other.

Common Mistakes and Tips

  • Confusing commands with events. If you name an event in the imperative ("SendEmail") you are disguising a command. Always use the past tense for events: "EmailRequested" or "OrderCreated".
  • Putting the consumer's business logic in the producer. The producer must not know what the consumers do. If it does, you couple things again.
  • Events that are too "fat" or too "thin". An event must carry enough data for the consumer to react without having to call back to the producer (which reintroduces coupling), but without becoming a dump of the entire database.
  • Forgetting the timestamp and the identifier. Every event must be traceable and idempotently identifiable.
  • Tip: start with a broker topology for simple flows and reserve the mediator for processes with many conditional steps.

Exercises

  1. Classify the following messages as a command or an event and justify your answer: (a) "ReserveRoom", (b) "PaymentRejected", (c) "UpdatePrice", (d) "SessionStarted".
  2. Sketch (in pseudocode or words) what a "customer onboarding" process would look like with a mediator topology versus a broker topology. Which one would you choose and why?
  3. Design a Java event record for "InvoiceIssued" that includes the minimum fields needed for an accounting consumer to react without querying back to the emitter.

Solutions

  1. (a) Command (imperative, directed, can be rejected). (b) Event (past fact, notification). (c) Command (order to change something). (d) Event (the session has already started).
  2. With a mediator: a CustomerOnboardingOrchestrator receives the request and calls, in order, validation → creation → welcome, handling failures. With a broker: RegistrationService emits CustomerRegistered; EmailService and CRMService react independently. For a simple onboarding, the broker is preferable because of its low coupling; if there were many conditional steps (identity verification, scoring), the mediator makes control easier.
  3. One possible solution:
public record InvoiceIssuedEvent(
        String invoiceId,
        String orderId,
        String customerId,
        BigDecimal totalAmount,
        BigDecimal taxes,
        String currency,
        Instant issuedAt) {
}

It includes amount, taxes, and currency so that accounting can post the entry without querying again.

Conclusion

We have seen that event-driven architecture replaces direct calls with the emission and consumption of business facts, achieving strong decoupling. We distinguished commands (orders), events (past facts), and messages (the generic term), and we understood the producer and consumer roles. Finally, we compared the two key topologies: mediator (centralized control) and broker (maximum autonomy).

In the next lesson, "Asynchronous Messaging: Queues and Brokers", we will dig deeper into the infrastructure that transports these events: queues versus topics, delivery guarantees, and specific technologies such as RabbitMQ, Kafka, and SQS.

Application Architecture Course

Module 1: Fundamentals of Application Architecture

Module 2: Design Principles and Tactics

Module 3: Architectural Styles and Patterns

Module 4: Distributed Architectures and Microservices

Module 5: Event-Driven Architectures and Messaging

Module 6: Domain-Driven Design (DDD)

Module 7: Data and Persistence

Module 8: Cloud Architecture and Deployment

Module 9: Quality, Security and Observability

Module 10: Evolution, Governance and Case Studies

© Copyright 2026. All rights reserved