티스토리 뷰
사내에서 배치 작업을 스케줄링이나 HTTP API가 아닌 DB polling
로 기동하길 원하는 요구사항이 있어, 어떤 방식으로 구현하면 좋을지 찾다보니 Spring Integration
를 활용할 수 있을 것 같아 학습 겸 Baeldung 자료를 기반으로 소개 및 튜토리얼 자료를 작성해보려 한다.
그리고 Spring Integration
에 대한 기본적인 개념을 학습한 후에 이를 활용한 DB polling
에 대한 포스팅을 해야겠다.
1. 소개
이 글에서는 실용적인 예제를 통해 Spring Integration
의 핵심 개념을 소개한다. Spring Integration
은 엔터프라이즈 아키텍처에서 시스템들과 프로세스들 간의 상호 연결성을 크게 향상시킬 수 있는 많은 요소들을 제공한다.
그리고 널리 사용되는 디자인 패턴을 구현하여, 개발자가 반복된 작업을 하지 않도록 지원한다.
엔터프라이즈 어플리케이션에서 Spring Integration
이 활용될 수 있는 부분을 알아보고 다른 대안보다 선호되는 이유도 살펴볼 것이다. 그리고 Spring Integration
기반의 어플리케이션을 쉽게 개발하기 위한 툴들도 알아볼 것이다.
2. 준비
gradle 의존성
dependencies {
implementation 'org.springframework.integration:spring-integration-core:5.5.16'
implementation 'org.springframework.integration:spring-integration-file:5.5.16'
}
3. 메세지 패턴
Spring Integration
의 기본 패턴 중 하나는 메세징
이다. 이 패턴은 미리 정의된 채널을 통해 기존의 시스템이나 프로세스로부터 하나 이상의 시스템들이나 프로세스들로 보내지는 메세지(데이터)를 중심으로 이루어진다.
역사적으로, 이 패턴은 다음과 같은 방식으로 서로 다른 시스템들을 유연하게 통합했다.
- 통합과 관련된 시스템을 대부분 분리한다.
- 통합과 관련된 시스템이 서로의 프로토콜, 형식 그리고 기타 구현의 세부 사항에 대해 의존하지 않도록 한다.
- 통합과 관련된 구성 요소의 개발과 재사용을 권장한다.
4. 메세지 패턴 예시
지정된 디렉토리에서 다른 디렉토리로 json 파일을 복사하는 기본적인 예제를 살펴볼 것이다.
4-1. @Configuration
아래 코드는 Service Activator
, Integration Channel
및 Inbound Channel Adapter
를 구성한다. 각 구성 요소들에 대해서는 뒤에서 살펴볼 것이다.
@Configuration
@EnableIntegration
public class BasicIntegrationConfig {
private final String srcDirectory;
private final String destDirectory;
private final String filePattern;
public BasicIntegrationConfig() {
srcDirectory = BasicIntegrationConfig.class.getClassLoader().getResource("source").getPath();
destDirectory = BasicIntegrationConfig.class.getClassLoader().getResource("destination").getPath();
filePattern = "*.json";
}
@Bean
public MessageChannel fileChannel() {
return new DirectChannel();
}
@Bean
@InboundChannelAdapter(value = "fileChannel", poller = @Poller(fixedDelay = "1000"))
public MessageSource<File> fileReadingMessageSource() {
FileReadingMessageSource sourceReader = new FileReadingMessageSource();
sourceReader.setDirectory(new File(srcDirectory));
sourceReader.setFilter(new SimplePatternFileListFilter(filePattern));
return sourceReader;
}
@Bean
@ServiceActivator(inputChannel = "fileChannel")
public MessageHandler fileWritingMessageHandler() {
FileWritingMessageHandler handler = new FileWritingMessageHandler(
new File(destDirectory)
);
handler.setFileExistsMode(FileExistsMode.REPLACE);
handler.setExpectReply(false);
return handler;
}
}
4-2. 메인 메소드
public static void main(String[] args) {
AbstractApplicationContext context = new AnnotationConfigApplicationContext(BasicIntegrationConfig.class);
context.registerShutdownHook();
Scanner scanner = new Scanner(System.in);
while(true) {
System.out.print("exit ? [y/n] ");
String input = scanner.nextLine().trim();
if(input.equalsIgnoreCase("y")) {
context.close();
scanner.close();
break;
}
}
System.exit(0);
}
5. Spring Integration 구성 요소
5-1. Message
The org.springframework.integration.Message
인터페이스는 Spring Integration
컨텍스트 내에서 데이터의 전송 단위인 스프링 메세지를 정의한다.
이는 두 가지 주요 요소에 대한 accessor(getter)
를 제공한다.
MessageHeaders
: 기본적으로org.springframework.integration.MessageHeaders
클래스에 정의된 메타 데이터를 송신하는데 사용할 수 있는 key-value 컨테이너메세지 페이로드
: 전송할 가치가 있는 실제 데이터 → 위의 예시에서는 json
public interface Message<T> {
T getPayload();
MessageHeaders getHeaders();
}
5-2. Channel
Spring Integration
에서 Channel
은 통합 아키텍처에서 기본 채널이다. 메세지가 한 시스템에서 다른 시스템으로 전달되는 파이프 역할을 수행한다. 다시 말해, 통합 시스템 또는 프로세스가 다른 시스템으로 메세지를 송수신할 때 사용되는 파이프로 생각하면 된다.
Spring Integration
에서 Channel
은 필요에 따라 다양한 형태로 제공된다. 대부분 커스텀 없이 설정 및 사용이 가능하지만, 상황에 따라 커스터마이징도 가능하다.
P2P(Point-to-Point) Channel
P2P(Point-to-Point) Channel
은 시스템과 구성 요소들 간에 일대일 통신을 위해 사용된다. 한 구성 요소는 다른 구성 요소가 확인할 수 있도록 메세지를 채널에 발행(publish)한다. 각 채널의 끝에는 하나의 구성 요소만 있을 수 있다.
채널 구성은 DirectChannel
인스턴스를 반환하는 것만큼 간단하다. 예제에서는 각각의 getter 메서드 이름으로 식별되는 세 개의 채널을 정의했다.
@Bean
public MessageChannel fileChannel1() {
return new DirectChannel();
}
@Bean
public MessageChannel fileChannel2() {
return new DirectChannel();
}
@Bean
public MessageChannel fileChannel3() {
return new DirectChannel();
}
Publish-Subscribe(Pub-Sub) Channel
Publish-Subscribe(Pub-Sub) Channel
은 시스템과 구성요소들 간의 일대다 통신을 설정하는데 사용된다. 이렇게 하면 이전에 생성한 3개의 direct channel
에 모두 전달 가능하다.
따라서 다음과 같이 필요에 따라 P2P channel
을 pub-sub channel
바꿀 수 있다.
@Bean
public MessageChannel pubSubFileChannel() {
return new PublishSubscribeChannel();
}
@Bean
@InboundChannelAdapter(value = "pubSubFileChannel", poller = @Poller(fixedDelay = "1000"))
public MessageSource<File> fileReadingMessageSource() {
FileReadingMessageSource sourceReader = new FileReadingMessageSource();
sourceReader.setDirectory(new File(srcDirectory));
sourceReader.setFilter(new SimplePatternFileListFilter(filePattern));
return sourceReader;
}
예제에서는 pub-sub channel
에 발행(publish)하기 위해 inbound channel adapter
를 변환했다. 이렇게하면 우리는 원본 디렉토리에서 읽은 파일을 여러 디렉토리로 보낼 수 있다.
5-3. Bridge
Spring Integration
의 bridge
는 직접 연결할 수 없는 두 개의 메세지 채널
이나 어댑터
를 연결하는데 사용된다. 예제에서는 bridge를 사용하여 pub-sub channel
을 3개의 다른 P2P channel
에 연결 할 수 있다. → P2P channel
과 pub-sub channel
은 직접 연결이 불가능하다.
@Bean
@BridgeFrom(value = "pubSubFileChannel")
public MessageChannel fileChannel1() {
return new DirectChannel();
}
@Bean
@BridgeFrom(value = "pubSubFileChannel")
public MessageChannel fileChannel2() {
return new DirectChannel();
}
@Bean
@BridgeFrom(value = "pubSubFileChannel")
public MessageChannel fileChannel3() {
return new DirectChannel();
}
위의 bean 구성은 pubSubFileChannel
을 3개의 P2P channel
에 연결한다. @BridgeFrom
어노테이션은 bridge
를 정의하며 pub-sub channel
을 구독(subscribe)해야 하는 모든 채널에 적용 가능하다.
위의 코드는 "pubSubFileChannel에서 fileChannel1, fileChannel2, fileChannel3으로 bridge
를 생성하여 pubSubFileChannel의 메세지가 세 channel
모두에 동시에 전달될 수 있도록 한다."로 볼 수 있다.
5-4. Service Activator
Service Activator
는 주어진 메소드에서 @ServiceActivator
어노테이션을 정의하는 POJO
다. 이를 통해 Inbound channel
에서 메세지를 수신할 때 POJO의 메서드를 실행할 수 있고, 외부 channel
에 메세지를 보낼 수 있다.
스프링 공식 문서에서는 스프링의 관리를 받는 객체가 서비스 역할을 할 수 있도록 Input channel
에 연결하기 위한 엔드포인트 유형이라고 설명하고 있다.
예제에서 Service Activator
는 input channel
로부터 파일을 받아 디렉토리에 write를 한다.
* 개인적으로 Baeldung과 스프링 공식 문서의 설명만으로는 직관적으로 개념을 받아들이기 어려웠는데, 해당 예제와 다른 사례들을 살펴보다보니 어떤식으로 활용할 수 있는지 이해할 수 있었다. 당연한 얘기지만, 짧은 설명만으로 납득이 되지 않는 개념은 예제를 찾아보는 것이 확실히 도움이 되는 것 같다.
5-5. Adapter
Adapter
는 시스템 또는 데이터 소스에 plug-in
할 수 있는 엔터프라이즈 통합 패턴 기반의 구성 요소이다. 말 그대로 우리가 알고 있는 벽에 있는 소켓이나 전자 장치에 연결하는 어댑터라고 볼 수 있다.
이는 데이터베이스, FTP 서버, 메세징 시스템(ex. JMS, AMQP), 소셜 네트워크(ex. Twitter)와 같은 블랙박스 시스템에 재사용 가능한 연결을 할 수 있다. 이런 시스템에 연결해야 한다는 것은 Adapter
는 이식과 재사용이 용이해야 한다는 의미이다.
Adapter
는 크게 인바운드와 아웃바운드로 나뉜다. 예제를 기준으로 이들을 살펴보자.
Inbound Adapter
우선 Inbound Adapter
는 이전에 살펴보았듯이, 외부 시스템으로부터 메세지를 가져오는데 사용된다. 예제에서는 파일시스템 디렉토리가 외부 시스템이 되는데, 다음과 같이 구성되었다.
@InboundChannelAdapter
어노테이션:Adapter
가 메세지(예제에서는 json)를 보낼Channel
과 어댑터가 지정된 간격으로 디렉토리를 폴링하는데 활용이 되는Poller
를 구성FileReadingMessageSource
: 파일시스템 폴링과 관련된 처리를 구현한Spring Integration
클래스
Outbound Adapter
Outbound Adapter
는 메세지를 외부로 보내는데 사용된다. Spring Integration은 다양한 상황을 처리하기 위해 쉽게 사용할 수 있는 다양한 Adapter
들을 지원한다.
6. 결론
Spring Integration
에 대한 기본적인 활용법을 살펴보았다.
Spring Integration
코드는 JavaSE 내에서 독립형 프로젝트로 배포할 수 있을 뿐만 아니라 Jakarta EE 환경에 포함되어서 배포도 가능하다. EAI 중심 제품들이나 ESB(Enterprise Service Bus)
패턴들과 직접적으로 경쟁하진 않지만, ESB
가 해결할 수 있는 문제들을 가벼운 방식으로 대체할 수 있는 방법이 될 것이다.
참고 자료 🙇♂️
'스프링' 카테고리의 다른 글
[Spring] 어플리케이션 종료 시 들어온 요청은 처리하고 마치기(graceful shutdown) (0) | 2023.11.05 |
---|---|
[Spring] 어플리케이션을 종료할 때 특정 작업 수행하기(feat. ApplicationListener) (0) | 2023.11.05 |
[Spring] @SpringBootApplication가 없는 상황에서 @SpringBootTest 수행하기 (0) | 2023.08.05 |
[Spring] 초기 데이터 로딩(data.sql, schema.sql) (0) | 2023.06.02 |
[Spring] 주기적인 작업 구현하기(feat. Scheduling Tasks) (0) | 2023.04.01 |