Akashic Records

Spring Integration의 주요 특징 본문

Spring Integration for Beginners

Spring Integration의 주요 특징

Andrew's Akashic Records 2024. 12. 18. 10:17
728x90
1. Spring Integration 소개
1.1 Spring Integration의 배경과 필요성
1.2 Spring Integration의 탄생과 역사
1.3 Spring Integration의 주요 특징

Spring Integration for Backend Developers

Spring Integration의 철학: Spring의 일관성 유지

이전 글에서 계속 언급된 이야기이지만, Spring Integration은 Spring Framework의 핵심 철학을 기반으로 설계되었으며, 이를 통해 통합 작업에서도 일관된 개발 경험유연한 확장성을 제공합니다. 

 

 Spring Integration은 Spring Framework의 핵심 철학인 POJO 기반 개발, DI/IoC, 유연한 설정, 모듈화를 일관되게 유지하면서 엔터프라이즈 애플리케이션 통합 문제를 해결합니다. 이를 통해 개발자는 Spring 생태계의 강력한 기능을 활용하면서도 복잡한 시스템 통합을 보다 간편하고 직관적으로 구현할 수 있습니다.

Spring Integration의 철학

Spring Integration이 유지하고자 하는 Spring의 일관성은 다음과 같은 철학적 원칙을 기반으로 합니다:

  1. POJO(Plain Old Java Object) 기반의 개발
  2. DI(Dependency Injection)와 IoC(Inversion of Control)
  3. 모듈화와 재사용성
  4. 유연한 설정 옵션
  5. 확장 가능하고 개방된 아키텍처

1. POJO 기반 개발

Spring Integration은 Spring Framework의 철학을 따르며, 통합 로직을 POJO로 작성할 수 있도록 지원합니다. POJO 기반의 개발은 특정 프레임워크나 라이브러리에 종속되지 않고 순수한 자바 객체로 구현할 수 있음을 의미합니다.

이를 통해 테스트 용이성유지보수성이 크게 향상됩니다.

 

예시: POJO 기반의 Service Activator

Service Activator는 메시지를 처리하는 핸들러로, 단순한 POJO 메서드를 사용합니다.

public class MyService {
    public void handleMessage(String message) {
        System.out.println("Processing message: " + message);
    }
}

 

Spring Integration 설정:

@Configuration
@EnableIntegration
public class IntegrationConfig {

    @Bean
    public MessageChannel inputChannel() {
        return new DirectChannel();
    }

    @Bean
    @ServiceActivator(inputChannel = "inputChannel")
    public MyService myService() {
        return new MyService();
    }
}

위의 설정에서 MyService 클래스는 특별한 인터페이스나 상속이 필요하지 않으며, POJO 형태로 메시지를 처리합니다. 이는 Spring Integration의 POJO 기반 철학을 잘 보여줍니다.

2. DI(Dependency Injection)와 IoC(Inversion of Control)

Spring Integration은 Spring IoC 컨테이너를 기반으로 동작합니다. 모든 컴포넌트(채널, 어댑터, 메시지 핸들러 등)는 Spring의 IoC 컨테이너에 의해 관리되며, DI를 통해 필요할 때 주입됩니다.

이로 인해 개발자는 클래스 간의 강한 결합을 피하고, 객체 간 관계를 유연하게 관리할 수 있습니다.

 

예시: DI를 통한 메시지 핸들러 주입

@Configuration
public class IntegrationConfig {

    @Bean
    public MessageChannel inputChannel() {
        return new DirectChannel();
    }

    @Bean
    public MyService myService() {
        return new MyService();
    }

    @Bean
    @ServiceActivator(inputChannel = "inputChannel")
    public MessageHandler handler(MyService myService) {
        return message -> myService.handleMessage(message.getPayload().toString());
    }
}

3. 모듈화와 재사용성

Spring Integration은 구성 요소를 모듈화하여 재사용할 수 있도록 설계되었습니다. 메시지 채널, 라우터, 필터, 트랜스포머 등의 요소는 독립적으로 구현되어 다른 통합 흐름에서 재사용할 수 있습니다.

이러한 모듈화는 코드 중복을 줄이고 시스템 유지보수를 용이하게 만듭니다.

 

예시: 메시지 채널과 핸들러의 재사용

@Bean
public MessageChannel commonChannel() {
    return new DirectChannel();
}

