Spring/Spring Integration

[Spring Integeration] 2장 - Spring Integration : 가이드 문서 읽어보기

공대키메라 2022. 9. 29. 10:57

지난 시간에는 Integration과 관련한 정보에 대해 알아보았다.

( 지난 시간 내용이 궁금하면 여기 클릭!  )

 

사실 이 정보들은 전부 Spring Integeration을 좀 더 잘 이해하기 위한 선행된 공부이기도 했다.

 

이번 시간에는 Spring Integeration이란 무엇인가에 대해 알아보고자 한다.

 

참고한 사이트는 다음과 같다. 

https://spring.io/projects/spring-integration

https://stackoverflow.com/questions/2122604/what-is-an-endpoint

https://docs.spring.io/spring-integration/reference/html/overview.html

https://spring.io/guides/gs/integration/

https://springsource.tistory.com/47

https://docs.spring.io/spring-integration/reference/html/index.html

https://sup2is.github.io/2020/08/12/what-is-spring-integration.html

1. Overview of Spring Integration Framework

Spring Integeration은 Spring programming 모델이 잘 알려진 Enterprise Integration Pattern을 지원하기 위해 확장한 것이다.

 

Spring Integeration은 스프링 기반 어플리케이션 내에 경량 메시징과 선언적인 어댑터를 통하여 외부 시스템과의 통합을 지원할 수 있게끔 한다.

 

그러한 어댑터들은 remoting, messaging 그리고 스케줄링 을 위한 Spring 지원을 통해 더 높은 레벨의 추상화를 제공한다. 

 

Spring Integration의 주요한 목표는 유지가능하고 테스트 가능한 코드가 필수적인 enterprise integration solution을 구축하는데 간단한 모델을 제공하는 것이다. 

 

배경(Background)

스프링 프레임워크의 중요한 주제 중 하나는 IoC(Inversion Of Control)이다. 넓은 의미에서 이것은 framework가 context 내에서 관리되는 component들을 대표해 책임을 관리한다는 것을 의미한다. 

 

그 component들은 간단한데, 왜냐하면 그러한 책임을 지지 않기 떄문이다. 

 

예를 들어, DI는 의존성을 생성하거나 위치시키는 것에 책임을 진다. 마찬가지로 AOP도 일반적인 cross-cutting 으로 비즈니스 컴포넌트들을 완화한다. 각각의 경우에, 결과는 테스트하고, 이해하고, 유지하고, 그리고 확장하기 더 쉬운 시스템이다. 

 

더욱이, Spring Framework와 포트폴리오는 기업 어플리케이션을 만들기 위해 이해할 수 있는 프로그래밍 모델을 제공한다. 개발자들은 이 모델의 일관성과, 특히 잘 생성된 최고의 사례를 바탕으로 많은 이익을 얻는다. 

스프링의 간단해진 추상화와 강력한 지원 라이브러리들이 테스트성과 가용성을 통시에 증가시기면서 개발자의 생산성을 올려준다. 

Spring Integeration은 이러한 같은 목표와 원칙에 영감을 받았다. Spring 프로그래밍 모델을 메시징 도메인으로 확장하고 Spring의 기존 엔터프라이즈 통합 지원을 기반으로 구축하여 더 높은 수준의 추상화를 제공한다. 특정 비즈니스 로직을 실행하고 응답을 보내야 하는 때 같은 런타임 문제에 제어 역전이 지원되는 메시지 기반 구조를 지원한다. 

 

다양한 전송 및 다양한 데이터 포맷들이 테스트 가능성에 영향을 주는 것 없이 통합하기 위해 라우팅과 메시지 변환을 제공한다.  비즈니스 컴포넌트들은 더 구조로부터 고립되고, 개발자들은 복잡한 통합 책임에서 해방된다. 

 

목표와 원칙(Goal and Principles)

Spring Integeration은 따라오는 목적들에 동기를 가진다.

 

  1. 복잡한 통합 솔루션을 구현하는데 간단한 모델을 제공

  

   2. 스프링 기반 어플리케이션 내에 비동기의 메시지 기반 행위를 원활하게 한다.

 

   3. 존재하는 스프링 사용자들에게 직관적이고 서서히 증가되는 적응을 돕는다. 

 

Spring Integeration은 다음 원칙으로 가이드한다. 

 

  1. 컴포넌트들은 모듈성과 테스트 가능성을 위해 느슨하게 결합되어야 한다. 

 

  2. 프레임워크는 비즈니스 로직과 통합 로직 사이에 관심을 강하게 분리해야만 한다. 

 

  3. Extension point는 재사용성과 휴대성을 증가시키기 위해 본질적으로 추상화여야 한다.

 

