chapter9 incheol - JAVA-JIKIMI/SPRING-IN-ACTION-5 GitHub Wiki

์Šคํ”„๋ง ํ†ตํ•ฉ์€ <Enterprise Integration Patterns 2003>์—์„œ ๋ณด์—ฌ์ค€ ๋Œ€๋ถ€๋ถ„์˜ ํ†ตํ•ฉ ํŒจํ„ด์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ๊ตฌํ˜„ํ•œ ๊ฒƒ์ด๋‹ค. ๊ฐ ํ†ตํ•ฉ ํŒจํ„ด์€ ํ•˜๋‚˜์˜ ์ปดํฌ๋„ŒํŠธ๋กœ ๊ตฌํ˜„๋˜๋ฉฐ, ์ด๊ฒƒ์„ ํ†ตํ•ด์„œ ํŒŒ์ดํ”„๋ผ์ธ์œผ๋กœ ๋ฉ”์‹œ์ง€๊ฐ€ ๋ฐ์ดํ„ฐ๋ฅผ ์šด๋ฐ˜ํ•œ๋‹ค. ์Šคํ”„๋ง ๊ตฌ์„ฑ์„ ์‚ฌ์šฉํ•˜๋ฉด ๋ฐ์ดํ„ฐ๊ฐ€ ์ด๋™ํ•˜๋Š” ํŒŒ์ดํ”„๋ผ์ธ์œผ๋กœ ์ด๋Ÿฐ ์ปดํฌ๋„ŒํŠธ๋“ค์„ ์กฐ๋ฆฝํ•  ์ˆ˜ ์žˆ๋‹ค.

๊ฐ„๋‹จํ•œ ํ†ตํ•ฉ ํ”Œ๋กœ์šฐ ์„ ์–ธํ•˜๊ธฐ

๋ฉ”์‹œ์ง€ ๊ฒŒ์ดํŠธ์›จ์ด ์ƒ์„ฑ

์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ํ†ตํ•ฉํ•  ์ˆ˜ ์žˆ๋Š” ๊ทธ๋Ÿฐ ๋ฆฌ์†Œ์Šค ์ค‘ ํ•˜๋‚˜๊ฐ€ ํŒŒ์ผ ์‹œ์Šคํ…œ์ด๋‹ค. ์ด์— ๋”ฐ๋ผ ์Šคํ”„๋ง ํ†ตํ•ฉ์˜ ๋งŽ์€ ์ปดํฌ๋„ŒํŠธ ์ค‘์— ํŒŒ์ผ์„ ์ฝ๊ฑฐ๋‚˜ ์“ฐ๋Š” ์ฑ„๋„ ์–ด๋Œ‘ํ„ฐ๊ฐ€ ์žˆ๋‹ค.

<dependency>
	<groupId>org.springframework.boot</groupId> 
	<artifactId>spring-boot-starter-integration</artifactId>
</dependency>

<dependency> 
	<groupId>org.springframework.integration</groupId> 
	<artifactId>spring-integration-file</artifactId>
</dependency>

์ฒซ ๋ฒˆ์งธ ์˜์กด์„ฑ์€ ์Šคํ”„๋ง ํ†ตํ•ฉ์˜ ์Šคํ”„๋ง ๋ถ€ํŠธ ์Šคํƒ€ํ„ฐ์ด๋‹ค. ํ†ตํ•ฉํ•˜๋ ค๋Š” ํ”Œ๋กœ์šฐ์™€ ๋ฌด๊ด€ํ•˜๊ฒŒ ์ด ์˜์กด์„ฑ์€ ์Šคํ”„๋ง ํ†ตํ•ฉ ํ”Œ๋กœ์šฐ์˜ ๊ฐœ๋ฐœ ์‹œ์— ๋ฐ˜๋“œ์‹œ ์ถ”๊ฐ€ํ•ด์•ผ ํ•œ๋‹ค.

๋‘ ๋ฒˆ์งธ ์˜์กด์„ฑ์€ ์Šคํ”„๋ง ํ†ตํ•ฉ์˜ ํŒŒ์ผ ์—”๋“œํฌ์ธํŠธ(endpoint) ๋ชจ๋“ˆ์ด๋‹ค. ์ด ๋ชจ๋“ˆ์€ ์™ธ๋ถ€ ์‹œ์Šคํ…œ ํ†ตํ•ฉ์— ์‚ฌ์šฉ๋˜๋Š” 24๊ฐœ ์ด์ƒ์˜ ์—”๋“œํฌ์ธํŠธ ๋ชจ๋“ˆ ์ค‘ ํ•˜๋‚˜๋‹ค.

import org.springframework.integration.file.FileHeaders;
import org.springframework.messaging.handler.annotation.Header;

@MessagingGateway(defaultRequestChannel="textInChannel")
public interface FileWriterGateway {
	void writeToFile(
		@Header(FileHeaders.FILENAME) String filename, 
		String data
	);
}
  • @MessagingGateway : FileWriterGateway ์ธํ„ฐํŽ˜์ด์Šค์˜ ๊ตฌํ˜„์ฒด(ํด๋ž˜์Šค)๋ฅผ ๋Ÿฐํƒ€์ž„ ์‹œ์— ์ƒ์„ฑํ•˜๋ผ๊ณ  ์Šคํ”„๋ง ํ†ตํ•ฉ์— ์•Œ๋ ค์ค€๋‹ค.
  • defaultRequestChannel : ํ•ด๋‹น ์ธํ„ฐํŽ˜์ด์Šค์˜ ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ๋กœ ์ƒ์„ฑ๋œ ๋ฉ”์‹œ์ง€๊ฐ€ ์ด ์†์„ฑ์— ์ง€์ •๋œ ๋ฉ”์‹œ์ง€ ์ฑ„๋„๋กœ ์ „์†ก๋œ๋‹ค๋Š” ๊ฒƒ์„ ๋‚˜ํƒ€๋‚ธ๋‹ค.
  • writeToFile()์˜ ํ˜ธ์ถœ๋กœ ์ƒ๊ธด ๋ฉ”์‹œ์ง€๊ฐ€ textInChannel์ด๋ผ๋Š” ์ด๋ฆ„์˜ ์ฑ„๋„๋กœ ์ „์†ก๋œ๋‹ค.
  • @Header : filename์— ์ „๋‹ฌ๋˜๋Š” ๊ฐ’์ด ๋ฉ”์‹œ์ง€ ํŽ˜์ด๋กœ๋“œ๊ฐ€ ์•„๋‹Œ ๋ฉ”์‹œ์ง€ ํ—ค๋”์— ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ๋‚˜ํƒ€๋‚ธ๋‹ค.

ํ†ตํ•ฉ ํ”Œ๋กœ์šฐ ๊ตฌ์„ฑ

Java๋กœ ํ†ตํ•ฉ ํ”Œ๋กœ์šฐ ๊ตฌ์„ฑํ•˜๊ธฐ

@Configuration
public class FileWriterIntegrationConfig {

  @Bean
  @Transformer(inputChannel="textInChannel", // Declares a transformer
      outputChannel="fileWriterChannel")
  public GenericTransformer<String, String> upperCaseTransformer() {
  	return text -> text.toUpperCase(); }