@Bean
@ServiceActivator(inputChannel = "commonChannel")
public MessageHandler commonHandler() {
    return message -> System.out.println("Common Handler: " + message.getPayload());
}

4. 유연한 설정 옵션

Spring Integration은 XML 설정, Java Config, DSL(Domain-Specific Language)과 같은 다양한 설정 방식을 지원합니다. 개발자는 프로젝트의 요구사항에 맞게 가장 적합한 방식을 선택할 수 있습니다.

 

XML 설정 예시

<int:channel id="inputChannel" />
<int:service-activator input-channel="inputChannel" ref="myService" method="handleMessage" />
<bean id="myService" class="com.example.MyService" />

 

Java Config 설정 예시

@Bean
public IntegrationFlow integrationFlow() {
    return IntegrationFlows.from("inputChannel")
            .handle(message -> System.out.println("Message: " + message.getPayload()))
            .get();
}

 

5. 확장 가능하고 개방된 아키텍처

Spring Integration은 다양한 기술과 프로토콜(HTTP, FTP, JMS, Kafka 등)과의 통합을 지원합니다. 또한 확장 가능한 구조를 통해 개발자가 새로운 어댑터나 커스텀 컴포넌트를 추가할 수 있습니다.

 

확장 포인트 예시

  • 커스텀 메시지 핸들러 추가
  • 새로운 어댑터 개발 (예: MQTT, ZeroMQ 등)
  • Expression Language(SpEL)를 사용한 동적 라우팅 구현
728x90

구성 유연성: Java Config, XML, DSL

Spring Integration은 구성 유연성을 제공하여 개발자가 프로젝트의 성격과 요구사항에 맞게 다양한 방식으로 설정할 수 있도록 지원합니다. 이를 통해 개발자는 더 큰 생산성과 유지보수성을 확보할 수 있습니다.

1. Java Config 기반 구성

Java Config는 애너테이션과 순수 자바 코드만을 사용하여 Spring Integration을 구성하는 방식입니다. 이는 Spring Framework 3.0 이후 공식적으로 지원되었으며, XML 설정의 복잡성을 줄여주는 효과가 있습니다.

 

  • Spring Integration의 Java Config는 빈 설정과 애너테이션 기반으로 메시지 흐름을 정의합니다.
  • @Bean과 @ServiceActivator, @Transformer 등을 사용하여 구성 요소를 분리하고 설정합니다.
  • XML 기반 설정을 코드로 옮긴 명시적인 설정 방식입니다.

 

예시: Java Config를 활용한 간단한 메시지 흐름

@Configuration
@EnableIntegration
public class FileIntegrationJavaConfig {

    @Bean
    public MessageChannel inputChannel() {
        return new DirectChannel();
    }

    @Bean
    public FileReadingMessageSource fileReader() {
        FileReadingMessageSource source = new FileReadingMessageSource();
        source.setDirectory(new File("input"));
        source.setAutoCreateDirectory(true);
        return source;
    }

    @Bean
    @ServiceActivator(inputChannel = "inputChannel", outputChannel = "outputChannel")
    public FileToStringTransformer fileToStringTransformer() {
        return new FileToStringTransformer();
    }

    @Bean
    @ServiceActivator(inputChannel = "outputChannel")
    public void handleMessage(String payload) {
        System.out.println("Processed: " + payload);
    }
}

 

Java Config 장점

  1. 명시적 설정: 각 단계가 독립적인 @Bean 또는 **@ServiceActivator**로 분리되어 관리하기 쉽습니다.
  2. 구조화: 설정과 비즈니스 로직이 명확하게 분리됩니다.
  3. 유연한 조합: 개별 설정 요소를 재사용하거나 조합할 수 있습니다.
  4. 학습 곡선이 낮음: 기존의 Java Config나 애너테이션 기반 설정에 익숙하면 쉽게 사용할 수 있습니다.

Java Config 단점

  1. 설정이 길어짐: 복잡한 메시지 흐름을 정의하려면 많은 @Bean 설정이 필요합니다.
  2. 흐름이 분산됨: 여러 @Bean에 분리되어 있어 전체 메시지 흐름을 한눈에 파악하기 어렵습니다.
  3. 선언적 표현 부족: 메시지 흐름이 코드 구조에 따라 나누어지므로 선언적이지 않습니다.

