0Pricing
Clean Architecture & Design Patterns in Practice · 강의

이벤트 기반 클린 아키텍처

도메인 이벤트를 활용해 결합도를 낮추고 확장성을 높이는 이벤트 기반 패턴을 클린 아키텍처에 통합합니다.

이벤트 기반 클린 아키텍처은(는) CoddyKit의 무료 Clean Architecture & Design Patterns in Practice 강의입니다. 이것은 4개 중 2번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 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.

자주 묻는 질문

“이벤트 기반 클린 아키텍처” 강의는 무료인가요?

네 — “이벤트 기반 클린 아키텍처” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 Clean Architecture & Design Patterns in Practice 강의 전체를 잠금 해제할 수 있습니다. Clean Architecture & Design Patterns in Practice 강의에는 총 4개의 강의가 포함되어 있습니다.

“이벤트 기반 클린 아키텍처”에서 뭘 배우나요?

도메인 이벤트를 활용해 결합도를 낮추고 확장성을 높이는 이벤트 기반 패턴을 클린 아키텍처에 통합합니다. 브라우저에서 직접 실행하는 실습 코드로 Clean Architecture & Design Patterns in Practice을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.

Clean Architecture & Design Patterns in Practice을(를) 시작하는 데 경험이 필요한가요?

사전 경험은 필요하지 않습니다. CoddyKit의 Clean Architecture & Design Patterns in Practice은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 2번째 강의입니다.

“이벤트 기반 클린 아키텍처” 강의는 얼마나 걸리나요?

대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.

이 Clean Architecture & Design Patterns in Practice 강의에서 코드를 작성하고 실행할 수 있나요?

네. 모든 Clean Architecture & Design Patterns in Practice 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.

이 강의의 모든 강의

  1. 횡단 관심사 처리
  2. 이벤트 기반 클린 아키텍처
  3. 마이크로서비스의 클린 아키텍처
  4. 클린 아키텍처 안의 CQRS
← Clean Architecture & Design Patterns in Practice(으)로 돌아가기