  @Bean
  @ServiceActivator(inputChannel="fileWriterChannel")
  public FileWritingMessageHandler fileWriter() { // Declares a file writer
    FileWritingMessageHandler handler =
        new FileWritingMessageHandler(new File("/tmp/sia5/files"));
    
		handler.setExpectReply(false); 
		handler.setFileExistsMode(FileExistsMode.APPEND); 
		handler.setAppendNewLine(true);
    
		return handler;
  }
}
  • ์ž๋ฐ” ๊ตฌ์„ฑ์—์„œ๋Š” ๋‘ ๊ฐœ์˜ ๋นˆ์„ ์ •์˜ํ•œ๋‹ค.
    • ๋ณ€ํ™˜๊ธฐ(GenericTransformer) : ์ „๋‹ฌ ๋ฐ›์€ ๋ฌธ์ž๋ฅผ ๋Œ€๋ฌธ์ž๋กœ ๋ณ€ํ™˜ํ•œ๋‹ค.
    • ํŒŒ์ผ-์“ฐ๊ธฐ ๋ฉ”์‹œ์ง€ ํ•ธ๋“ค๋Ÿฌ(FileWritingMessageHandler)
      • fileWriterChannel๋กœ๋ถ€ํ„ฐ ๋ฉ”์‹œ์ง€๋ฅผ ๋ฐ›์•„์„œ FileWritingMessageHandler์˜ ์ธ์Šคํ„ด์Šค๋กœ ์ •์˜๋œ ์„œ๋น„์Šค์— ๋„˜๊ฒจ์ค€๋‹ค.
      • handler.setExpectReply(false)๋Š” ์„œ๋น„์Šค์—์„œ ์‘๋‹ต ์ฑ„๋„(ํ”Œ๋กœ์šฐ์˜ ์—…์ŠคํŠธ๋ฆผ ์ปดํฌ๋„ŒํŠธ๋กœ ๊ฐ’์ด ๋ฐ˜ํ™˜๋  ์ˆ˜ ์žˆ๋Š” ์ฑ„๋„)์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š์Œ์„ ๋‚˜ํƒ€๋‚ธ๋‹ค. ํ•ด๋‹น ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜์ง€ ์•Š์•˜๋‹ค๋ฉด ํ†ตํ•ฉ ํ”Œ๋กœ์šฐ๊ฐ€ ์ •์ƒ์ ์œผ๋กœ ์ž‘๋™ํ•˜๋”๋ผ๋„ ์‘๋‹ต ์ฑ„๋„์ด ๊ตฌ์„ฑ๋˜์ง€ ์•Š์•˜๋‹ค๋Š” ๋กœ๊ทธ ๋ฉ”์‹œ์ง€๋“ค์ด ๋‚˜ํƒ€๋‚œ๋‹ค.

์Šคํ”„๋ง ํ†ตํ•ฉ์˜ DSL ๊ตฌ์„ฑ ์‚ฌ์šฉํ•˜๊ธฐ

ํ†ตํ•ฉ ํ”Œ๋กœ์šฐ์˜ ๊ฐ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ณ„๋„์˜ ๋นˆ์œผ๋กœ ์„ ์–ธํ•˜์ง€ ์•Š๊ณ  ์ „์ฒด ํ”Œ๋กœ์šฐ๋ฅผ ํ•˜๋‚˜์˜ ๋นˆ์œผ๋กœ ์„ ์–ธํ•œ๋‹ค.

@Configuration
public class FileWriterIntegrationConfig {

    @Bean
    public IntegrationFlow fileWriterFlow() {
        return IntegrationFlows.from(MessageChannels.direct("textInChannel"))
            .<String, String>transform(t -> t.toUpperCase())
            .handle(Files
                .outboundAdapter(new File("/tmp/sia5/files"))
                .fileExistsMode(FileExistsMode.APPEND)
                .appendNewLine(true))
            .get();
    }
}
  • IntegrationFlows ํด๋ž˜์Šค๋Š” ํ”Œ๋กœ์šฐ๋ฅผ ์„ ์–ธํ•  ์ˆ˜ ์žˆ๋Š” ๋นŒ๋” API๋ฅผ ์‹œ์ž‘์‹œํ‚จ๋‹ค.
  • channel() ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ํ•ด๋‹น ์ฑ„๋„์„ ์ด๋ฆ„์œผ๋กœ ์ฐธ์กฐํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ์ฝ”๋“œ๋Š” ๋งŽ์ด ์ค„์—ˆ์ง€๋งŒ ์ฝ”๋“œ์˜ ๊ฐ€๋…์„ฑ์„ ๋†’์ด๊ธฐ ์œ„ํ•ด ๋“ค์—ฌ์“ฐ๊ธฐ๋ฅผ ์ž˜ ํ•ด์•ผ ํ•œ๋‹ค.

์Šคํ”„๋ง ํ†ตํ•ฉ์˜ ์ปดํฌ๋„ŒํŠธ ์‚ดํŽด๋ณด๊ธฐ

์Šคํ”„๋ง ํ†ตํ•ฉ์€ ๋‹ค์ˆ˜์˜ ํ†ตํ•ฉ ์‹œ๋‚˜๋ฆฌ์˜ค๋ฅผ ๊ฐ–๋Š” ๋งŽ์€ ์˜์—ญ์„ ํฌํ•จํ•œ๋‹ค. ๋”ฐ๋ผ์„œ ๊ทธ ๋ชจ๋“  ๊ฒƒ์„ ํ•˜๋‚˜์˜ ์ฑ•ํ„ฐ์— ํฌํ•จ์‹œํ‚ค๋ ค๊ณ  ํ•˜๋Š” ๊ฒƒ์€ ๋งˆ์น˜ ์ฝ”๋ผ๋ฆฌ๋ฅผ ๋ด‰ํˆฌ์— ๋งž์ถฐ ๋„ฃ์œผ๋ ค๊ณ  ํ•˜๋Š” ๊ฒƒ๊ณผ ๊ฐ™๋‹ค. ํ†ตํ•ฉ ํ”Œ๋กœ์šฐ๋Š” ํ•˜๋‚˜ ์ด์ƒ์˜ ์ปดํฌ๋„ŒํŠธ๋กœ ๊ตฌ์„ฑ๋˜๋ฉฐ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

  • ์ฑ„๋„(Channel) : ํ•œ ์š”์†Œ๋กœ๋ถ€ํ„ฐ ๋‹ค๋ฅธ ์š”์†Œ๋กœ ๋ฉ”์‹œ์ง€๋ฅผ ์ „๋‹ฌํ•œ๋‹ค.
  • ํ•„ํ„ฐ(Filter) : ์กฐ๊ฑด์— ๋งž๋Š” ๋ฉ”์‹œ์ง€๊ฐ€ ํ”Œ๋กœ์šฐ๋ฅผ ํ†ต๊ณผํ•˜๊ฒŒ ํ•ด์ค€๋‹ค.
  • ๋ณ€ํ™˜๊ธฐ(Transaformer) : ๋ฉ”์‹œ์ง€ ๊ฐ’์„ ๋ณ€๊ฒฝํ•˜๊ฑฐ๋‚˜ ๋ฉ”์‹œ์ง€ ํŽ˜์ด๋กœ๋“œ์˜ ํƒ€์ž…์„ ๋‹ค๋ฅธ ํƒ€์ž…์œผ๋กœ ๋ณ€ํ™˜ํ•œ๋‹ค.
  • ๋ผ์šฐํ„ฐ(Router) : ์—ฌ๋Ÿฌ ์ฑ„๋„ ์ค‘ ํ•˜๋‚˜๋กœ ๋ฉ”์‹œ์ง€๋ฅผ ์ „๋‹ฌํ•˜๋ฉฐ, ๋Œ€๊ฐœ ๋ฉ”์‹œ์ง€ ํ—ค๋”๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•œ๋‹ค.
  • ๋ถ„๋ฐฐ๊ธฐ(Splitter) : ๋“ค์–ด์˜ค๋Š” ๋ฉ”์‹œ์ง€๋ฅผ ๋‘ ๊ฐœ ์ด์ƒ์˜ ๋ฉ”์‹œ์ง€๋กœ ๋ถ„ํ• ํ•˜๋ฉฐ, ๋ถ„ํ• ๋œ ๊ฐ ๋ฉ”์‹œ์ง€๋Š” ๋‹ค๋ฅธ ์ฑ„๋„๋กœ ์ „์†ก๋œ๋‹ค.
  • ์ง‘์ ๊ธฐ(Aggregator) : ๋ถ„๋ฐฐ๊ธฐ์™€ ์ƒ๋ฐ˜๋œ ๊ฒƒ์œผ๋กœ ๋ณ„๊ฐœ์˜ ์ฑ„๋„๋กœ๋ถ€ํ„ฐ ์ „๋‹ฌ๋˜๋Š” ๋‹ค์ˆ˜์˜ ๋ฉ”์‹œ์ง€๋ฅผ ํ•˜๋‚˜์˜ ๋ฉ”์‹œ์ง€๋กœ ๊ฒฐํ•ฉํ•œ๋‹ค.
  • ์„œ๋น„์Šค ์•กํ‹ฐ๋ฒ ์ดํ„ฐ(Service activator) : ๋ฉ”์‹œ์ง€๋ฅผ ์ฒ˜๋ฆฌํ•˜๋„๋ก ์ž๋ฐ” ๋ฉ”์„œ๋“œ์— ๋ฉ”์‹œ์ง€๋ฅผ ๋„˜๊ฒจ์ค€ ํ›„ ๋ฉ”์„œ๋“œ์˜ ๋ฐ˜ํ™˜๊ฐ’์„ ์ถœ๋ ฅ ์ฑ„๋„๋กœ ์ „์†กํ•œ๋‹ค.
  • ์ฑ„๋„ ์–ด๋Œ‘ํ„ฐ(Channel adapter) : ์™ธ๋ถ€ ์‹œ์Šคํ…œ์— ์ฑ„๋„์„ ์—ฐ๊ฒฐํ•œ๋‹ค. ์™ธ๋ถ€ ์‹œ์Šคํ…œ์œผ๋กœ๋ถ€ํ„ฐ ์ž…๋ ฅ์„ ๋ฐ›๊ฑฐ๋‚˜ ์“ธ ์ˆ˜ ์žˆ๋‹ค.
  • ๊ฒŒ์ดํŠธ์›จ์ด(Gateway) : ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ํ†ตํ•ด ํ†ตํ•ฉ ํ”Œ๋กœ์šฐ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์ „๋‹ฌํ•œ๋‹ค.