2. XML 기반 구성

XML 설정은 Spring Integration의 초기 버전부터 제공되던 설정 방식으로, 여전히 많은 프로젝트에서 사용됩니다. 특히 시각화가 중요하거나 외부 구성 파일이 필요한 환경에서 유용합니다.

 

장점

  • 시각적 표현: XML은 메시지 흐름과 구성 요소를 한눈에 볼 수 있음.
  • 레거시 시스템 지원: 기존 Spring 프로젝트와의 호환성이 뛰어남.
  • 환경 분리: 설정 파일을 외부화하여 개발, 테스트, 운영 환경에 맞게 관리 가능.

예시: XML을 활용한 메시지 흐름

<int:channel id="inputChannel" />
<int:channel id="outputChannel" />

<int:service-activator input-channel="inputChannel" 
                      output-channel="outputChannel" 
                      ref="myService" 
                      method="processMessage" />

<bean id="myService" class="com.example.MyService" />
  • 설명:
    • <int:channel>: 메시지가 흐르는 경로를 설정합니다.
    • <int:service-activator>: 메시지를 처리할 핸들러를 연결합니다.
    • ref="myService": 메시지를 처리하는 서비스 클래스와 메서드를 참조합니다.

핸들러 코드 (POJO 형태):

public class MyService {
    public void processMessage(String message) {
        System.out.println("Processing message: " + message);
    }
}

 

3. DSL (Domain-Specific Language) 기반 구성

Spring Integration DSL은 Java 기반의 유연하고 직관적인 API를 제공하여 메시지 흐름을 선언적으로 구성할 수 있도록 지원합니다. 이는 Spring Integration 4.x부터 공식적으로 지원되었으며, 설정이 더욱 간결해졌습니다.

  • Spring Integration의 Java DSL함수형 스타일을 기반으로 메시지 흐름을 구성하는 방식을 제공합니다.
  • IntegrationFlows 클래스를 사용해 플루언트 API로 메시지의 흐름을 선언적으로 작성할 수 있습니다.
  • 코드가 직관적이며, 메시지 흐름의 단계가 명확히 표현됩니다.

예시: DSL을 활용한 메시지 흐름

@Configuration
@EnableIntegration
public class FileIntegrationDSLConfig {

    @Bean
    public IntegrationFlow fileIntegrationFlow() {
        return IntegrationFlows.from("inputChannel") // 메시지 시작점
                .transform(new FileToStringTransformer()) // 파일을 문자열로 변환
                .filter((String payload) -> payload.contains("IMPORTANT")) // 필터링
                .handle(System.out::println) // 결과 출력
                .get();
    }

    @Bean
    public MessageChannel inputChannel() {
        return MessageChannels.direct().get();
    }
}

 

DSL 장점

  1. 간결하고 선언적: 메시지 흐름의 구조를 한눈에 볼 수 있습니다.
  2. 유연성: 복잡한 워크플로우나 다양한 처리 단계를 명확하게 표현할 수 있습니다.
  3. 가독성: 비즈니스 로직이 코드에 흐름대로 표현되므로 가독성이 높습니다.
  4. 연결성: 채널, 필터, 변환기 등을 코드 내에서 유기적으로 연결할 수 있습니다.

DSL 단점

  1. 초기 학습 곡선: 플루언트 API에 익숙해질 필요가 있습니다.
  2. 규모가 커질 경우: 매우 복잡한 워크플로우에서는 한 클래스에 모든 로직이 집중될 수 있습니다.
  3. 주입된 빈 설정: 일부 빈이 DSL 코드에서 명확하지 않게 주입될 수 있습니다.

4. 구성 방식 비교

구성 장점 단점 권장 사용 환경
Java Config - 타입 세이프티- 유지보수 용이 설정이 길어질 경우 복잡해질 수 있음 코드 기반의 프로젝트
XML 설정 - 시각적 구성- 환경 분리 용이 설정 파일이 길고 복잡함 레거시 프로젝트, 외부 설정 필요시
DSL - 간결하고 직관적- 함수형 스타일 복잡한 흐름에서는 코드 길어질 수 있음 선언적이고 간단한 메시지 흐름

 

확장성과 다양한 어댑터 제공 (파일, 데이터베이스, 메시지 브로커 등)

