ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [스프링 인 액션] Chapter 9 - 스프링 통합하기 :: 간단한 통합 플로우 선언하기
    개발서적읽기/Spring in Action 제 5판 2020. 9. 26. 20:19



    많은 애플리케이션은 외부 시스템과 연결하여 작업을 수행한다. 외부 시스템에서 데이터를 


    읽거나 쓸 때, 애플리케이션에서 필요한 형태로 변경하기 위해 특별한 데이터 처리 플로우가


    필요할 수 있다. 이번 장에서는 데이터 이동을 처리하는 통합 패턴을 스프링 통합(Spring 


    Integration)을 통해 알아볼 것이다. 각 통합 패턴은 하나의 컴포넌트로 구현되며, 이것을


    통해서 파이프라인으로 메시지가 데이터를 운반한다. 스프링 구성을 사용하면 데이터가 


    이동하는 파이프라인으로 컴포넌트들을 조립할 수 있다. 우선 스프링 통합 사용 시의 기능과


    특성을 보여주는 간단한 통합 플로우를 정의해보자.


    애플리케이션은 통합 플로우를 통해서 외부 리소스나 애플리케이션 자체에 데이터를 수신 또는


    전송할 수 있으며, 스프링 통합은 이런 통합 플로우를 생성할 수 있게 해준다. 전송되는 데이터로


    파일 시스템을 예로 들 수 있는데, 그래서 스프링 통합의 컴포넌트 중에 파일을 읽거나 쓰는


    채널 어댑터(Channel Adapter)가 있다. 이번 장에서는 파일 시스템에 데이터를 쓰는 통합 


    플로우를 생성할 것이다. 우선 스프링 통합 의존성을 빌드에 추가하자.

    <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>

    첫 번째 의존성은 스프링 통합의 스프링 부트 스타터다. 통합하려는 플로우와 무관하게 이 


    의존성은 스프링 통합 플로우의 개발 시에 반드시 추가해야 한다. 모든 스프링 부트 스타터 


    의존성이 그렇듯 이 의존성도 Initializr 폼의 체크 상자에서 선택할 수 있다.


    두 번째 의존성은 스프링 통합의 파일 엔드포인트 모듈이다. 이 모듈은 외부 시스템 통합에 


    사용되는 24개 이상의 엔드포인트 모듈 중 하나다.


    의존성을 추가한 다음 해야할 것은, 파일에 데이터를 쓸 수 있도록 애플리케이션에서 통합 


    플로우로 데이터를 전송하는 게이트웨이를 생성해야 한다. 

    package sia5;

    import org.springframework.integration.annotation.MessagingGateway;
    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 );

    }

    FileWriterGateway에는 @MessageGateway가 지정되었다. 이 애노테이션은 FileWriterGateway


    인터페이스의 구현체를 런타임 시에 생성하라고 스프링 통합에 알려준다. Repository 구현체를


    Spring Data가 자동 생성하는 것과 유사하다.


    @MessageGateway의 defaultRequestChannel 속성은 해당 인터페이스의 메서드 호출로 생성된


    메시지가 이 속성에 지정된 메시지 채널로 전송된다는 것을 나타낸다.


    writeToFile()의 filename 매개변수에는 @Header가 지정되었다. @Header 애노테이션은 


    filename에 전달되는 값이 메시지의 payload가 아닌 메시지 헤더에 있다는 것을 나타낸다.


    FileHeaders.FILENAME 상수의 실제 값은 file_name이다.


    반면에 data 매개변수 값은 메시지 페이로드로 전달된다.


    메시지는 메시지 헤더와 같은 메타데이터와 실제 데이터인 페이로드로 구성된다.


    이제는 메시지 게이트웨이가 생성되었으므로 통합 플로우를 구성해야 한다. 스프링 통합 스타터


    의존성을 빌드에 추가했으므로 스프링 통합의 자동-구성이 수행될 수 있다. 


    그러나 애플리케이션의 요구를 충족하는 플로우를 정의하는 구성은 우리가 추가로 작성해야 


    한다. 통합 플로우는 다음 세 가지 구성 방법으로 정의할 수 있다.

    • XML 구성

    • Java 구성

    • DSL을 사용한 Java 구성

    세 가지 구성 방법 모두를 알아보자. 



    ■XML을 사용해서 통합 플로우 정의하기

    XML 구성의 사용은 가급적 피하려 했지만, 스프링 통합에서는 오랫동안 XML로 통합 

    플로우를 정의했으므로 최소한 하나의 예는 알아볼 필요가 있다.
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:int="http://www.springframework.org/schema/integration"
    xmlns:int-file="http://www.springframework.org/schema/integration/file"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/integration
    http://www.springframework.org/schema/integration/spring-integration.xsd
    http://www.springframework.org/schema/integration/file
    http://www.springframework.org/schema/integration/file/spring-integration-file.xsd">

    <int:channel id="textInChannel" />

    <int:transformer id="upperCase"
    input-channel="textInChannel"
    output-channel="fileWriterChannel"
    expression="payload.toUpperCase()" />

    <int:channel id="fileWriterChannel" />

    <int-file:outbound-channel-adapter id="writer"
    channel="fileWriterChannel"
    directory="/tmp/sia5/files"
    mode="APPEND"
    append-new-line="true" />

    </beans>
    XML 구성에서 주목할 내용은 다음과 같다.

    • textInChannel이라는 이름의 채널을 구성하였다. 이것은 FileWriterGateway의 요청

      채널로 설정된 것과 같은 채널이다. FileWriterGateway의 writeToFile() 메서드가

      호출되면 결과 메시지가 textInChannel로 전달된다.

    • textInChannel로부터 메시지를 받는 변환기를 구성하였다. 이 변환기는 SpEL(Spring

      Expression Language) 표현식을 사용해서 메시지 페이로드에 대해 toUpperCase()를

      호출하여 대문자로 변환한다. 그리고 변환된 결과는 fileWriterChannel로 전달된다.

    • fileWriterChannel이라는 이름의 채널을 구성하였다. 이 채널은 변환기와 아웃바운드

      채널 어댑터(outbound channel adapter)를 연결하는 전달자의 역할을 수행한다.

    • 끝으로, int-file 네임스페이스를 사용하여 아웃바운드 채널 어댑터를 구성하였다.

      이 XML 네임스페이스는 파일에 데이터를 쓰기 위해 스프링 통합의 파일 모듈에서 

      제공한다. 아웃바운드 채널 어댑터는 fileWriterChannel로부터 메시지를 받은 후 

      해당 메시지 페이로드를 directory 속성에 지정된 디렉터리의 파일에 쓴다.

      이때 파일 이름은 해당 메시지의 file_name 헤더에 지정된 것을 사용한다. 만일 해당 

      파일이 이미 있으면 기존 데이터에 덮어쓰지 않고 줄을 바꾸어 제일 끝에 추가한다.

    스프링 부트 애플리케이션에서 XML 구성을 사용하고자 한다면 @ImportResource 

    애노테이션을 사용하여 XML을 리소스로 import 해야 한다. 

    @Configuration
    @ImportResource("classpath:/filewriter-config.xml")
    public class FileWriterIntegrationConfig {

    }

    많은 개발자들은 XML 기반의 스프링 통합을 꺼린다. 


    지금부터는 Java를 이용한 구성 방법을 살펴보자.




    ■Java로 통합 플로우 정의하기


    대부분의 스프링 애플리케이션이 XML 구성 대신 Java 구성을 사용한다. 실제로 스프링 부트


    애플리케이션에서 Java 구성은 스프링의 자동-구성을 자연스럽게 보완해 주는 방법이다.


    따라서 스프링 부트 애플리케이션에 통합 플로우를 추가할 때는 XML 보다는 Java로 


    플로우를 정의하는 것이 좋다.

    package sia5;

    import java.io.File;

    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.ImportResource;
    import org.springframework.context.annotation.Profile;
    import org.springframework.integration.annotation.ServiceActivator;
    import org.springframework.integration.annotation.Transformer;
    import org.springframework.integration.dsl.IntegrationFlow;
    import org.springframework.integration.dsl.IntegrationFlows;
    import org.springframework.integration.dsl.channel.MessageChannels;
    import org.springframework.integration.file.FileWritingMessageHandler;
    import org.springframework.integration.file.dsl.Files;
    import org.springframework.integration.file.support.FileExistsMode;
    import org.springframework.integration.transformer.GenericTransformer;

    @Configuration
    public class FileWriterIntegrationConfig {

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

    @Bean
    @ServiceActivator(inputChannel="fileWriterChannel")
    public FileWritingMessageHandler fileWriter() {
    FileWritingMessageHandler handler =
    new FileWritingMessageHandler(new File("/tmp/sia5/files"));
    handler.setExpectReply(false);
    handler.setFileExistsMode(FileExistsMode.APPEND);
    handler.setAppendNewLine(true);
    return handler;
    }

    }

    이 Java 구성에서는 두 개의 빈을 정의한다. 변환기의 파일-쓰기 메시지 핸들러다.


    변환기 빈인 GenericTransformer는 함수형 인터페이스이므로 메시지 텍스트에 


    toUpperCase()를 호출하는 람다로 구현할 수 있다. @Transformer 애노테이션은 


    GenericTransformer가 textInChannel의 메시지를 받아서 fileWriterChannel로 쓰는 통합 


    플로우 변환기라는 것을 지정한다.


    파일-쓰기 빈에는 @ServiceActivator가 지정되었다. 이 애노테이션은 fileWriterChannel


    로부터 메시지를 받아서 FileWritingMessageHandler의 인스턴스로 정의된 서비스에


    넘겨주는 역할을 한다. FileWritingMessageHandler는 메시지 핸들러이며, 메시지 


    페이로드를 지정된 디렉터리의 파일에 쓴다. 이때 파일 이름은 해당 메시지의 file_name


    헤더에 지정된 것을 사용한다. 그리고 XML 구성과 동일하게 해당 파일이 이미 있으면


    기존 데이터에 덮어쓰지 않고 줄을 바꾸어 제일 끝에 추가한다.


    FileWritingMessageHandler 빈의 구성에서 한 가지 특이한 것은 setExpectReply(false)를


    호출한다는 것이다. 이 메서드는 서비스에서 응답 채널(플로우의 업스트림 컴포넌트로


    값이 반환될 수 있는 채널)을 사용하지 않음을 나타낸다. 만일 setExpectReplay(false)를


    호출하지 않으면, 통합 플로우가 정상적으로 작동하더라도 응답 채널이 구성되지 않았다는


    로그 메시지들이 나타난다.


    위 Java 구성에서는 채널들을 별도로 선언하지 않았다는 것에 주목하자. textInChannel과


    fileWriterChannel이라는 이름의 빈이 없으면 이 채널들은 자동으로 생성된다. 그러나 각 


    채널의 구성 방법을 더 제어하고 싶으면 다음과 같이 별도의 빈으로 구성할 수 있다.

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

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

    스프링 통합의 자바 DSL(Domain Specific Language) 구성 방법을 사용하면 코드를 훨씬 더


    간소화할 수 있다.




    ■스프링 통합의 DSL 구성 사용하기


    이번에는 Java를 사용하면서 추가로 DSL 방식으로 파일-쓰기 통합 플로우를 정의할 것이다.


    통합 플로우의 각 컴포넌트를 별도의 빈으로 선언하지 않고 전체 플로우를 하나의 빈으로 


    선언한다.

    @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();
    }

    Java 구성과 마찬가지로, 여기서도 채널 빈을 따로 선언할 필요가 없다.


    하지만 변환기를 아웃바운드 채널 어댑터와 연결하는 채널의 경우에 이 채널을 별도로 


    구성해야 한다면, 아래와 같이 플로우 정의에서 channel()을 호출하여 해당 채널을 


    이름으로 참조할 수 있다.

    @Bean
    public IntegrationFlow fileWriterFlow() {
    return IntegrationFlows
    .from(MessageChannels.direct("textInChannel"))
    .<String, String>transform(t -> t.toUpperCase())
    .channel(MessageChannels.direct("fileWriterChannel"))
    .handle(Files
    .outboundAdapter(new File("/tmp/sia5/files"))
    .fileExistsMode(FileExistsMode.APPEND)
    .appendNewLine(true))
    .get();
    }

    이렇게 세 가지의 구성 방법을 사용해서 스프링 통합 플로우를 정의해 보았다.


    다음 포스팅에선 스프링 통합의 큰 그림을 그려본다.

    댓글

Designed by Tistory.