๋ฉ”์‹œ์ง€ ์ฑ„๋„

๋ฉ”์‹œ์ง€ ์ฑ„๋„์€ ํ†ตํ•ฉ ํŒŒ๋ฆฌํ”„๋ผ์ธ์„ ํ†ตํ•ด์„œ ๋ฉ”์‹œ์ง€๊ฐ€ ์ด๋™ํ•˜๋Š” ์ˆ˜๋‹จ์ด๋‹ค. ์ฆ‰, ์ฑ„๋„์€ ์Šคํ”„๋ง ํ†ตํ•ฉ์˜ ๋‹ค๋ฅธ ๋ถ€๋ถ„์„ ์—ฐ๊ฒฐํ•˜๋Š” ํ†ต๋กœ๋‹ค. ์Šคํ”„๋ง ํ†ตํ•ฉ์€ ๋‹ค์Œ์„ ํฌํ•จํ•ด์„œ ์—ฌ๋Ÿฌ ์ฑ„๋„ ๊ตฌํ˜„์ฒด(ํด๋ž˜์Šค)๋ฅผ ์ œ๊ณตํ•œ๋‹ค.

  • PublishSubscribeChannel : ์ „์†ก๋˜๋Š” ๋ฉ”์‹œ์ง€๋Š” ํ•˜๋‚˜ ์ด์ƒ์˜ ์ปจ์Šˆ๋จธ๋กœ ์ „๋‹ฌ๋œ๋‹ค. ์ปจ์Šˆ๋จธ๊ฐ€ ์—ฌ๋Ÿฟ์ผ ๋•Œ๋Š” ๋ชจ๋“  ์ปจ์Šˆ๋จธ๊ฐ€ ํ•ด๋‹น ๋ฉ”์‹œ์ง€๋ฅผ ์ˆ˜์‹ ํ•œ๋‹ค.
  • QueueChannel : ์ „์†ก๋˜๋Š” ๋ฉ”์‹œ์ง€๋Š” FIFO ๋ฐฉ์‹์œผ๋กœ ์ปจ์Šˆ๋จธ๊ฐ€ ๊ฐ€์ ธ๊ฐˆ ๋•Œ๊นŒ์ง€ ํ์— ์ €์žฅ๋œ๋‹ค. ์ปจ์Šˆ๋จธ๊ฐ€ ์—ฌ๋Ÿฟ์ผ ๋•Œ๋Š” ๊ทธ์ค‘ ํ•˜๋‚˜์˜ ์ปจ์Šˆ๋จธ๋งŒ ํ•ด๋‹น ๋ฉ”์‹œ์ง€๋ฅผ ์ˆ˜์‹ ํ•œ๋‹ค.
  • PriorityChannel : QueueChannel๊ณผ ์œ ์‚ฌํ•˜์ง€๋งŒ, FIFO ๋ฐฉ์‹ ๋Œ€์‹  ๋ฉ”์‹œ์ง€์˜ priority ํ—ค๋”๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์ปจ์Šˆ๋จธ๊ฐ€ ๋ฉ”์‹œ์ง€๋ฅผ ๊ฐ€์ ธ๊ฐ„๋‹ค.
  • RendezvousChannel : QueueChannel๊ณผ ์œ ์‚ฌํ•˜์ง€๋งŒ, ์ „์†ก์ž์™€ ๋™์ผํ•œ ์Šค๋ ˆ๋“œ๋กœ ์‹คํ–‰๋˜๋Š” ์ปจ์Šˆ๋จธ๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ๋‹จ์ผ ์ปจ์Šˆ๋จธ์—๊ฒŒ ๋ฉ”์‹œ์ง€๋ฅผ ์ „์†กํ•œ๋‹ค. ์ด ์ฑ„๋„์€ ํŠธ๋žœ์žญ์…˜์„ ์ง€์›ํ•œ๋‹ค.
  • ExecutorChannel : DirectChannel๊ณผ ์œ ์‚ฌํ•˜์ง€๋งŒ, TaskExecutor๋ฅผ ํ†ตํ•ด์„œ ๋ฉ”์‹œ์ง€๊ฐ€ ์ „์†ก๋œ๋‹ค. (์ „์†ก์ž์™€ ๋‹ค๋ฅธ ์Šค๋ ˆ๋“œ์—์„œ ์ฒ˜๋ฆฌ๋œ๋‹ค) ์ด ์ฑ„๋„ ํƒ€์ž…์€ ํŠธ๋žœ์žญ์…˜์„ ์ง€์›ํ•˜์ง€ ์•Š๋Š”๋‹ค.
  • FluxMessageChannel : ํ”„๋กœ์ ํŠธ ๋ฆฌ์•กํ„ฐ(Product Reactor)์˜ ํ”Œ๋Ÿญ์Šค(Flux)๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•˜๋Š” ๋ฆฌ์•กํ‹ฐ๋ธŒ ์ŠคํŠธ๋ฆผ์ฆˆ ํผ๋ธ”๋ฆฌ์…”(Reactive Streams Publisher) ์ฑ„๋„์ด๋‹ค.
// option1. PublishSubscribeChannel
@Bean
public MessageChannel orderChannel() {
	return new PublishSubscribeChannel();
}

// option2. QueueChannel
@Bean
public MessageChannel orderChannel() {
	return new QueueChannel();
}

@Bean
public IntegrationFlow orderFlow() {
	return IntegrationFlows 
		...
		.channel("orderChannel")
		...
		.get();
}

ํ•„ํ„ฐ

ํ•„ํ„ฐ๋Š” ํ†ตํ•ฉ ํŒŒ์ดํ”„๋ผ์ธ์˜ ์ค‘๊ฐ„์— ์œ„์น˜ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ํ”Œ๋กœ์šฐ์˜ ์ „ ๋‹จ๊ณ„๋กœ๋ถ€ํ„ฐ ๋‹ค์Œ ๋‹จ๊ณ„๋กœ์˜ ๋ฉ”์‹œ์ง€ ์ „๋‹ฌ์„ ํ—ˆ์šฉ ๋˜๋Š” ๋ถˆํ—ˆํ•œ๋‹ค.