특징(Feature)

  • Implementation of most of the Enterprise Integration Patterns
  • Endpoint
  • Channel (Point-to-point and Publish/Subscribe)
  • Aggregator
  • Filter
  • Transformer
  • Control Bus
  • Integration with External Systems
  • ReST/HTTP
  • FTP/SFTP
  • STOMP
  • WebServices (SOAP and ReST)
  • TCP/UDP
  • JMS
  • RabbitMQ
  • Email
  • The framework has extensive JMX support
  • Exposing framework components as MBeans
  • Adapters to obtain attributes from MBeans, invoke operations, send/receive notifications

여기서 특징 관련해서 설명을 찾고 있었는데 이미 Integration에 관한 근본적인 내용은 오랫동안 변하지 않은 것 같다(그게 무슨말이냐?)

 

10년 전이나, 지금이나 내용이 동일하다는 것이다! (띠용!)

 

용어들에 대해 잘 정리를 해 놓은 블로그를 찾아서 주소를 첨부한다 (선배님... 감사합니다...) (여기 클릭)

 

좀 더 간단하게 정리한 내용을 참고해보면 다음과 같다.

 

  • 채널: 한 요소로부터 다른 요소로 메시지를 전달
  • 필터: 조건에 맞는 메시지가 플로우를 통과하게 해줌
  • 변환기: 메시지 값을 변경하거나 메시지 페이로드의 타입을 다른 타입으로 변환
  • 라우터: 여러 채널 중 하나로 메시지를 전달하며 대개 메시지 헤더를 기반으로 함
  • 분배기: 들어오는 메시지를 두 개 이상의 메시지로 분할하며, 분할된 각 메시지는 다른 채널로 전송
  • 집적기: 분배기와 상반된 것으로 별개의 채널로부터 전달되는 다수의 메시지를 하나의메시지로 결합함
  • 서비스 액티베이터: 메시지를 처리하도록 자바 메서드에 메시지를 넘겨준 후 메서드의 반환값을 출력 채널로 전송
  • 채널 어댑터: 외부 시스템에 채널을 연결함. 외부 시스템으로부터 입력을 받거나 쓸 수 있음
  • 게이트웨이: 인터페이스를 통해 통합플로우로 데이터를 전달

출처 : https://sup2is.github.io/2020/08/12/what-is-spring-integration.html

 


2. 가이드로 Spring Integration 따라하기

Spring 공식 사이트에서 Spring Integeration에 대한 sample 파일들을 git 에 공유하고 있다.

(Spring Integration 학습용 git 주소)

 

물론 Spring Initializr로도 가능하니 편한대로 하자. gradle 과 maven 둘 다 지원하니 편한거 사용하자.

 

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.7.1</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.example</groupId>
	<artifactId>integration-complete</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>integration-complete</name>
	<description>Demo project for Spring Boot</description>

	<properties>
		<java.version>1.8</java.version>
	</properties>

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

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

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

 

Define an Integration Flow

이 가이드 샘플 어플리케이션에서, Spring Integration flow를 다음과 같이 정의할 것이다.

 

  • spring.io에 RSS feed로부터 블로그 포스트를 읽는다. 
  • 블로그 포스트들을 포스트 제목과 URL로 구성된 읽기 쉬운 String 형태로 변환한다.
  • 그 String을 파일 끝에 /tmp/si/SPringBlog에 추가한다.

 

이 흐름들을 정의하기 위해, Spring Integration의 XML namespace에서 몇개의 엘리먼트들로 Spring XML Configuration을 생성할 수 있다.

 

특히, 요구된 integeration flow에서 core, feed 그리고  file로 작업할 것이다. 

 

integeration.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:file="http://www.springframework.org/schema/integration/file"
	xmlns:feed="http://www.springframework.org/schema/integration/feed"
	xsi:schemaLocation="http://www.springframework.org/schema/integration/feed https://www.springframework.org/schema/integration/feed/spring-integration-feed.xsd
		http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/integration/file https://www.springframework.org/schema/integration/file/spring-integration-file.xsd
		http://www.springframework.org/schema/integration https://www.springframework.org/schema/integration/spring-integration.xsd">

    <feed:inbound-channel-adapter id="news" url="https://spring.io/blog.atom" auto-startup="${auto.startup:true}">
        <int:poller fixed-rate="5000"/>
    </feed:inbound-channel-adapter>

    <int:transformer
            input-channel="news"
            expression="payload.title + ' @ ' + payload.link + '#{systemProperties['line.separator']}'"
            output-channel="file"/>

    <file:outbound-channel-adapter id="file"
            mode="APPEND"
            charset="UTF-8"
            directory="/tmp/si"
            filename-generator-expression="'${feed.file.name:SpringBlog}'"/>

</beans>

 

integration.xml 추가

 

<feed:inbount-channel-adapter>