Spring Integration은 다양한 시스템과의 통합을 위해 확장 가능한 구조를 제공하며, 파일 시스템, 데이터베이스, 메시지 브로커 등과 쉽게 연동할 수 있도록 다양한 어댑터(Adapter)를 지원합니다. 이를 통해 복잡한 시스템 간 통합 작업을 간소화하고, 개발자는 최소한의 설정으로 비즈니스 요구사항에 맞는 시스템 통합을 구현할 수 있습니다.

1. 확장 가능한 아키텍처

Spring Integration은 확장 가능한 구조를 바탕으로 다양한 외부 시스템과 연결될 수 있도록 설계되었습니다.

 

확장성의 핵심 요소

  1. 모듈화된 구성 요소
    메시지 채널, 엔드포인트, 어댑터와 같은 요소들은 독립적으로 구현되어 재사용과 확장이 가능합니다.
  2. 커스텀 어댑터 지원
    Spring Integration의 표준 어댑터를 넘어서 개발자가 필요에 따라 커스텀 어댑터를 추가할 수 있습니다.
  3. 다양한 프로토콜 지원
    HTTP, FTP, JMS, WebSocket, AMQP, MQTT 등 여러 프로토콜을 통한 통합이 가능합니다.

2. 다양한 어댑터의 종류와 예시

Spring Integration은 파일, 데이터베이스, 메시지 브로커, 이메일, HTTP 등 다양한 어댑터를 제공하며 확장 가능한 구조를 통해 개발자가 쉽게 통합 시스템을 구현할 수 있도록 지원합니다.

이러한 유연성과 확장성 덕분에 Spring Integration은 다양한 환경에서 요구되는 시스템 간 통합을 효과적으로 처리할 수 있는 강력한 프레임워크로 자리 잡았습니다.

1) 파일(File) 어댑터

파일 시스템에서 데이터를 읽거나 쓰는 작업을 처리합니다. 로컬 파일뿐만 아니라 원격 디렉터리(SFTP/FTP)도 지원합니다.

 

예시: 디렉터리에서 파일 읽기

@Configuration
@EnableIntegration
public class FileIntegrationConfig {

    @Bean
    public IntegrationFlow fileReadingFlow() {
        return IntegrationFlows
                .from(Files.inboundAdapter(new File("/input-directory"))
                        .patternFilter("*.txt")) // 텍스트 파일만 읽음
                .transform(Transformers.fileToString()) // 파일 내용을 문자열로 변환
                .handle(message -> System.out.println("File Content: " + message.getPayload()))
                .get();
    }
}
  • 설명:
    • Files.inboundAdapter(): 지정된 디렉터리에서 파일을 읽습니다.
    • Transformers.fileToString(): 파일 내용을 문자열로 변환합니다.

2) 데이터베이스(Database) 어댑터

JDBC를 사용하여 데이터베이스와 연동하며, 데이터를 삽입하거나 조회할 수 있습니다.

 

예시: 데이터베이스에서 데이터 조회

@Configuration
@EnableIntegration
public class DatabaseIntegrationConfig {

    @Bean
    public IntegrationFlow jdbcFlow(DataSource dataSource) {
        return IntegrationFlows.from(Jdbc.inboundAdapter(dataSource, "SELECT * FROM orders"))
                .handle(message -> System.out.println("Order: " + message.getPayload()))
                .get();
    }
}
  • 설명:
    • Jdbc.inboundAdapter(): SQL 쿼리를 실행하여 결과를 메시지로 변환합니다.

3) JMS (Java Message Service) 어댑터

메시지 큐 시스템을 통해 시스템 간 데이터를 비동기적으로 처리할 수 있습니다.

 

예시: JMS를 사용한 메시지 수신

@Configuration
@EnableIntegration
public class JmsIntegrationConfig {

    @Bean
    public IntegrationFlow jmsFlow(ConnectionFactory connectionFactory) {
        return IntegrationFlows.from(Jms.messageDrivenChannelAdapter(connectionFactory)
                        .destination("queue.orders")) // JMS 큐 이름
                .handle(message -> System.out.println("Received JMS Message: " + message.getPayload()))
                .get();
    }
}
  • 설명:
    • Jms.messageDrivenChannelAdapter(): JMS 큐로부터 메시지를 읽어옵니다.
    • 비동기 메시지 처리를 통해 성능과 확장성을 확보합니다.