111

// option1. ์–ด๋…ธํ…Œ์ด์…˜ ๊ธฐ๋ฐ˜ ํ•„ํ„ฐ ์„ค์ •
@Filter(inputChannel="numberChannel",
        outputChannel="evenNumberChannel")
public boolean evenNumberFilter(Integer number) {
  return number % 2 == 0;
}

// option2. ์ž๋ฐ” DSL ๊ตฌ์„ฑ ํ•„ํ„ฐ ์„ค์ •
@Bean
public IntegrationFlow evenNumberFlow(AtomicInteger integerSource) {
    return IntegrationFlows 
        ...
        .<Integer>filter((p) -> p % 2 == 0) 
        ...
        .get();
}

๋ณ€ํ™˜๊ธฐ

๋ณ€ํ™˜๊ธฐ๋Š” ๋ฉ”์‹œ์ง€ ๊ฐ’์˜ ๋ณ€๊ฒฝ์ด๋‚˜ ํƒ€์ž…์„ ๋ณ€ํ™˜ํ•˜๋Š” ์ผ์„ ์ˆ˜ํ–‰ํ•œ๋‹ค.

222

// option1. ์–ด๋…ธํ…Œ์ด์…˜ ๊ธฐ๋ฐ˜ ๋ณ€ํ™˜๊ธฐ ์„ค์ •
@Bean
@Transformer(inputChannel="numberChannel",
             outputChannel="romanNumberChannel")
public GenericTransformer<Integer, String> romanNumTransformer() {
  return RomanNumbers::toRoman;
}

// option2. ์ž๋ฐ” DSL ๊ตฌ์„ฑ ๋ณ€ํ™˜๊ธฐ ์„ค์ •
@Bean
public IntegrationFlow transformerFlow() {
	return IntegrationFlows ...
				.transform(RomanNumbers::toRoman)
		    ...
				.get();
}

// option3. ์–ด๋…ธํ…Œ์ด์…˜ + ์ž๋ฐ” DSL ๊ตฌ์„ฑ ์กฐํ•ฉ
@Bean
public RomanNumberTransformer romanNumberTransformer() {
  return new RomanNumberTransformer();
}

@Bean
public IntegrationFlow transformerFlow(
                    RomanNumberTransformer romanNumberTransformer) {
  return IntegrationFlows
				...
				.transform(romanNumberTransformer) 
				...
				.get();
}

๋ผ์šฐํ„ฐ

๋ผ์šฐํ„ฐ๋Š” ์ „๋‹ฌ ์กฐ๊ฑด์„ ๊ธฐ๋ฐ˜์œผ๋กœ ํ†ตํ•ฉ ํ”Œ๋กœ์šฐ ๋‚ด๋ถ€๋ฅผ ๋ถ„๊ธฐ(์„œ๋กœ ๋‹ค๋ฅธ ์ฑ„๋„๋กœ ๋ฉ”์‹œ์ง€๋ฅผ ์ „๋‹ฌ)ํ•œ๋‹ค.

333

์˜ˆ๋ฅผ ๋“ค์–ด, ์ •์ˆ˜๊ฐ’์„ ์ „๋‹ฌํ•˜๋Š” numberChannel์ด๋ผ๋Š” ์ด๋ฆ„์˜ ์ฑ„๋„์ด ์žˆ๋‹ค๊ณ  ํ•˜์ž. ๊ทธ๋ฆฌ๊ณ  ๋ชจ๋“  ์ง์ˆ˜ ๋ฉ”์‹œ์ง€๋ฅผ evenChannel์ด๋ผ๋Š” ์ด๋ฆ„์˜ ์ฑ„๋„๋กœ ์ „๋‹ฌํ•˜๊ณ , ํ™€์ˆ˜ ๋ฉ”์‹œ์ง€๋Š” oddChannel์ด๋ผ๋Š” ์ด๋ฆ„์˜ ์ฑ„๋„๋กœ ์ „๋‹ฌํ•œ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด๋ณด์ž.

// option1. ์–ด๋…ธํ…Œ์ด์…˜ ๊ธฐ๋ฐ˜ ๋ผ์šฐํ„ฐ ์„ค์ •
@Bean
@Router(inputChannel="numberChannel")
public AbstractMessageRouter evenOddRouter() {
    return new AbstractMessageRouter() {
        @Override
        protected Collection<MessageChannel>
        determineTargetChannels(Message<?> message) { Integer number = (Integer) message.getPayload();
            if (number % 2 == 0) {
                return Collections.singleton(evenChannel()); }
            return Collections.singleton(oddChannel()); }
    }; 
}

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

์—ฌ๊ธฐ์„œ ์„ ์–ธํ•œ AbstractMessageRouter ๋นˆ์€ numberChannel์ด๋ผ๋Š” ์ด๋ฆ„์˜ ์ž…๋ ฅ ์ฑ„๋„๋กœ๋ถ€ํ„ฐ ๋ฉ”์‹œ์ง€๋ฅผ ๋ฐ›๋Š”๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ด ๋นˆ์„ ๊ตฌํ˜„ํ•œ ์ต๋ช…์˜ ๋‚ด๋ถ€ ํด๋ž˜์Šค์—์„œ๋Š” ๋ฉ”์‹œ์ง€ ํŽ˜์ด๋กœ๋“œ๋ฅผ ๊ฒ€์‚ฌํ•˜์—ฌ ์ง์ˆ˜์ผ ๋•Œ๋Š” evenChannel์ด๋ผ๋Š” ์ด๋ฆ„์˜ ์ฑ„๋„์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