...
    <feed:inbound-channel-adapter id="news" url="https://spring.io/blog.atom" auto-startup="${auto.startup:true}">
        <int:poller fixed-rate="5000"/>
    </feed:inbound-channel-adapter>
...

inbound adapter는 폴링 한 번 마다 하나씩의 포스트들을 가져온다. 여기서 설정된 것 처럼 5초마다 포스트를 가져온다. 

 

포스트들은 new 라는 채널안에 위치한다.

 

여기서 feed라는게 정확히 무엇을 의미하나 싶어서 단어의 정확한 뜻을 찾아보았는데, "먹이를 주다, 공급하다" 정도의 의미로 해석할 수 있다. xml 태그를 직관적으로 읽어보면

 

"정보를 읽어오는데(공급하는데) 이 받아오는 정보를 inbount channel adapter에 넣어준다! 
id는 "news"고 url은 ~~ 이다. polling을 해오는 고정된 비율(시간)은 5초이다."

 

여기서 Channel Adapter 는 메시지 채널을 다른 시스템이나 전송층으로 연결해주는 엔드포인트이다. 

Spring Integration 은 다양한 Channel Adapter 를 제공하고 있다

 

https://docs.spring.io/spring-integration/reference/html/images/source-endpoint.jpg

 

https://docs.spring.io/spring-integration/reference/html/images/target-endpoint.jpg

 

inbound Channel adapter 가 source system을 메시지 채널에 연결해주고 있다.

 

outbound channel adapter가 메시지 채널을 target system으로 연결해주고 있다. 

 

<int:transformer>

  <int:transformer
    input-channel="news"
    expression="payload.title + ' @ ' + payload.link + '#{systemProperties['line.separator']}'"
    output-channel="file"/>

transformer는 메시지의 내용과 구조를 변경해서 리턴하는 역할을 한다. 

 

news 채널에서 입력을 받고(input-channel="news") entry를 변환하는데, 읽을 수 있는 String에서 entry의 제목과 link를 추출하고 병합한다.

 

<file:outbound-channel-adapter>

 <file:outbound-channel-adapter id="file"
    mode="APPEND"
    charset="UTF-8"
    directory="/tmp/si"
    filename-generator-expression="'${feed.file.name:SpringBlog}'"/>

채널에서 파일로 컨텐츠를 작성하는 outbound channel adaptor다. 특히, 여기서 설정된 것 처럼, /tmp/si/SpringBlog에 파일을 file 채널에 추가한다.

 

출처 :&nbsp;https://raw.githubusercontent.com/spring-guides/gs-integration/main/images/blogToFile.png

 

Make the Application Executable

@SpringBootApplication
@ImportResource("/integration/integration.xml")
public class IntegrationApplication {
  public static void main(String[] args) throws Exception {
    ConfigurableApplicationContext ctx = new SpringApplication(IntegrationApplication.class).run(args);
    System.out.println("Hit Enter to terminate");
    System.in.read();
    ctx.close();
  }

}

 

spring boot 에서 이제 다음을 실행하면 된다.

 

현재 spring 에서 제공하는 guide에서는 모든 설정은 xml로 했기 때문에 @ImportResource를 붙여서 해당 파일의 정보를 읽어야 한다.

 

그리고 실행한 후에 파일을 찾아보면 다음과 같이 나온다. (필자는 여러 날에 걸쳐서 여러번 작동시켰기 때문에 여러 내용이 append 되있다.)

