0Pricing
Clean Architecture & Design Patterns in Practice · レッスン

イベント駆動のClean Architecture

ドメインイベントを使用して疎結合とスケーラビリティを高め、イベント駆動パターンをClean Architectureに統合します。

「イベント駆動のClean Architecture」はCoddyKit上の無料Clean Architecture & Design Patterns in Practiceレッスンです。 これはレッスン2/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはClean Architecture & Design Patterns in Practice学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Clean Architecture & Design Patterns in Practiceコースには全4レッスンが含まれています。

このレッスンの一部はまだ翻訳されておらず、英語で表示されています。

Intro to Event-Driven Clean Arch

Welcome to Event-Driven Clean Architecture! In complex systems, parts often need to react to things happening elsewhere without being tightly coupled.

This lesson explores how to integrate event-driven patterns into your Clean Architecture, using domain events to enhance decoupling and scalability, while strictly adhering to the Dependency Rule.

What are Domain Events?

A Domain Event is something that happened in the domain that domain experts care about. It's an immutable fact, a record of an occurrence, like 'Order Placed' or 'User Registered'.

  • They represent a change in the state of the domain.
  • They are typically named in the past tense (e.g., OrderPlaced).
  • They should be simple data structures, containing only relevant information about the event.

Events and the Dependency Rule

In Clean Architecture, the Dependency Rule states that dependencies must flow inwards. Domain events fit perfectly:

  • Entities can raise events, but don't know who handles them.
  • Use Cases orchestrate entities and can publish events after an operation.
  • Interface Adapters (e.g., presenters, external service gateways) can subscribe to and handle events from inner layers.

This maintains strict separation, as inner layers remain unaware of outer layer specifics.

Implementing a Domain Event

A domain event is essentially a data transfer object (DTO) that carries information about what happened. It's good practice to have a common interface for all domain events.

Here's a simple Java interface:

public interface DomainEvent {
  long occurredOn();
}

Code: Concrete Domain Event

Let's create a specific domain event: OrderPlacedEvent. It holds the orderId and the timestamp of when it occurred.

Try running this simple definition:

public class OrderPlacedEvent implements DomainEvent {
  private final String orderId;
  private final long occurredOn;

  public OrderPlacedEvent(String orderId) {
    this.orderId = orderId;
    this.occurredOn = System.currentTimeMillis();
  }

  public String getOrderId() {
    return orderId;
  }

  @Override
  public long occurredOn() {
    return occurredOn;
  }

  public static void main(String[] args) {
    OrderPlacedEvent event = new OrderPlacedEvent("ORD-123");
    System.out.println("Order event for: " + event.getOrderId());
  }
}

The Event Publisher

To publish events, we need an Event Publisher (also known as an Event Dispatcher or Event Bus). This component takes a domain event and dispatches it to all registered handlers.

It acts as a mediator, decoupling the event source from its consumers. The Use Case will depend on this publisher interface, not on specific handlers.

Code: Event Publisher Interface

Here's an interface for our EventPublisher. We'll also need a way for handlers to register themselves.

public interface EventPublisher {
  void publish(DomainEvent event);
  <T extends DomainEvent> void subscribe(Class<T> eventType, EventHandler<T> handler);
}

Code: In-Memory Event Publisher

A simple in-memory implementation for demonstration purposes. In a real application, this might use a message queue (e.g., Kafka, RabbitMQ) for persistence and distribution.

Run this to see the basic publisher in action:

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public interface DomainEvent { long occurredOn(); }
public interface EventHandler<T extends DomainEvent> { void handle(T event); }

public class OrderPlacedEvent implements DomainEvent {
  private final String orderId; private final long occurredOn;
  public OrderPlacedEvent(String orderId) {
    this.orderId = orderId; this.occurredOn = System.currentTimeMillis();
  }
  public String getOrderId() { return orderId; }
  @Override public long occurredOn() { return occurredOn; }
}

public class SimpleEventPublisher implements EventPublisher {
  private final Map<Class<? extends DomainEvent>, List<EventHandler<?>>> subscribers = new HashMap<>();

  @Override
  public void publish(DomainEvent event) {
    List<EventHandler<?>> handlers = subscribers.get(event.getClass());
    if (handlers != null) {
      for (EventHandler handler : handlers) {
        // Unchecked cast is safe due to type checking during subscription
        ((EventHandler<DomainEvent>) handler).handle(event);
      }
    }
  }

  @Override
  public <T extends DomainEvent> void subscribe(Class<T> eventType, EventHandler<T> handler) {
    subscribers.computeIfAbsent(eventType, k -> new ArrayList<>()).add(handler);
  }

  public static void main(String[] args) {
    SimpleEventPublisher publisher = new SimpleEventPublisher();
    publisher.subscribe(OrderPlacedEvent.class, event -> {
      System.out.println("Handler 1: Order " + event.getOrderId() + " placed!");
    });
    publisher.subscribe(OrderPlacedEvent.class, event -> {
      System.out.println("Handler 2: Notifying ops for order " + event.getOrderId());
    });

    OrderPlacedEvent event = new OrderPlacedEvent("DEMO-456");
    publisher.publish(event);
  }
}

Integrating Use Cases & Handlers