// option2. ์ž๋ฐ” DSL ๊ตฌ์„ฑ ๋ผ์šฐํ„ฐ ์„ค์ •
@Bean
public IntegrationFlow numberRoutingFlow(AtomicInteger source) {
    return IntegrationFlows
        ...
        .<Integer, String>route(n -> n%2==0 ? "EVEN":"ODD", mapping -> mapping 
            .subFlowMapping("EVEN", 
                sf -> sf.<Integer, Integer>transform(n -> n * 10) 
                    .handle((i,h) -> { ... })
                )
            .subFlowMapping("ODD", sf -> sf 
                .transform(RomanNumbers::toRoman) 
                .handle((i,h) -> { ... })
             ) 
            .get();
}

๋ถ„๋ฐฐ๊ธฐ

๋•Œ๋กœ๋Š” ํ†ตํ•ฉ ํ”Œ๋กœ์šฐ์—์„œ ํ•˜๋‚˜์˜ ๋ฉ”์‹œ์ง€๋ฅผ ์—ฌ๋Ÿฌ ๊ฐœ๋กœ ๋ถ„ํ• ํ•˜์—ฌ ๋…๋ฆฝ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ์œ ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

444

๋ถ„๋ฐฐ๊ธฐ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์ค‘์š”ํ•œ ๋‘ ๊ฐ€์ง€ ๊ฒฝ์šฐ๊ฐ€ ์žˆ๋‹ค.

  • ๋ฉ”์‹œ์ง€ ํŽ˜์ด๋กœ๋“œ๊ฐ€ ๊ฐ™์€ ํƒ€์ž…์˜ ์ปฌ๋ ‰์…˜ ํ•ญ๋ชฉ๋“ค์„ ํฌํ•จํ•˜๋ฉฐ, ๊ฐ ๋ฉ”์‹œ์ง€ ํŽ˜์ด๋กœ๋“œ ๋ณ„๋กœ ์ฒ˜๋ฆฌํ•˜๊ณ ์ž ํ•  ๋•Œ๋‹ค
  • ์—ฐ๊ด€๋œ ์ •๋ณด๋ฅผ ํ•จ๊ป˜ ์ „๋‹ฌํ•˜๋Š” ํ•˜๋‚˜์˜ ๋ฉ”์‹œ์ง€ ํŽ˜์ด๋กœ๋“œ๋Š” ๋‘ ๊ฐœ ์ด์ƒ์˜ ์„œ๋กœ ๋‹ค๋ฅธ ํƒ€์ž… ๋ฉ”์‹œ์ง€๋กœ ๋ถ„ํ• ๋  ์ˆ˜ ์žˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด, ์ฃผ๋ฌธ ๋ฐ์ดํ„ฐ๋ฅผ ์ „๋‹ฌํ•˜๋Š” ๋ฉ”์‹œ์ง€๋Š” ๋Œ€๊ธˆ ์ฒญ๊ตฌ ์ •๋ณด์™€ ์ฃผ๋ฌธ ํ•ญ๋ชฉ ๋ฆฌ์ŠคํŠธ์˜ ๋‘ ๊ฐ€์ง€ ๋ฉ”์‹œ์ง€๋กœ ๋ถ„ํ• ํ•  ์ˆ˜ ์žˆ๋‹ค.

public class OrderSplitter {
    public Collection<Object> splitOrderIntoParts(PurchaseOrder po) {
				ArrayList<Object> parts = new ArrayList<>(); 
				parts.add(po.getBillingInfo()); 
				parts.add(po.getLineItems());
				
				return parts;
		}
}

๊ทธ ๋‹ค์Œ์— @Splitter ์• ๋…ธํ…Œ์ด์…˜์„ ์ง€์ •ํ•˜์—ฌ ํ†ตํ•ฉ ํ”Œ๋กœ์šฐ์˜ ์ผ๋ถ€๋กœ OrderSplitter ๋นˆ์„ ์„ ์–ธํ•  ์ˆ˜ ์žˆ๋‹ค.

@Bean
@Splitter(inputChannel="poChannel",
					outputChannel="splitOrderChannel")
public OrderSplitter orderSplitter() {
		return new OrderSplitter();
}

ํ”Œ๋กœ์šฐ์˜ ์ด ์ง€์ ์—์„œ PayloadTypeRouter๋ฅผ ์„ ์–ธํ•˜์—ฌ ๋Œ€๊ธˆ ์ฒญ๊ตฌ ์ •๋ณด์™€ ์ฃผ๋ฌธ ํ•ญ๋ชฉ ์ •๋ณด๋ฅผ ๊ฐ ์ •๋ณด์— ์ ํ•ฉํ•œ ํ•˜์œ„ ํ”Œ๋กœ์šฐ๋กœ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ๋‹ค.

@Bean
@Router(inputChannel = "splitOrderChannel")
public MessageRouter splitOrderRouter() {
    PayloadTypeRouter router = new PayloadTypeRouter();
    router.setChannelMapping(BillingInfo.class.getName(), "billingInfoChannel");
    router.setChannelMapping(List.class.getName(), "lineItemsChannel");
    return router;
}

BillingInfo ํƒ€์ž…์˜ ํŽ˜์ด๋กœ๋“œ๋Š” billingInfoChannel๋กœ ์ „๋‹ฌ๋˜์–ด ์ฒ˜๋ฆฌ๋˜๋ฉฐ, java.util.List ์ปฌ๋ ‰์…˜์— ์ €์žฅ๋œ ์ฃผ๋ฌธ ํ•ญ๋ชฉ๋“ค์€ List ํƒ€์ž…์œผ๋กœ lineItemsChannel์— ์ „๋‹ฌ๋œ๋‹ค.

List์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๋Œ€์‹  ๊ฐ LineItem์„ ๋ณ„๋„๋กœ ์ฒ˜๋ฆฌํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด?

์ด๋•Œ๋Š” List์„ ๋‹ค์ˆ˜์˜ ๋ฉ”์‹œ์ง€๋กœ ๋ถ„ํ• ํ•˜๊ธฐ ์œ„ํ•ด @Splitter ์• ๋…ธํ…Œ์ด์…˜์„ ์ง€์ •ํ•œ ๋ฉ”์„œ๋“œ(๋นˆ์ด ์•„๋‹˜)๋ฅผ ์ž‘์„ฑํ•˜๊ณ  ์ด ๋ฉ”์„œ๋“œ์—์„œ๋Š” ์ฒ˜๋ฆฌ๋œ LineItem์ด ์ €์žฅ๋œ ์ปฌ๋ ‰์…˜์„ ๋ฐ˜ํ™˜ํ•˜๋ฉด ๋œ๋‹ค.

@Splitter(inputChannel="lineItemsChannel", outputChannel="lineItemChannel")
public List<LineItem> lineItemSplitter(List<LineItem> lineItems) {
    return lineItems;
}

์ด ๊ฒฝ์šฐ List ํŽ˜์ด๋กœ๋“œ๋ฅผ ๊ฐ–๋Š” ๋ฉ”์‹œ์ง€๊ฐ€ limeItemsChannel์— ๋„์ฐฉํ•˜๋ฉด ์ด ๋ฉ”์‹œ์ง€๋Š” lineItemSplitter() ๋ฉ”์„œ๋“œ ์ธ์ž๋กœ ์ „๋‹ฌ๋œ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ด ๋ฉ”์„œ๋“œ๋Š” ๋ถ„ํ• ๋œ LineItem๋“ค์ด ์ €์žฅ๋œ ์ปฌ๋ ‰์…˜์„ ๋ฐ˜ํ™˜ํ•˜๋Š”๋ฐ, ์—ฌ๊ธฐ์„œ๋Š” ์ด๋ฏธ LineItem๋“ค์ด ์ €์žฅ๋œ ์ปฌ๋ ‰์…˜์„ ๊ฐ–๊ณ  ์žˆ์œผ๋ฏ€๋กœ ์ด๊ฒƒ์„ ๋ฐ”๋กœ ๋ฐ˜ํ™˜ํ•œ๋‹ค. ์ด์— ๋”ฐ๋ผ ์ปฌ๋ ‰์…˜์— ์ €์žฅ๋œ ๊ฐ LineItem์€ lineItemChannel๋กœ ์ „๋‹ฌ๋œ๋‹ค.

return IntegrationFlows...
    .split(orderSplitter()).<Object, String> route(
        p->{
            if(p.getClass().isAssignableFrom(BillingInfo.class)){
                return"BILLING_INFO";
            }else{
                return"LINE_ITEMS";
            }
        }, mapping->mapping
            .subFlowMapping("BILLING_INFO",
                sf->sf.<BillingInfo> handle((billingInfo,h)->{
                    ...
                }))
            .subFlowMapping("LINE_ITEMS",
                sf->sf.split()
                        .<LineItem> handle((lineItem,h)->{
                    ...
                }))
            )
    .get();

์„œ๋น„์Šค ์•กํ‹ฐ๋ฒ ์ดํ„ฐ

์„œ๋น„์Šค ์•กํ‹ฐ๋ฒ ์ดํ„ฐ๋Š” ์ž…๋ ฅ ์ฑ„๋„๋กœ๋ถ€ํ„ฐ ๋ฉ”์‹œ์ง€๋ฅผ ์ˆ˜์‹ ํ•˜๊ณ  ์ด ๋ฉ”์‹œ์ง€๋ฅผ MessageHandler ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•œ ํด๋ž˜์Šค(๋นˆ)์— ์ „๋‹ฌํ•œ๋‹ค.

555

@Bean
@ServiceActivator(inputChannel="someChannel")
public MessageHandler sysoutHandler() {
    return message -> {
        System.out.println("Message payload: " + message.getPayload());
    };
}

๋˜๋Š” ๋ฐ›์€ ๋ฉ”์‹œ์ง€์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ•œ ํ›„ ์ƒˆ๋กœ์šด ํŽ˜์ด๋กœ๋“œ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ์„œ๋น„์Šค ์•งํ‹ฐ๋ฒ ์ดํ„ฐ๋ฅผ ์„ ์–ธํ•  ์ˆ˜๋„ ์žˆ๋‹ค. ์ด ๊ฒฝ์šฐ ์ด ๋นˆ์€ MessageHandler๊ฐ€ ์•„๋‹Œ GenericHandler๋ฅผ ๊ตฌํ˜„ํ•œ ๊ฒƒ์ด์–ด์•ผ ํ•œ๋‹ค.

@Bean
@ServiceActivator(inputChannel="orderChannel",
    outputChannel="completeOrder")
public GenericHandler<Order> orderHandler(OrderRepository orderRepo) { 
		return (payload, headers) -> {
		    return orderRepo.save(payload); 
		};
}

์ด๋ฒˆ์—๋Š” ์ž๋ฐ” DSL ๊ตฌ์„ฑ์œผ๋กœ ๋ณ€๊ฒฝํ•ด๋ณด์ž.

public IntegrationFlow someFlow() {
    return IntegrationFlows
            ...
            .handle(msg -> {
                System.out.println("Message payload: " + msg.getPayload());
            }) 
        .get();
}

์—ฌ๊ธฐ์„œ๋Š” handle() ๋ฉ”์„œ๋“œ์˜ ์ธ์ž๋กœ ์ „๋‹ฌ๋˜๋Š” MessageHandler๋กœ ๋žŒ๋‹ค๋ฅผ ์‚ฌ์šฉํ•˜์˜€๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ๋ฉ”์„œ๋“œ ์ฐธ์กฐ ๋˜๋Š” MessageHandler ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ํด๋ž˜์Šค ์ธ์Šคํ„ด์Šค๊นŒ์ง€๋„ handler() ๋ฉ”์„œ๋“œ์˜ ์ธ์ž๋กœ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ๋‹ค.

๋งŒ์ผ ์„œ๋น„์Šค ์•กํ‹ฐ๋ฒ ์ดํ„ฐ๋ฅผ ํ”Œ๋กœ์šฐ์˜ ์ œ์ผ ๋์— ๋‘์ง€ ์•Š๋Š”๋‹ค๋ฉด MessageHandler์˜ ๊ฒฝ์šฐ์™€ ์œ ์‚ฌํ•˜๊ฒŒ handle() ๋ฉ”์„œ๋“œ์—์„œ GenericHandler๋ฅผ ์ธ์ž๋กœ ๋ฐ›์„ ์ˆ˜๋„ ์žˆ๋‹ค.

public IntegrationFlow orderFlow(OrderRepository orderRepo) {
    return IntegrationFlows
        ...
        .<Order>handle((payload, headers) -> { 
            return orderRepo.save(payload);
        }) 
        ...
        .get();
}

๊ฒŒ์ดํŠธ์›จ์ด

๊ฒŒ์ดํŠธ์›จ์ด๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ํ†ตํ•ฉ ํ”Œ๋กœ์šฐ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์ œ์ถœ(submit)ํ•˜๊ณ  ์„ ํƒ์ ์œผ๋กœ ํ”Œ๋กœ์šฐ์˜ ์ฒ˜๋ฆฌ ๊ฒฐ๊ณผ์ธ ์‘๋‹ต์„ ๋ฐ›์„ ์ˆ˜ ์žˆ๋Š” ์ˆ˜๋‹จ์ด๋‹ค.

666

์ด์ „์˜ ๋ณธ FileWriterGateway๋Š” ๋‹จ๋ฐฉํ–ฅ ๊ฒŒ์ดํŠธ์›จ์ด๋ฉฐ, ํŒŒ์ผ์— ์“ฐ๊ธฐ ์œ„ํ•ด ๋ฌธ์ž์—ด์„ ์ธ์ž๋กœ ๋ฐ›๊ณ  void๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋ฉ”์„œ๋“œ๋ฅผ ๊ฐ–๊ณ  ์žˆ๋‹ค. ์–‘๋ฐฉํ–ฅ ๊ฒŒ์ดํŠธ์›จ์ด์˜ ์ž‘์„ฑ๋„ ์–ด๋ ต์ง€ ์•Š์œผ๋ฉฐ, ์ด๋•Œ๋Š” ๊ฒŒ์ดํŠธ์›จ์ด ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ž‘์„ฑํ•  ๋•Œ ํ†ตํ•ฉ ํ”Œ๋กœ์šฐ๋กœ ์ „์†กํ•  ๊ฐ’์„ ๋ฉ”์„œ๋“œ์—์„œ ๋ฐ˜ํ™˜ํ•ด์•ผ ํ•œ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด, ๋ฌธ์ž์—ด์„ ๋ฐ›์•„์„œ ๋ชจ๋‘ ๋Œ€๋ฌธ์ž๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ํ†ตํ•ฉ ํ”Œ๋กœ์šฐ์˜ ๊ฒŒ์ดํŠธ์›จ์ด๋ฅผ ์ƒ๊ฐํ•ด ๋ณด์ž.

// option1. ์–ด๋…ธํ…Œ์ด์…˜ ๊ธฐ๋ฐ˜ ๊ฒŒ์ดํŠธ์›จ์ด ์„ค์ •
@Component
@MessagingGateway(defaultRequestChannel="inChannel",
    defaultReplyChannel="outChannel")
public interface UpperCaseGateway {
    String uppercase(String in);
}

// option2. ์ž๋ฐ” DSL ๊ตฌ์„ฑ ๊ฒŒ์ดํŠธ์›จ์ด ์„ค์ •
@Bean
public IntegrationFlow uppercaseFlow() {
    return IntegrationFlows
        .from("inChannel")
        .<String, String> transform(s -> s.toUpperCase()) 
				.channel("outChannel")
        .get();
}

์ฑ„๋„ ์–ด๋Œ‘ํ„ฐ

์ฑ„๋„ ์–ด๋Œ‘ํ„ฐ๋Š” ํ†ตํ•ฉ ํ”Œ๋กœ์šฐ์˜ ์ž…๊ตฌ์™€ ์ถœ๊ตฌ๋ฅผ ๋‚˜ํƒ€๋‚ธ๋‹ค. ๋ฐ์ดํ„ฐ๋Š” ์ธ๋ฐ”์šด๋“œ(inbound) ์ฑ„๋„ ์–ด๋Œ‘ํ„ฐ๋ฅผ ํ†ตํ•ด ํ†ตํ•ฉ ํ”Œ๋กœ์šฐ๋กœ ๋“ค์–ด์˜ค๊ณ , ์•„์›ƒ๋ฐ”์šด๋“œ(outbound) ์ฑ„๋„ ์–ด๋Œ‘ํ„ฐ๋ฅผ ํ†ตํ•ด ํ†ตํ•ฉ ํ”Œ๋กœ์šฐ์—์„œ ๋‚˜๊ฐ„๋‹ค.

777

์ธ๋ฐ”์šด๋“œ ์ฑ„๋„ ์–ด๋Œ‘ํ„ฐ๋Š” ํ”Œ๋กœ์šฐ์— ์ง€์ •๋œ ๋ฐ์ดํ„ฐ ์†Œ์Šค์— ๋”ฐ๋ผ ์—ฌ๋Ÿฌ ๊ฐ€์ง€ ํ˜•ํƒœ๋ฅผ ๊ฐ–๋Š”๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์ฆ๊ฐ€๋˜๋Š” ์ˆซ์ž๋ฅผ AtomicInteger๋กœ๋ถ€ํ„ฐ ํ”Œ๋กœ์šฐ๋กœ ๋„ฃ๋Š” ์ธ๋ฐ”์šด๋“œ ์ฑ„๋„ ์–ด๋Œ‘ํ„ฐ๋ฅผ ์„ ์–ธํ•  ์ˆ˜ ์žˆ๋‹ค.

// option1. ์ž๋ฐ” ์–ด๋…ธํ…Œ์ด์…˜ ๊ธฐ๋ฐ˜ ์–ด๋Œ‘ํ„ฐ ๊ตฌ์„ฑ
@Bean
@InboundChannelAdapter(
    poller=@Poller(fixedRate="1000"), channel="numberChannel")
public MessageSource<Integer> numberSource(AtomicInteger source) {
	return () -> {
		return new GenericMessage<>(source.getAndIncrement());
	};
}

// option2. ์ž๋ฐ” DSL ๊ธฐ๋ฐ˜ ์–ด๋Œ‘ํ„ฐ ๊ตฌ์„ฑ
@Bean
public IntegrationFlow someFlow(AtomicInteger integerSource) {
    return IntegrationFlows 
        .from(integerSource, "getAndIncrement",
          c -> c.poller(Pollers.fixedRate(1000))) 
        ...
        .get();
}

์—”๋“œํฌ์ธํŠธ ๋ชจ๋“ˆ

์Šคํ”„๋ง ํ†ตํ•ฉ์€ ์šฐ๋ฆฌ ๋‚˜๋ฆ„์˜ ์ฑ„๋„ ์–ด๋Œ‘ํ„ฐ๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค€๋‹ค. ์•„๋ž˜ ํ‘œ์— ์žˆ๋Š” ๊ฒƒ์„ ํฌํ•จํ•ด์„œ ๋‹ค์–‘ํ•œ ์™ธ๋ถ€ ์‹œ์Šคํ…œ๊ณผ์˜ ํ†ตํ•ฉ์„ ์œ„ํ•ด ์ฑ„๋„ ์–ด๋Œ‘ํ„ฐ๊ฐ€ ํฌํ•จ๋œ 24๊ฐœ ์ด์ƒ์˜ ์—”๋“œํฌ์ธํŠธ ๋ชจ๋“ˆ์„ ์Šคํ”„๋ง ํ†ตํ•ฉ์ด ์ œ๊ณตํ•œ๋‹ค.

Module Dependency artifact ID
AMQP spring-integration-amqp
Spring application events spring-integration-event
RSS and Atom spring-integration-feed
Filesystem spring-integration-file
FTP /FTPS spring-integration-ftp
GemFire spring-integration-gemfire
HTTP spring-integration-http
JDBC spring-integration-jdbc
JPA spring-integration-jpa
JMS spring-integration-jms
Email spring-integration-mail
MongoDB spring-integration-mongodb
MQTT spring-integration-mqtt
Redis spring-integration-redis
RMI spring-integration-rmi
SFTP spring-integration-sftp
STOMP spring-integration-stomp
Stream spring-integration-stream
Syslog spring-integration-syslog
TCP /UDP spring-integration-ip
Twitter spring-integration-twitter
Web Services spring-integration-ws
WebFlux spring-integration-webflux
WebSocket spring-integration-websocket
XMPP spring-integration-xmpp
ZooKeeper spring-integration-zookeeper

์ด๋ฉ”์ผ ํ†ตํ•ฉ ํ”Œ๋กœ์šฐ ์ƒ์„ฑํ•˜๊ธฐ

ํƒ€์ฝ” ํด๋ผ์šฐ๋“œ ๋ฐ›์€ ํŽธ์ง€ํ•จ์˜ ํƒ€์ฝ” ์ฃผ๋ฌธ ์ด๋ฉ”์ผ์„ ์ง€์†์ ์œผ๋กœ ํ™•์ธํ•˜์—ฌ ์ด๋ฉ”์ผ์˜ ์ฃผ๋ฌธ ๋ช…์„ธ๋ฅผ ํŒŒ์‹ฑํ•œ ํ›„ ํ•ด๋‹น ์ฃผ๋ฌธ ๋ฐ์ดํ„ฐ์˜ ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•ด ํƒ€์ฝ” ํด๋ผ์šฐ๋“œ์— ์ œ์ถœํ•˜๋Š” ํ†ตํ•ฉ ํ”Œ๋กœ์šฐ๋ฅผ ๊ตฌํ˜„ํ•ด๋ณด์ž.

์ด๋ฉ”์ผ ์„ค์ • ์ •๋ณด๋ฅผ ์ •์˜ํ•˜์ž

@Data
@ConfigurationProperties(prefix="tacocloud.email")
@Component
public class EmailProperties {
    private String username;
    private String password;
    private String host;
    private String mailbox;
    private long pollRate = 30000;
    public String getImapUrl() {
        return String.format("imaps://%s:%s@%s/%s",
            this.username, this.password, this.host, this.mailbox);
    }
}

EmailProperties ํด๋ž˜์Šค์— ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋Š” application.yml ํŒŒ์ผ์— ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.

tacocloud:
  email:
    host: imap.tacocloud.com 
    mailbox: INBOX
    username: taco-in-flow 
    password: 1L0v3T4c0s 
    poll-rate: 10000

์ด๋ฉ”์ผ๋กœ ํƒ€์ฝ” ์ฃผ๋ฌธ์„ ๋ฐ›๊ธฐ ์œ„ํ•œ ํ†ตํ•ฉ ํ”Œ๋กœ์šฐ

888

์ง€๊ธˆ๋ถ€ํ„ฐ๋Š” ํƒ€์ฝ” ์ฃผ๋ฌธ ์ด๋ฉ”์ผ ํ”Œ๋กœ์šฐ์˜ ์ž๋ฐ” DSL ๊ตฌ์„ฑ ํ•ด๋ณด์ž

@Configuration
public class TacoOrderEmailIntegrationConfig {

    @Bean
    public IntegrationFlow tacoOrderEmailFlow(EmailProperties emailProps,
                                              EmailToOrderTransformer emailToOrderTransformer,
                                              OrderSubmitMessageHandler orderSubmitHandler) {
        return IntegrationFlows.from(Mail.imapInboundAdapter(emailProps.getImapUrl()),
                                     e -> e.poller(Pollers.fixedDelay(emailProps.getPollRate())))
                               .transform(emailToOrderTransformer)
                               .handle(orderSubmitHandler)
                               .get();
    }
}

tacoOrderEmailFlow() ๋ฉ”์„œ๋“œ์— ์ •์˜๋œ ํƒ€์ฝ” ์ฃผ๋ฌธ ์ด๋ฉ”์ผ ํ”Œ๋กœ์šฐ๋Š” 3๊ฐœ์˜ ์„œ๋กœ ๋‹ค๋ฅธ ์ปดํฌ๋„ŒํŠธ๋กœ ๊ตฌ์„ฑ๋œ๋‹ค.

  • IMAP ์ด๋ฉ”์ผ ์ธ๋ฐ”์šด๋“œ ์ฑ„๋„ ์–ด๋Œ‘ํ„ฐ : ์ด ์ฑ„๋„ ์–ด๋Œ‘ํ„ฐ๋Š” EmailProperties์˜ getImapUrl() ๋ฉ”์„œ๋“œ๋กœ๋ถ€ํ„ฐ ์ƒ์„ฑ๋œ IMP URL๋กœ ์ƒ์„ฑ๋˜๋ฉฐ, EmailProperties์˜ poolRate ์†์„ฑ์— ์„ค์ •๋œ ์ง€์—ฐ ์‹œ๊ฐ„์ด ๋  ๋•Œ๋งˆ๋‹ค ์ด๋ฉ”์ผ์„ ํ™•์ธํ•œ๋‹ค. ๋ฐ›์€ ์ด๋ฉ”์ผ์€ ๋ณ€ํ™˜๊ธฐ์— ์—ฐ๊ฒฐํ•˜๋Š” ์ฑ„๋„๋กœ ์ „๋‹ฌ๋œ๋‹ค.
  • ์ด๋ฉ”์ผ์„ Order ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๋ณ€ํ™˜๊ธฐ : ์ด ๋ณ€ํ™˜๊ธฐ๋Š” tacoOrderEmailFlow() ๋ฉ”์„œ๋“œ๋กœ ์ฃผ์ž…๋˜๋Š” EmailToOrderTransformer์— ๊ตฌํ˜„๋œ๋‹ค. ๋ณ€ํ™˜๋œ ์ฃผ๋ฌธ ๋ฐ์ดํ„ฐ(Order ๊ฐ์ฒด)๋Š” ๋‹ค๋ฅธ ์ฑ„๋„์„ ํ†ตํ•ด ์ตœ์ข… ์ปดํฌ๋„ŒํŠธ๋กœ ์ „๋‹ฌ๋œ๋‹ค.
  • ํ•ธ๋“ค๋Ÿฌ(์•„์›ƒ๋ฐ”์šด๋“œ ์ฑ„๋„ ์–ด๋Œ‘ํ„ฐ๋กœ ์ž‘๋™) : ํ•ธ๋“ค๋Ÿฌ๋Š” Order ๊ฐ์ฒด๋ฅผ ๋ฐ›์•„์„œ ํƒ€์ฝ” ํด๋ผ์šฐ๋“œ์˜ REST API๋กœ ์ œ์ถœํ•œ๋‹ค.

ํ†ตํ•ฉ ๋ณ€ํ™˜๊ธฐ๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์ž…๋ ฅ ์ด๋ฉ”์ผ์„ ํƒ€์ฝ” ์ฃผ๋ฌธ(Order ๊ฐ์ฒด)์œผ๋กœ ๋ณ€ํ™˜ํ•˜๊ธฐ

@Component
public class EmailToOrderTransformer
    extends AbstractMailMessageTransformer<Order> {
    @Override
    protected AbstractIntegrationMessageBuilder<Order>
    doTransform(Message mailMessage) throws Exception {
        Order tacoOrder = processPayload(mailMessage); // ์ด๋ฉ”์ผ์„ Order ๊ฐ์ฒด๋กœ ํŒŒ์‹ฑ
        return MessageBuilder.withPayload(tacoOrder); 
    }
...
}

AbstractMailMessageTransformer๋Š” ํŽ˜์ด๋กœ๋“œ๊ฐ€ ์ด๋ฉ”์ผ์ธ ๋ฉ”์‹œ์ง€๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐ ํŽธ๋ฆฌํ•œ ๋ฒ ์ด์Šค ํด๋ž˜์Šค๋‹ค. ์ž…๋ ฅ ๋ฉ”์‹œ์ง€๋กœ๋ถ€ํ„ฐ ์ด๋ฉ”์ผ ์ •๋ณด๋ฅผ Message ๊ฐ์ฒด(doTransform() ๋ฉ”์„œ๋“œ์˜ ์ธ์ž๋กœ ์ „๋‹ฌ)๋กœ ์ถ”์ถœํ•˜๋Š” ์ผ์„ ์ง€์›ํ•œ๋‹ค.

EmailToOrderTransformer๊ฐ€ ๋งˆ์ง€๋ง‰์œผ๋กœ ํ•˜๋Š” ์ผ์€ Order ๊ฐ์ฒด๋ฅผ ํฌํ•จํ•˜๋Š” ํŽ˜์ด๋กœ๋“œ๋ฅผ ๊ฐ–๋Š” MessageBuilder๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

๋ฉ”์‹œ์ง€ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ํ†ตํ•ด์„œ ํƒ€์ฝ” ํด๋ผ์šฐ๋“œ API์— ์ฃผ๋ฌธ์„ POSTํ•˜๊ธฐ

@Component
public class OrderSubmitMessageHandler implements GenericHandler<Order> {
    private RestTemplate rest;
    private ApiProperties apiProps;

    public OrderSubmitMessageHandler(ApiProperties apiProps, RestTemplate rest) {
        this.apiProps = apiProps;
        this.rest = rest;
    }

    @Override
    public Object handle(Order order, Map<String, Object> headers) {
        rest.postForObject(apiProps.getUrl(), order, String.class);
        return null;
    }
}

GenericHandler ์ธํ„ฐํŽ˜์ด์Šค์˜ handle ๋ฉ”์„œ๋“œ๋ฅผ ์˜ค๋ฒ„๋ผ์ด๋“œ ํ•˜์˜€๋‹ค. ์ด ๋ฉ”์„œ๋“œ๋Š” ์ž…๋ ฅ๋œ Order ๊ฐ์ฒด๋ฅผ ๋ฐ›์œผ๋ฉฐ, ์ฃผ์ž…๋œ RestTemplate์„ ์‚ฌ์šฉํ•ด์„œ ์ฃผ๋ฌธ(Order ๊ฐ์ฒด)์„ ์ œ์ถœํ•œ๋‹ค.

ApiProperties ๊ตฌ์„ฑ

@Data 
@ConfigurationProperties(prefix="tacocloud.api") 
@Component
public class ApiProperties {
  private String url;
}

application.yml์—๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ตฌ์„ฑํ•œ๋‹ค.

tacocloud:
  api:
    url: http://api.tacocloud.com

ํƒ€์ฝ” ํด๋ผ์šฐ๋“œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋นŒ๋“œ ๋ฐ ์‹คํ–‰ํ•˜๊ธฐ

  • simple-flow ํ”„๋กœ์ ํŠธ๋ฅผ ๋นŒ๋“œํ•œ๋‹ค.
  • ./mvnw clean package
  • simple-flow ํ”„๋กœ์ ํŠธ๊ฐ€ ๋นŒ๋“œ๋˜์–ด simple-flow\target ์•„๋ž˜์— simple-flow-0.0.9-SNAPSHOT.jar ํŒŒ์ผ๋กœ ์ƒ์„ฑ๋œ๋‹ค.
  • java -Dspring.profiles.active=javaconfig -jar target/simple-flow-0.0.9-SNAPSHOT.jar
  • /tmp/sia5/files/simple.txt ํŒŒ์ผ ํ™•์ธ

์š”์•ฝ

  • ์Šคํ”„๋ง ํ†ตํ•ฉ์€ ํ”Œ๋กœ์šฐ๋ฅผ ์ •์˜ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค€๋‹ค. ๋ฐ์ดํ„ฐ๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์œผ๋กœ ๋“ค์–ด์˜ค๊ฑฐ๋‚˜ ๋‚˜๊ฐˆ ๋•Œ ํ”Œ๋กœ์šฐ๋ฅผ ํ†ตํ•ด ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ํ†ตํ•ฉ ํ”Œ๋กœ์šฐ๋Š” XML, Java, Java DSL์„ ์‚ฌ์šฉํ•ด์„œ ์ •์˜ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ๋ฉ”์‹œ์ง€ ๊ฒŒ์ดํŠธ์›จ์ด์™€ ์ฑ„๋„ ์–ด๋Œ‘์ฒ˜๋Š” ํ†ตํ•ฉ ํ”Œ๋กœ์šฐ์˜ ์ž…๊ตฌ๋‚˜ ์ถœ๊ตฌ์˜ ์—ญํ• ์„ ํ•œ๋‹ค.
  • ๋ฉ”์‹œ์ง€๋Š” ํ”Œ๋กœ์šฐ ๋‚ด๋ถ€์—์„œ ๋ณ€ํ™˜, ๋ถ„ํ• , ์ง‘์ , ์ „๋‹ฌ๋  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์„œ๋น„์Šค ์•กํ‹ฐ๋ฒ ์ดํ„ฐ์—์˜ํ•ด ์ฒ˜๋ฆฌ๋  ์ˆ˜ ์žˆ๋‹ค.
  • ๋ฉ”์‹œ์ง€ ์ฑ„๋„์€ ํ†ตํ•ฉ ํ”Œ๋กœ์šฐ์˜ ์ปดํฌ๋„ŒํŠธ๋“ค์„ ์—ฐ๊ฒฐํ•œ๋‹ค.

์ฐธ๊ณ 

โš ๏ธ **GitHub.com Fallback** โš ๏ธ