A Bootiful Podcast: Hashicorp's Rosemary Wang on securing the intersection of apps and ops with Hashicorp Vault @ https://spring.io/blog/2022/09/08/a-bootiful-podcast-hashicorp-s-rosemary-wang-on-securing-the-intersection-of-apps-and-ops-with-hashicorp-vault
A Bootiful Podcast: Hashicorp's Rosemary Wang on securing the intersection of apps and ops with Hashicorp Vault @ https://spring.io/blog/2022/09/08/a-bootiful-podcast-hashicorp-s-rosemary-wang-on-securing-the-intersection-of-apps-and-ops-with-hashicorp-vault
This Week in Spring - September 13th, 2022 @ https://spring.io/blog/2022/09/13/this-week-in-spring-september-13th-2022
Spring Cloud Dataflow 2.9.6 Released @ https://spring.io/blog/2022/09/14/spring-cloud-dataflow-2-9-6-released
Spring Framework 6.0.0-M6 and 5.3.23 available now @ https://spring.io/blog/2022/09/15/spring-framework-6-0-0-m6-and-5-3-23-available-now
A Bootiful Podcast: big data legend, former Pivot, and friend to the Spring community, Tim Spann @ https://spring.io/blog/2022/09/15/a-bootiful-podcast-big-data-legend-former-pivot-and-friend-to-the-spring-community-tim-spann
Spring Cloud Sleuth OpenTelemetry (OTel) 1.1.0 Has Been Released @ https://spring.io/blog/2022/09/16/spring-cloud-sleuth-opentelemetry-otel-1-1-0-has-been-released
Spring Tools 4.16.0 released @ https://spring.io/blog/2022/09/16/spring-tools-4-16-0-released
Spring Data REST Vulnerability (CVE-2022-31679) @ https://spring.io/blog/2022/09/19/spring-data-rest-vulnerability-cve-2022-31679
Spring Data 2022.0.0-M6, 2021.2.3, and 2021.1.7 released @ https://spring.io/blog/2022/09/19/spring-data-2022-0-0-m6-2021-2-3-and-2021-1-7-released
Spring Security 6.0.0-M7 and 5.8.0-M3 are released @ https://spring.io/blog/2022/09/19/spring-security-6-0-0-m7-and-5-8-0-m3-are-released
This Week in Spring - September 20th, 2022 @ https://spring.io/blog/2022/09/20/this-week-in-spring-september-20th-2022
Spring for GraphQL 1.0.2 released @ https://spring.io/blog/2022/09/20/spring-for-graphql-1-0-2-released
Spring for GraphQL 1.1.0-M1 released @ https://spring.io/blog/2022/09/20/spring-for-graphql-1-1-0-m1-released
Spring for Apache Pulsar 0.1.0-M1 is now available @ https://spring.io/blog/2022/09/20/spring-for-apache-pulsar-0-1-0-m1-is-now-available
Spring Cloud Dataflow 2.10.0-M2 Released @ https://spring.io/blog/2022/09/20/spring-cloud-dataflow-2-10-0-m2-released
Spring Data 2022.0.0-M6, 2021.2.3, and 2021.1.7 released @ https://spring.io/blog/2022/09/19/spring-data-2022-0-0-m6-2021-2-3-and-2021-1-7-released
Spring Security 6.0.0-M7 and 5.8.0-M3 are released @ https://spring.io/blog/2022/09/19/spring-security-6-0-0-m7-and-5-8-0-m3-are-released
This Week in Spring - September 20th, 2022 @ https://spring.io/blog/2022/09/20/this-week-in-spring-september-20th-2022
Spring for GraphQL 1.0.2 released @ https://spring.io/blog/2022/09/20/spring-for-graphql-1-0-2-released
Spring for GraphQL 1.1.0-M1 released @ https://spring.io/blog/2022/09/20/spring-for-graphql-1-1-0-m1-released
Spring for Apache Pulsar 0.1.0-M1 is now available @ https://spring.io/blog/2022/09/20/spring-for-apache-pulsar-0-1-0-m1-is-now-available
Spring Cloud Dataflow 2.10.0-M2 Released @ https://spring.io/blog/2022/09/20/spring-cloud-dataflow-2-10-0-m2-released

 

 spring에서는 이것말고도 Testing 도 어떻게 하는지 가이드라인을 제시한다.

 

package com.example.integration;

import static org.assertj.core.api.Assertions.assertThat;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;

import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.integration.endpoint.SourcePollingChannelAdapter;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.MessageChannel;

import com.rometools.rome.feed.synd.SyndEntryImpl;

@SpringBootTest({ "auto.startup=false",   // we don't want to start the real feed
          "feed.file.name=Test" })   // use a different file
public class FlowTests {

  @Autowired
  private SourcePollingChannelAdapter newsAdapter;

  @Autowired
  private MessageChannel news;

  @Test
  public void test() throws Exception {
    assertThat(this.newsAdapter.isRunning()).isFalse();
    SyndEntryImpl syndEntry = new SyndEntryImpl();
    syndEntry.setTitle("Test Title");
    syndEntry.setLink("http://characters/frodo");
    File out = new File("/tmp/si/Test");
    out.delete();
    assertThat(out.exists()).isFalse();
    this.news.send(MessageBuilder.withPayload(syndEntry).build());
    assertThat(out.exists()).isTrue();
    BufferedReader br = new BufferedReader(new FileReader(out));
    String line = br.readLine();
    assertThat(line).isEqualTo("Test Title @ http://characters/frodo");
    br.close();
    out.delete();
  }

}

여기까지가 Spring 공식 사이트에서 제공하는 Spring Integration 가이드다. 

 

솔직히 이걸로는 어떻게 사용할 지 감이 안 잡힌다. 무엇보다도 이 가이드 문서는 아~주 간단하게 사용하는 법을 제시한 것이지 내부적으로 어떤 기능이 있는지, 어떻게 사용해야 하는지는 reference docs를 봐야 할 것이다. (이걸론 개발 못해...)

 

(궁금하면 여기 클릭)