SEDA(Staged Event-Driven Architecture) 란?

엔스마트·2024년 7월 10일

SEDA(Staged Event-Driven Architecture)는 확장성과 유연성을 극대화하기 위해 복잡한 애플리케이션을 여러 개의 "Stage"(단계)로 나누어 각각의 단계를 이벤트 기반으로 처리하는 소프트웨어 아키텍처 패턴입니다. SEDA의 주요 목표는 고성능, 고확장성, 고가용성을 달성하는 것입니다.

SEDA의 주요 개념

Stage (단계):

  • 각 Stage는 독립적으로 실행되는 작업 단위입니다. 한 Stage에서 수행된 작업은 다음 Stage로 넘겨집니다.
  • Stage는 입력 큐, 이벤트 핸들러, 스레드 풀 등의 요소로 구성됩니다.

Event (이벤트):

  • Stage 간의 데이터를 주고받는 수단입니다. 이벤트는 특정한 작업이나 상태 변화를 나타냅니다.

Input Queue (입력 큐):

  • 각 Stage는 입력 큐를 가지고 있습니다. 이전 Stage의 출력이 현재 Stage의 입력 큐로 들어옵니다.
  • 큐는 작업이 병목 현상을 일으키지 않도록 비동기적으로 데이터를 저장하고 전달합니다.

Event Handler (이벤트 핸들러):

  • 입력 큐에서 꺼낸 이벤트를 처리하는 로직을 담당합니다. 각 Stage마다 고유한 이벤트 핸들러가 있습니다.

Thread Pool (스레드 풀):

  • Stage의 작업을 병렬로 처리하기 위해 여러 개의 스레드를 관리합니다. 스레드 풀을 통해 동시성을 제어합니다.

SEDA의 장점

확장성:

  • 각 Stage가 독립적으로 실행되므로 필요한 경우 쉽게 확장할 수 있습니다. 예를 들어, 특정 Stage의 처리량이 증가하면 해당 Stage의 스레드 수를 늘릴 수 있습니다.

유연성:

  • 시스템을 여러 Stage로 분할하여 설계할 수 있으므로, 각 Stage를 독립적으로 개발하고 최적화할 수 있습니다.

고가용성:

  • 시스템이 자연스럽게 비동기적으로 동작하므로, 한 Stage의 장애가 다른 Stage에 영향을 미치지 않도록 설계할 수 있습니다.

SEDA의 구성 요소

  • Input Queue: Stage가 처리할 작업을 기다리는 큐입니다. Stage는 이 큐에서 작업을 꺼내서 처리합니다.
  • Event Handler: Input Queue에서 꺼낸 작업을 실제로 처리하는 로직을 포함합니다.
  • Thread Pool: 작업을 병렬로 처리하기 위해 여러 개의 스레드를 관리합니다.
  • Output Queue: Stage에서 처리된 작업을 다음 Stage로 전달하기 위해 사용하는 큐입니다.

SEDA 아키텍처의 예제

아래는 Spring Boot를 사용하여 SEDA 아키텍처를 구현한 간단한 예제입니다. 여기서는 주문 처리를 세 단계로 나누어 처리합니다: 수신 -> 처리 -> 응답 전송.

주문 이벤트 클래스 정의

import org.springframework.context.ApplicationEvent;

public class OrderEvent extends ApplicationEvent {
    private String orderId;
    private String product;
    private int quantity;

    public OrderEvent(Object source, String orderId, String product, int quantity) {
        super(source);
        this.orderId = orderId;
        this.product = product;
        this.quantity = quantity;
    }

    public String getOrderId() {
        return orderId;
    }

    public String getProduct() {
        return product;
    }

    public int getQuantity() {
        return quantity;
    }
}

이벤트를 발행하는 서비스 클래스

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;

@Service
public class OrderService {

    private final ApplicationEventPublisher eventPublisher;

    @Autowired
    public OrderService(ApplicationEventPublisher eventPublisher) {
        this.eventPublisher = eventPublisher;
    }

    public void createOrder(String orderId, String product, int quantity) {
        OrderEvent event = new OrderEvent(this, orderId, product, quantity);
        eventPublisher.publishEvent(event);
    }
}

Stage 인터페이스와 Stage 구현 클래스들

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

public interface Stage {
    void process(OrderEvent event);
}

@Service
public class ReceiveStage implements Stage {

    private final BlockingQueue<OrderEvent> nextStageQueue = new LinkedBlockingQueue<>();

    @Override
    public void process(OrderEvent event) {
        try {
            System.out.println("ReceiveStage processing order: " + event.getOrderId());
            nextStageQueue.put(event);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    public BlockingQueue<OrderEvent> getNextStageQueue() {
        return nextStageQueue;
    }
}

@Service
public class ProcessStage implements Stage {

    private final BlockingQueue<OrderEvent> nextStageQueue = new LinkedBlockingQueue<>();

    @Override
    public void process(OrderEvent event) {
        try {
            System.out.println("ProcessStage processing order: " + event.getOrderId());
            nextStageQueue.put(event);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    public BlockingQueue<OrderEvent> getNextStageQueue() {
        return nextStageQueue;
    }
}

@Service
public class SendResponseStage implements Stage {

    @Override
    public void process(OrderEvent event) {
        System.out.println("SendResponseStage sending response for order: " + event.getOrderId());
    }
}

SEDA 관리 클래스

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.stereotype.Service;

@Service
@EnableAsync
public class SEDA implements ApplicationListener<OrderEvent> {

    private final ReceiveStage receiveStage;
    private final ProcessStage processStage;
    private final SendResponseStage sendResponseStage;

    @Autowired
    public SEDA(ReceiveStage receiveStage, ProcessStage processStage, SendResponseStage sendResponseStage) {
        this.receiveStage = receiveStage;
        this.processStage = processStage;
        this.sendResponseStage = sendResponseStage;
    }

    @Async
    @Override
    public void onApplicationEvent(OrderEvent event) {
        receiveStage.process(event);

        new Thread(() -> {
            try {
                OrderEvent receivedEvent = receiveStage.getNextStageQueue().take();
                processStage.process(receivedEvent);

                OrderEvent processedEvent = processStage.getNextStageQueue().take();
                sendResponseStage.process(processedEvent);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }).start();
    }
}

Spring Boot 메인 클래스

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;

@SpringBootApplication
@EnableAsync
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

실행과 테스트

위의 예제 코드를 모두 작성한 후에는 OrderService를 통해 주문이 생성될 때마다 OrderEvent가 발생하고, 이를 SEDA 클래스에서 단계별로 비동기적으로 처리하는 것을 확인할 수 있습니다.

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class OrderServiceTest {

    @Autowired
    private OrderService orderService;

    @Test
    void testCreateOrder() {
        orderService.createOrder("ORD-123", "Product A", 2);
        // 주문 생성 후 각 단계의 로그 확인
    }
}

이 예제는 Spring Boot를 사용하여 SEDA 아키텍처를 구현한 예제입니다. 각 단계는 BlockingQueue를 통해 다음 단계로 이벤트를 전달하며, 이벤트 기반으로 비동기적으로 작업을 처리합니다. 이를 통해 SEDA의 기본 개념을 이해하고 적용할 수 있습니다.

profile
클라우드 전환, MSA 서비스, DevOps 환경 구축과 기술지원 그리고 엔터프라이즈 시스템을 구축하는 최고 실력과 경험을 가진 Architect Group 입니다.

0개의 댓글