Now, let's see how a Use Case publishes an event and how an Event Handler (residing in, for example, the Interface Adapters layer) reacts to it. The Use Case remains decoupled from the handler.

This design allows new handlers to be added without modifying the Use Case.

Code: Full Event-Driven Flow

This example demonstrates a PlaceOrderUseCase publishing an OrderPlacedEvent, which is then handled by an OrderEmailNotifier.

Notice how PlaceOrderUseCase only depends on EventPublisher, not the specific notifier.

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

// Domain Layer Interfaces & Classes
interface DomainEvent { long occurredOn(); }
class OrderPlacedEvent implements DomainEvent {
  private final String orderId; private final long occurredOn;
  public OrderPlacedEvent(String orderId) {
    this.orderId = orderId; this.occurredOn = System.currentTimeMillis();
  }
  public String getOrderId() { return orderId; }
  @Override public long occurredOn() { return occurredOn; }
}

// Application Layer Interfaces & Classes
interface EventPublisher {
  void publish(DomainEvent event);
  <T extends DomainEvent> void subscribe(Class<T> eventType, EventHandler<T> handler);
}
interface EventHandler<T extends DomainEvent> { void handle(T event); }

// Use Case (Application Layer)
class PlaceOrderUseCase {
  private final EventPublisher eventPublisher;

  public PlaceOrderUseCase(EventPublisher eventPublisher) {
    this.eventPublisher = eventPublisher;
  }

  public void execute(String orderDetails) {
    // Simulate order creation logic
    String newOrderId = "ORD-" + System.nanoTime();
    System.out.println("Order " + newOrderId + " created with details: " + orderDetails);

    // Publish domain event
    eventPublisher.publish(new OrderPlacedEvent(newOrderId));
  }
}

// Infrastructure/Interface Adapters Layer Implementation
class SimpleEventPublisher implements EventPublisher {
  private final Map<Class<? extends DomainEvent>, List<EventHandler<?>>> subscribers = new HashMap<>();

  @Override
  public void publish(DomainEvent event) {
    List<EventHandler<?>> handlers = subscribers.get(event.getClass());
    if (handlers != null) {
      for (EventHandler handler : handlers) {
        ((EventHandler<DomainEvent>) handler).handle(event);
      }
    }
  }

  @Override
  public <T extends DomainEvent> void subscribe(Class<T> eventType, EventHandler<T> handler) {
    subscribers.computeIfAbsent(eventType, k -> new ArrayList<>()).add(handler);
  }
}

class OrderEmailNotifier implements EventHandler<OrderPlacedEvent> {
  @Override
  public void handle(OrderPlacedEvent event) {
    System.out.println("Email Notifier: Sending email for order " + event.getOrderId());
  }
}

class InventoryUpdater implements EventHandler<OrderPlacedEvent> {
  @Override
  public void handle(OrderPlacedEvent event) {
    System.out.println("Inventory Updater: Updating inventory for order " + event.getOrderId());
  }
}

public class Main {
  public static void main(String[] args) {
    SimpleEventPublisher publisher = new SimpleEventPublisher();

    // Register handlers
    publisher.subscribe(OrderPlacedEvent.class, new OrderEmailNotifier());
    publisher.subscribe(OrderPlacedEvent.class, new InventoryUpdater());

    // Create use case with publisher dependency
    PlaceOrderUseCase placeOrderUseCase = new PlaceOrderUseCase(publisher);

    // Execute use case, which publishes the event
    placeOrderUseCase.execute("Laptop, Quantity: 1");
    placeOrderUseCase.execute("Keyboard, Quantity: 2");
  }
}

Quick Check: Domain Events

When integrating domain events into Clean Architecture, what is the primary benefit of having Use Cases publish events rather than directly calling other services?

Recap: Event-Driven Clean Arch

In this lesson, we explored Event-Driven Clean Architecture. You learned:

  • Domain Events are immutable facts representing significant occurrences.
  • They enable decoupling, allowing Use Cases to publish events without knowing their subscribers.
  • Event Publishers mediate event dispatch to Event Handlers.
  • This pattern supports scalability and extensibility, making systems easier to evolve while adhering to the Dependency Rule.

By leveraging domain events, your Clean Architecture can become even more robust and adaptable.

よくある質問

「イベント駆動のClean Architecture」レッスンは無料ですか?

はい。「イベント駆動のClean Architecture」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Clean Architecture & Design Patterns in Practiceコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Clean Architecture & Design Patterns in Practiceコースには全4レッスンが含まれています。

「イベント駆動のClean Architecture」で何を学びますか?

ドメインイベントを使用して疎結合とスケーラビリティを高め、イベント駆動パターンをClean Architectureに統合します。 ブラウザで直接実行するハンズオンコードでClean Architecture & Design Patterns in Practiceを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

Clean Architecture & Design Patterns in Practiceを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのClean Architecture & Design Patterns in Practiceは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン2/4です。

「イベント駆動のClean Architecture」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このClean Architecture & Design Patterns in Practiceレッスンでコードを書いて実行できますか?

はい。すべてのClean Architecture & Design Patterns in Practiceレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. 横断的関心事への対応
  2. イベント駆動のClean Architecture
  3. マイクロサービスにおけるClean Architecture
  4. クリーンアーキテクチャにおけるCQRS
← Clean Architecture & Design Patterns in Practiceに戻る