4) 메시지 브로커 어댑터 (AMQP, Kafka, MQTT)

메시지 브로커를 통해 시스템 간 데이터를 전송합니다.

 

AMQP/RabbitMQ 예시:

@Configuration
@EnableIntegration
public class RabbitMQIntegrationConfig {

    @Bean
    public IntegrationFlow amqpInboundFlow(ConnectionFactory connectionFactory) {
        return IntegrationFlows.from(Amqp.inboundAdapter(connectionFactory, "queue.orders"))
                .handle(message -> System.out.println("RabbitMQ Message: " + message.getPayload()))
                .get();
    }
}

 

Kafka 예시:

@Bean
public IntegrationFlow kafkaInboundFlow() {
    return IntegrationFlows.from(Kafka.messageDrivenChannelAdapter(consumerFactory(), "topic.orders"))
            .handle(message -> System.out.println("Kafka Message: " + message.getPayload()))
            .get();
}

 

5) HTTP 어댑터

HTTP를 통해 외부 시스템과 데이터를 교환합니다.

 

예시: HTTP 엔드포인트 생성

@Configuration
@EnableIntegration
public class HttpIntegrationConfig {

    @Bean
    public IntegrationFlow httpInboundFlow() {
        return IntegrationFlows.from(Http.inboundGateway("/receive"))
                .handle(message -> System.out.println("HTTP Message: " + message.getPayload()))
                .get();
    }
}
  • 설명:
    • Http.inboundGateway(): HTTP 요청을 받아 메시지로 변환합니다.

6) 이메일(Email) 어댑터

SMTP와 IMAP 프로토콜을 지원하여 이메일 송수신 작업을 처리합니다.

 

예시: 이메일 수신

@Bean
public IntegrationFlow emailInboundFlow() {
    return IntegrationFlows.from(Mail.imapInboundAdapter("imap://user:password@mail.example.com/INBOX")
                        .javaMailProperties(p -> p.put("mail.debug", "true")))
                .handle(message -> System.out.println("Email Received: " + message.getPayload()))
                .get();
}

3. 커스텀 어댑터 지원

Spring Integration은 다양한 표준 어댑터 외에도 커스텀 어댑터를 쉽게 확장할 수 있도록 설계되었습니다. 이를 통해 조직의 고유한 요구사항에 맞는 통합 로직을 구현할 수 있습니다.

엔터프라이즈 통합 패턴(EIP) 지원

Spring Integration의 가장 큰 특징 중 하나는 **엔터프라이즈 통합 패턴(Enterprise Integration Patterns, EIP)**을 기반으로 설계되었다는 점입니다. EIP는 시스템 간 메시지 기반 통합의 표준 패턴으로, 다양한 통합 문제를 해결하기 위한 설계 패턴들을 제공합니다.

 

Gregor HohpeBobby Woolf가 저술한 "Enterprise Integration Patterns" 책에서 제시된 이 패턴들은 시스템 간 데이터 흐름, 라우팅, 변환 등 복잡한 통합 시나리오를 해결하기 위해 사용됩니다. Spring Integration은 이 패턴들을 프레임워크 내에 자연스럽게 통합하였으며, 개발자가 이를 쉽게 사용할 수 있도록 구현체를 제공합니다.

1. 엔터프라이즈 통합 패턴(EIP)이란?

EIP는 시스템 간 메시지를 기반으로 데이터를 처리하고 통합하는 방법을 정의하는 패턴들의 집합입니다. 복잡한 비즈니스 통합 로직을 재사용 가능한 설계 패턴을 사용하여 효율적으로 구축하도록 돕습니다.

 

EIP는 다음과 같은 주요 개념에 기반합니다:

  • 메시지: 시스템 간 데이터를 전달하는 단위.
  • 메시지 채널: 메시지가 이동하는 경로.
  • 엔드포인트: 메시지를 처리하거나 변환하는 구성 요소.
  • 라우팅과 필터링: 메시지 흐름을 제어하는 패턴.

2. Spring Integration에서 제공하는 주요 EIP 패턴

 Spring Integration은 EIP 패턴을 기반으로 시스템 간 메시지 기반 통합을 간소화하고 일관된 방식으로 처리할 수 있도록 돕습니다. 이러한 패턴들은 실제 통합 시나리오에서 자주 발생하는 문제를 해결하는 데 매우 유용하며, 개발자의 생산성과 시스템의 유지보수성을 크게 향상시킵니다.

 Spring Integration은 EIP의 핵심 패턴을 프레임워크에 통합하여 다음과 같은 기능을 제공합니다.

1) 메시지 채널(Message Channel)

메시지 채널은 메시지가 흐르는 경로입니다. 채널을 통해 메시지를 송신자에서 수신자로 전달합니다.

 

예시: DirectChannel 설정

@Bean
public MessageChannel inputChannel() {
    return new DirectChannel();
}

@Bean
public MessageChannel outputChannel() {
    return new DirectChannel();
}

 

2) 메시지 라우터(Message Router)

라우터는 메시지의 내용을 기반으로 다른 채널로 메시지를 전달하는 역할을 합니다.

 

예시: 조건에 따라 메시지 라우팅

@Bean
public IntegrationFlow routerFlow() {
    return IntegrationFlows.from("inputChannel")
            .<String, Boolean>route(payload -> payload.contains("important"),
                mapping -> mapping
                        .subFlowMapping(true, subflow -> subflow.handle(m -> System.out.println("Important: " + m)))
                        .subFlowMapping(false, subflow -> subflow.handle(m -> System.out.println("Normal: " + m))))
            .get();
}
  • 설명:
    • 메시지의 내용이 important를 포함하면 하나의 흐름으로, 그렇지 않으면 다른 흐름으로 라우팅됩니다.

3) 메시지 필터(Message Filter)

필터는 특정 조건에 맞지 않는 메시지를 차단하는 역할을 합니다.

 

예시: 필터링 패턴

@Bean
public IntegrationFlow filterFlow() {
    return IntegrationFlows.from("inputChannel")
            .filter((String payload) -> payload.startsWith("Spring")) // 조건에 맞는 메시지만 통과
            .handle(message -> System.out.println("Filtered Message: " + message.getPayload()))
            .get();
}
  • 설명:
    • 메시지가 "Spring"으로 시작할 때만 다음 단계로 전달됩니다.

4) 메시지 트랜스포머(Message Transformer)

트랜스포머는 메시지를 다른 형식으로 변환하는 역할을 합니다.

 

예시: 메시지 변환

@Bean
public IntegrationFlow transformerFlow() {
    return IntegrationFlows.from("inputChannel")
            .transform(String::toUpperCase) // 메시지를 대문자로 변환
            .handle(message -> System.out.println("Transformed Message: " + message.getPayload()))
            .get();
}
  • 설명:
    • 메시지의 내용을 대문자로 변환합니다.

5) 메시지 스플리터(Message Splitter)와 어그리게이터(Aggregator)

  • 스플리터: 하나의 메시지를 여러 개의 메시지로 분할합니다.
  • 어그리게이터: 여러 메시지를 다시 하나로 결합합니다.

예시: 메시지 스플리터와 어그리게이터

@Bean
public IntegrationFlow splitAggregateFlow() {
    return IntegrationFlows.from("inputChannel")
            .split() // 메시지 분할
            .transform(String::toUpperCase)
            .aggregate() // 메시지 결합
            .handle(message -> System.out.println("Aggregated Message: " + message.getPayload()))
            .get();
}
  • 설명:
    • 메시지를 분할한 후 각 메시지를 변환하고, 다시 결합하여 최종 결과를 처리합니다.

6) 서비스 액티베이터(Service Activator)

서비스 액티베이터는 메시지를 POJO 메서드로 전달하여 비즈니스 로직을 처리하는 역할을 합니다.

 

예시: Service Activator

@Bean
@ServiceActivator(inputChannel = "inputChannel")
public MessageHandler serviceActivator() {
    return message -> System.out.println("Service Activator: " + message.getPayload());
}

3. Spring Integration에서 EIP의 이점

  1. 재사용성
    표준화된 패턴을 사용하므로 코드 재사용이 용이합니다.
  2. 일관성
    엔터프라이즈 애플리케이션에서 발생하는 다양한 통합 문제를 일관된 방식으로 해결할 수 있습니다.
  3. 유연한 확장성
    새로운 패턴이나 기능을 추가하기 쉽고 확장성이 뛰어납니다.
  4. 가독성 향상
    EIP 기반으로 메시지 흐름을 설계하면 로직이 명확해지고 시스템의 가독성이 향상됩니다.

728x90
Comments