레이블이 Java인 게시물을 표시합니다. 모든 게시물 표시
레이블이 Java인 게시물을 표시합니다. 모든 게시물 표시

2020년 5월 12일 화요일

Quarkus + Apache Camel 소개

Quarkus + Apache Camel 소개

들어 가며

최근 Red Hat 은 GraalVM 기술의 등장에 발맞춰 Quarkus 오픈소스 프로젝트를 시작했습니다. 현재 GraalVM 과 함께 지속적으로 발전하는 Quarkus 는 자바 언어의 실행 방식을 확장합니다. 이 글에서는 Quarkus 를 이용해 Apache Camel 자바 애플리케이션이 어떻게 개선되는지 실험해 보고자 합니다.

Quarkus 소개

Red Hat 이 주도하는 오픈소스 프로젝트인 Quarkus 는 자바 오픈소스 및 자바 표준을 기반으로 JVM 및 native image 환경에 최적화된 애플리케이션을 개발 및 빌드할 있게 하는 Kubernetes 를 위한 자바 프레임 워크입니다. Quarkus는, Serverless, Microservice, Container, Kubernetes, FaaS 와 Cloud 를 염두에 두고 설계되어, 자바 애플리케이션을 실행하기 위한 효과적인 해결책을 제공합니다. Quarkus 는 자바 애플리케이션의 메모리 사용량을 줄여주고, 시작 시간을 빠르게 합니다. 이로써 C 나 Golang 등 native image 로 빌드되는 언어 만이 가질 수 있었던 장점을 자바 언어로 손쉽게 구현할 수 있게 합니다.

Red Hat 은 Quarkus 를 Red Hat Runtimes 에 포함해 GA 했습니다.

Apache Camel 소개

Red Hat 이 주도하는 오픈소스인 Apache Camel 은 기업 통합 패턴(EIP, Enterprise Inrgration Patters) 구현체로 경량의 통합 프레임워크입니다. 그러므로 Apache Camel 은 애플리케이션에 함께 패키징이 가능합니다. Apache Camel 은 내부에 라우터 엔진, 프로세서, 컴포넌트, 메시징 시스템을 포함해, 애플리케이션의 내부를 외부 세계와 손쉽게 인터페이스할 수 있게 해줍니다. 즉 Apache Camel 은 애플리케이션, 시스템, 서비스들 사이에서 데이터(Data)와 기능(Function)을 통합(인터페이스)하는 중재자(Mediator)로서 역할합니다.

Red Hat 은 Apache Camel 을 Red Hat Fuse 에 포함해 GA 했습니다.

Quarkus Apache Camel 프로젝트 준비

두 오픈소스 프로젝트 Quarkus 와 Apache Camel 은, Red Hat 주도로, 컨테이너 기반 MSA(Microservice Architecture)와 통합(Integration)을 손쉽게 구현할 수 있게 서로 협력하고 발전하고 있습니다. 그러므로 Quarkus Apache Camel 프로젝트의 생성도 두 기술이 함께 잘 녹아 있는 절차를 따릅니다.

이 문서의 실행 명령들은 필자의 MacBook(macOS) 에서 실행하고 검증했습니다. 그러므로 이 문서를 따라 테스트 하는 독자는 Linux, macOS, Windows 10(WSL 2 설치) 환경에서 테스트할 것을 권장드립니다.

Quarkus Apache Camel 애플리케이션도, 자바 애플리케이션이므로, 자바 애플리케이션 빌드 도구가 필요합니다.

  • JDK 11+ 과 JAVA_HOME 경로 등록
  • Apache Maven 3.6.2+

Quarkus 로 native image 를 빌드할 경우, 추가로 다음의 플랫폼 환경과 도구가 필요합니다.

  • 64 bit OS (Linux 64bit x86 or ARM, Mac OS X 64bit, Windows 64bit)
  • 시스템 메모리 14 GB+
  • GraalVM Community Edition 20.0.0 과 GRAALVM_HOME 경로 등록
  • GraalVM Native image generator (native-image)

GraalVM 은 아래 사이트에서 플랫폼에 맞는 실행 바이너리를 다운로드 합니다.

GraalVM 설치 후, GraalVM native image generator 는 다음 명령을 실행해 설치합니다.

$ cd $GRAALVM_HOME/bin
$ ./gu install native-image

주의

GraalVM 설치 후, 각 플랫폼 별로 native image 빌드를 위한 추가 개발 도구를 설치해야 합니다. 다음 사이트를 참고해 추가 개발 도구를 설치합니다.

위 사이트에 설명이 없는 CentOS 7 나 RHEL 7 플랫폼의 추가 개발 도구는 다음 명령을 실행해 설치합니다.

$ sudo yum group install -y "Development Tools"
$ sudo yum install -y zlib-devel

유스케이스

이 글의 Apache Camel 애플리케이션은 아래와 같은 통합 유스케이스의 구현을 실험했습니다. 이 통합 유스케이스는, CSV 파일을 읽어 JSON 포맷으로 Rest API 에 전달하는 절차로, 기업에서 실무적으로도 사용할 수 있을 것입니다. 실무적 유스케이스를 사용한 이유는 Apache Camel의 실무적 통합 컴포넌트와 의존 라이브러리들이 Quarkus 를 이용해 Native image 로 잘 전환되는 지를 검증하기 위해서였습니다.

  1. CVS 데이터 생산자는 5초마다 실행됩니다.
  2. 실행된 데이터 생산자는 두 줄의 CSV UserInfo 레코드를 애플리케이션 실행 디렉토리 아래 input 디렉토리에 userinfo.csv 파일로 기록합니다.
  3. 파일 소비자는 input 디렉토리에서 생성된 userinfo.csv 파일을 읽고 분할기를 이용해 두 줄의 레코드를 두 개의 UserInfo 자바 객체로 변환 (unmarshal) 합니다.
  4. 파일 소비자는 UserInfo 자바 객체의 readCount 멤버 변수 값을 파일 소비자가 읽은 순서로 갱신합니다.
  5. 파일 소비자는 웹서비스의 Rest API(http://localhost:8080/userinfo) 를 호출해 UserInfo 정보를 JSON 메시지로 전송합니다.
  6. 웹 서비스는 수신한 JSON 메시지를 콘솔 로그로 기록합니다.
  7. 콘솔 로그에서 CSV 파일 레코드 생성 시각과 파일 소비자가 읽은 순서 등을 확인합니다.

이 유스케이스의 기업 통합 패턴(EIP, Enterprise Integration Patterns) 다이어그램은 다음과 같습니다.

Quarkus Apache Camel 프로젝트

지금까지 기본 설명을 마쳤으므로, 이제 Quarkus Apache Camel 프로젝트 생성 절차를 설명합니다. Apache Camel Quarkus 프로젝트를 가장 손쉽게 시작하는 방법 중 하나는 다음 사이트에서 필요한 컴포넌트를 선택해 Qurkus 기능이 포함된 자바 프로젝트를 생성하는 것입니다.

위 사이트에 접속해 다음과 같이 생성할 프로젝트의 정보를 입력하고, 옵션을 선택합니다. 애플리케이션 상세 (Configure your application details) 에서 다음과 같이 입력합니다.

  • Group : com.demo
  • Artifact : quarkus-camel-demo
  • Build Tool : Maven

확장 선택 (Pick your extensions) 에서 다음의 확장을 선택합니다.

  • RESTEasy Jackson
  • Camel Quarkus Core
  • Camel Quarkus Timer
  • Camel Quarkus File
  • Camel Quarkus Gson
  • Camel Quarkus Bindy
  • Camel Quarkus Rest
  • Camel Quarkus Http
  • Camel Quarkus Log
  • Camel Caffeine LRUCache

"Generate your application" 버튼을 클릭해 프로젝트를 생성합니다.

다음과 같은 프로젝트 생성 화면에서 생성된 프로젝트 ZIP 파일을 다운로드합니다.

좀더 간편한 방법으로 Quarkus Code Starter 사이트에서 프로젝트를 생성하지 않고, Quarkus Maven plugin 을 이용해 다음과 같이 명령 행에서 프로젝트를 생성하는 방법도 있습니다. 이 방법을 사용하면 좀더 신속하게 프로젝트를 생성할 수 있습니다.

$ mvn io.quarkus:quarkus-maven-plugin:1.4.1.Final:create \
  -DprojectGroupId=com.demo \
  -DprojectArtifactId=quarkus-camel-demo \
  -DprojectVersion=1.0.0 \
  -DclassName="com.demo.UserInfoResource" \
  -Dpath="/userinfo" \
  -Dextensions="
    quarkus-core,
    quarkus-resteasy-jackson,
    camel-quarkus-timer,
    camel-quarkus-file,
    camel-quarkus-gson,
    camel-quarkus-bindy,
    camel-quarkus-rest,
    camel-quarkus-http,
    camel-quarkus-log,
    camel-quarkus-caffeine-lrucache"

위 과정을 마친 후, 선호하는 통합 개발 환경(IDE) 에 프로젝트를 임포트해 개발을 시작합니다.

이 과정을 통해 완성된 프로젝트와 추가된 소스를 확인하려면 필자의 소스를 참조해 주십시오.

Apache Camel DSL

위 통합 유스케이스는 Apache Camel Java DSL 로 다음과 같이 구현할 수 있습니다. (Apache Camel DSL 은 필자의 바른모 블로그Camel in Action 을 참조해 주십시오.)

package com.demo;  
  
import org.apache.camel.Exchange;  
import org.apache.camel.Processor;  
import org.apache.camel.builder.RouteBuilder;  
import org.apache.camel.dataformat.bindy.csv.BindyCsvDataFormat;  
import org.apache.camel.model.rest.RestBindingMode;  
  
import java.util.concurrent.atomic.AtomicInteger;  
  
  
public class CamelRouteBuilder extends RouteBuilder {  
  
  static AtomicInteger readCount = new AtomicInteger();  
  
  public void configure() {  
  
  BindyCsvDataFormat biindy = new BindyCsvDataFormat(com.demo.UserInfo.class);  
  
    restConfiguration()  
      .host("localhost")  
      .port(8080)  
      .jsonDataFormat("json-gson")  
      .bindingMode(RestBindingMode.json);  
  
    from("timer://csv?period=5000")  
      .routeId("csvProducerRoute")  
      .setBody(simple("Gildong,Hong,${date:now}\nWoochi,Jun,${date:now}"))  
      .to("file://input?fileName=userinfo.csv")  
      .log("New userinfo.csv saved.");  
  
    from("file://input?fileName=userinfo.csv&delete=true")  
      .routeId("csvConsumerRoute")  
      .log("Loaded userinfo.csv records\n${body}")  
      .unmarshal(biindy)  
      .split(body())  
      .parallelProcessing()  
      .process(new Processor() {  
        @Override  
        public void process(Exchange exchange) throws Exception {  
          UserInfo userInfo = exchange.getIn().getBody(UserInfo.class);  
          userInfo.setReadCount(readCount.incrementAndGet());  
        }  
     })  
     .log("Call Rest API using ${body} ...")  
     .to("rest:post:userinfo");  
  }  
}

Quarkus 는, 위와 같이 Camel DSL 클래스를 구현만 하면, 애플리케이션 시작 시 Camel DSL 자바 클래스를 자동으로 찾아 실행합니다. Spring Boot 의 경우, 애플리케이션 시작에 Camel DSL 을 실행하기 위해서는, @Service 나 @Component 애노테이션을 Camel DSL 클래스에 지정해 Spring Bean 임을 선언해야 합니다. 그런데 Quarkus 는 이런 애노테이션 지정 없이 클래스가 RouteBuilder 를 상속하면 자동으로 실행됩니다. Quarkus 의 이런 관례는, 처음 Quarkus 를 경험하는 개발자들에게 도리어 혼란을 야기할 수도 있을 것 같습니다. 관례의도 가 조화롭게 잘 정의된 프레임워크가 좋은 프레임워크이기 때문입니다.

Rest API 서비스

위 통합 유스케이스의 Rest API 서비스는 Java EE JAX-RS 표준 애노테이션을 이용해 다음과 같이 구현합니다. (참고로 Lombok 프레임워크도 Quarkus 에서 native image 로 잘 빌드되는지 실험하기 위해 일부러 사용했습니다.)

package com.demo;  
  
import lombok.extern.slf4j.Slf4j;  
  
import javax.ws.rs.Consumes;  
import javax.ws.rs.POST;  
import javax.ws.rs.Path;  
import javax.ws.rs.Produces;  
import javax.ws.rs.core.MediaType;  
  
@Slf4j  
@Path("/userinfo")  
public class UserInfoResource {  
  
  @POST  
 @Consumes(MediaType.APPLICATION_JSON)  
  @Produces(MediaType.APPLICATION_JSON)  
  public UserInfo hello(UserInfo userInfo) {  
 log.info("Received : " + userInfo);  
    return userInfo;  
  }  
}

Quarkus 가 사용하는 Java EE JAX-RS 애노테이션은, Spring Web 애노테이션과 다르지만, 가급적 표준을 따르는 것이 올바른 방법일 것 같기도 합니다. 그럼에도 Quarkus 프로젝트에서 조만간 Spring 호환 애노테이션도 제공할 것으로 보이는 데, 그렇게 되면 두 애노테이션 중 편리한 애노테이션을 사용할 수 있을 것입니다. 참고로 Quarkus 는 기본적으로 Vert.x 기술을 기반해 웹 서비스를 제공한다는 점도 알아두면 유용할 것입니다.

애플리케이션 빌드

Quarkus 를 이용하면 자바 애플리케이션을 세 가지 실행 형태로 빌드할 수 있습니다. 하나는 전통적인 Jar 실행 패키지고, 하나는 Native image 실행 바이너리입니다. 그리고 Container image 로도 빌드합니다.

Jar 빌드

Jar 빌드는 전통적인 자바 빌드 방법으로 아래와 같이 maven 명령을 이용합니다. (-DskipTests 로 테스트 단계를 건너뛰는 옵션을 지정했습니다. 이 옵션을 사용하지 않으면 테스트가 진행됩니다.)

$ ./mvnw package -DskipTests
$ ls -alh target
...
-rw-r--r--    1 jcha  staff   290K  5  5 01:38 quarkus-camel-demo-1.0.0-runner.jar
-rw-r--r--    1 jcha  staff    22K  5  5 01:38 quarkus-camel-demo-1.0.0.jar
drwxr-xr-x  129 jcha  staff   4.0K  5  5 01:38 lib
...
$ du -sh lib
 37M	lib
...

빌드가 성공하면 실행 jar 파일(*-runner.jar), 클래스를 포함한 jar, 그리고 의존 라이브러리를 포함하는 lib 디렉토리가 생성됩니다. 생성된 실행 jar 파일의 크기는 290 KB 크기로, 실제 소스가 컴파일된 jar 22 KB 와 Quarkus 클래스 268 KB 가 포함됩니다. Quarkus 클래스 영역은 애플리케이션이 커져도 동일한 크기를 가질 것입니다. Quarkus 클래스들은 실행 jar 를 실행하면 실제 애플리케이션을 탑재하고 시작시킵니다. 실행은 아래 Jar 패키지 실행을 참조해 주십시오.

Native image 빌드

Native image 빌드는 자바 애플리케이션이 JVM 의존 없는 단일 실행 바이너러로 빌드되는 것을 의미합니다. 즉 단일 실행 바이너리가 JVM 기능을 포함합니다. 빌드 방법은 아래와 같이 Maven 을 이용해 빌드합니다. Jar 빌드와 다른 점은 Maven 빌드에 native 프로파일 옵션인 -Pnative 을 추가로 지정했다는 점입니다. Native image 빌드에 사용하는 Maven native 프로파일은 Quarkus Code Starter 나 Quarkus maven plugin 이 프로젝트 생성할 때 Maven pom.xml 에 자동으로 구성합니다.

$ ./mvnw package -Pnative -DskipTests
...
[quarkus-camel-demo-1.0.0-runner:6453]      [total]: 147,334.21 ms, 11.75 GB
...
$ ls -alh target
drwxr-xr-x    4 jcha  staff   128B  5  5 10:29 quarkus-camel-demo-1.0.0-native-image-source-jar
-rwxr-xr-x    1 jcha  staff    87M  5  5 10:29 quarkus-camel-demo-1.0.0-runner
...
$ file target/quarkus-camel-demo-1.0.0-runner
target/quarkus-camel-demo-1.0.0-runner: Mach-O 64-bit executable x86_64

$ otool -L target/quarkus-camel-demo-1.0.0-runner
target/quarkus-camel-demo-1.0.0-runner:
    /usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 902.1.0)
    /System/Library/Frameworks/CoreServices.framework/Versions/A/CoreServices (compatibility version 1.0.0, current version 1069.22.0)
    /System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation (compatibility version 150.0.0, current version 1675.129.0)
    /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1281.100.1)
    /usr/lib/libz.1.dylib (compatibility version 1.0.0, current version 1.2.11)

Native image 빌드는 상당하 많은 시스템 메모리를 요구합니다. 필자의 개발 PC(MacBook) 빌드에서는 최종적으로 11.75 GB의 시스템 메모리가 사용됐다고 기록이 남았습니다. GraalVM native image 문서를 보면 Native image 빌드를 위해서는 시스템 메모리가 14 GB 이상이 필요하다고 합니다. GraalVM 팀은 빌드 과정 메모리 최적화를 안한 것인지 못하는 것인지 궁금합니다. (이런 이유로 필자의 라즈베리 파이에서는 빌드 환경은 구성 가능했지만 직접적으로 Native image 빌드는 수행할 수 없었습니다. )

빌드가 성공하면 실행 바이너리 파일(*-runner)과 자바 애플리케이션 클래스를 포함한 jar 파일이 생성됩니다. Native image 빌드로 생성된 실행 바이너리는 87 MB 크기로 JVM 기능을 포함합니다. 실행 바이너리는 jar 패키지, 의존 jar, 그리고 JVM 영역을 모두 포함하므로, 애플리케이션 클래스 jar 와 lib 37 MB 을 빼면, 실행 바이너리에서 순수 JVM 영역은 약 50 MB 정도로 볼 수 있을 것입니다.

실행 바이너리 실행 시, 모든 실행 바이너리가 실행 메모리에 적재되지는 않으므로, 실제 시스템 메모리 사용은 실행 바이너리 크기보다 훨씬 적습니다.

필자는 개발 PC 는 MacBook 이므로, 생성된 실행 바리너리를 file 명령을 확인해 본 결과, 파일 형식이 Mach-O 64-bit executable x86_64 인 Native image 파일(*-runner)임을 확인한 수 있었습니다.

CentOS 7 에서도 빌드를 해 보았는데, 이 경우 실행 바이너리의 의존은 다음과 같았습니다.

$ ldd target/quarkus-camel-demo-1.0.0-runner
        linux-vdso.so.1 =>  (0x00007ffdc6f93000)
        libstdc++.so.6 => /lib64/libstdc++.so.6 (0x00007faa18c05000)
        libpthread.so.0 => /lib64/libpthread.so.0 (0x00007faa189e9000)
        libdl.so.2 => /lib64/libdl.so.2 (0x00007faa187e5000)
        libz.so.1 => /lib64/libz.so.1 (0x00007faa185cf000)
        librt.so.1 => /lib64/librt.so.1 (0x00007faa183c7000)
        libc.so.6 => /lib64/libc.so.6 (0x00007faa17ff9000)
        libm.so.6 => /lib64/libm.so.6 (0x00007faa17cf7000)
        /lib64/ld-linux-x86-64.so.2 (0x00007faa18f0c000)
        libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007faa17ae1000) 

위 결과로 Native image 로 빌드된 자바 애플리케이션은 플랫폼의 기본 라이브러리들에 의존하고 있음을 알 수 있었습니다. 그런데 만약 자바 애플리케이션의 Native image 영역 중 반드시 최적화가 필요하지 않은 JVM 영역이 플랫폼에 맞도록 공유 라이브러리로 분리될 수 있다면, 자바 애플리케이션의 Native image 크기를 더 줄일 수 있지 않을까 생각해 봅니다.

Container image 빌드

Container image 빌드는 Native image 빌드를 실행한 플랫폼 OS 의 실행 바이너리가 아닌 컨테이너 실행 환경인 Linux 의 실행 바이너리를 생성한다는 점이 다릅니다.
필자는 필자의 개발 PC 에서 Container image 빌드를 하기 위해 Mac 용 “Docker desktop” 을 설치했습니다. 다음은 필자가 사용한 “Docker desktop” 버전은 다음과 같습니다.

Container image 빌드는 Native image 빌드에 -Dquarkus.native.container-build=true 옵션을 추가하면 됩니다.

$ ./mvnw package -Pnative -Dquarkus.native.container-build=true -DskipTests
...
[quarkus-camel-demo-1.0.0-runner:24]      [total]: 181,112.75 ms
[INFO] [io.quarkus.deployment.QuarkusAugmentor] Quarkus augmentation completed in 191029ms
...
$ ls -alh target
drwxr-xr-x    4 jcha  staff   128B  5  7 18:57 quarkus-camel-demo-1.0.0-native-image-source-jar
-rwxr-xr-x    1 jcha  staff    89M  5  7 18:57 quarkus-camel-demo-1.0.0-runner
...
$ file target/quarkus-camel-demo-1.0.0-runner
target/quarkus-camel-demo-1.0.0-runner: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=b951368d112bf5a6589d0c6fe8e67712cd9ce87f, with debug_info, not stripped

$ otool -L target/quarkus-camel-demo-1.0.0-runner
llvm-objdump: error: target/quarkus-camel-demo-1.0.0-runner': object is not a Mach-O file type.

위 콘솔 로그에서 보듯이 필자의 개발 PC 에서는 Conatiner image 빌드가 약 3분 정도 소요됐습니다. 그리고 필자의 개발 PC 가 MacBook(macOS) 임에도 불구하고, 생성된 실행 바이너리(*-runner)의 파일 형식은 ELF 64-bit LSB executable 로 컨테이너 이미지에 포함할 수 있는 Linux 용 바이너리가 생성됐습니다. 기대한 대로 생성된 실행 바이너리는 macOS 의 otool 로도 읽을 수 없습니다.

생성된 컨테이너 용 실행 바이너리는 다음 절차를 수행해 최종적으로 컨테이너 이미지로 빌드합니다.

$ docker build -f src/main/docker/Dockerfile.native -t quarkus/quarkus-camel-demo .
...
Successfully built 7a6b92b9eaa3
Successfully tagged quarkus/quarkus-camel-demo:latest
$ docker images                                                                    
REPOSITORY                                                           TAG                 IMAGE ID            CREATED              SIZE
quarkus/quarkus-camel-demo                                           latest              7a6b92b9eaa3        About a minute ago   294MB
registry.access.redhat.com/ubi8/ubi-minimal                          8.1                 91d23a64fdf2        5 weeks ago         107MB

...

최종적으로 생성된 자바 애플리케이션 컨테이너 이미지는 294MB 크기입니다. 이 컨테이너 이미지의 베이스 이미지인 레드햇 유니버셜 베이스 이미지(ubi-minimal:8.1)는 107 MB 입니다.

애플리케이션 실행

이 글에서 자바 애플리케이션은 세 가지 형태로 빌드됐습니다. 하나는 전통적인 Jar 패키지이고 다른 하나는 Native image 이고, 마지막으로 Container image 입니다. 각각의 실행 방법은 다음과 같습니다.

Jar 애플리케이션 실행

다음은 필자의 개발 PC 에서 빌드된 jar 실행 패키지 자바 애플리케이션을 실행한 결과입니다.

$ java -jar target/quarkus-camel-demo-1.0.0-runner.jar
__  ____  __  _____   ___  __ ____  ______ 
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ 
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \   
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/   
2020-05-05 01:38:39,595 INFO  [org.apa.cam.sup.LRUCacheFactory] (main) Detected and using LURCacheFactory: camel-caffeine-lrucache
2020-05-05 01:38:39,800 INFO  [org.apa.cam.imp.eng.AbstractCamelContext] (main) Apache Camel 3.1.0 (CamelContext: camel-1) is starting
2020-05-05 01:38:39,802 INFO  [org.apa.cam.imp.eng.DefaultManagementStrategy] (main) JMX is disabled
2020-05-05 01:38:39,889 INFO  [org.apa.cam.imp.eng.AbstractCamelContext] (main) StreamCaching is not in use. If using streams then its recommended to enable stream caching. See more details at http://camel.apache.org/stream-caching.html
2020-05-05 01:38:40,056 INFO  [org.apa.cam.com.htt.HttpComponent] (main) Created ClientConnectionManager org.apache.http.impl.conn.PoolingHttpClientConnectionManager@6724cdec
2020-05-05 01:38:40,115 INFO  [org.apa.cam.imp.eng.AbstractCamelContext] (main) Route: csvProducerRoute started and consuming from: timer://csv
2020-05-05 01:38:40,120 INFO  [org.apa.cam.imp.eng.AbstractCamelContext] (main) Route: csvConsumerRoute started and consuming from: file://input
2020-05-05 01:38:40,125 INFO  [org.apa.cam.imp.eng.AbstractCamelContext] (main) Total 2 routes, of which 2 are started
2020-05-05 01:38:40,127 INFO  [org.apa.cam.imp.eng.AbstractCamelContext] (main) Apache Camel 3.1.0 (CamelContext: camel-1) started in 0.325 seconds
...

Quakus 로 빌드한 Jar 실행 패키지 자바 애플리케이션의 시작 시간은 0.325 초였습니다. 일반적으로 자바 애플리케이션의 시작은 JVM 의 실행 초기화와 프레임워크의 초기화를 포함합니다. 그러므로 최소한 수 초 정도 시작 시간이 필요합니다. 그런데 이 과정도 Qurkus 는 최적해 실행 패키지의 시작 시간이 개선됐음을 볼 수 있었습니다.

Native image 애플리케이션 실행

다음은 필자의 개발 PC 에서 Native image 자바 애플리케이션을 실행한 결과입니다.

$ ./target/quarkus-camel-demo-1.0.0-runner              
__  ____  __  _____   ___  __ ____  ______ 
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ 
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \   
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/   
2020-05-05 12:51:21,838 INFO  [org.apa.cam.imp.eng.AbstractCamelContext] (main) Apache Camel 3.1.0 (CamelContext: camel-1) is starting
2020-05-05 12:51:21,838 INFO  [org.apa.cam.imp.eng.DefaultManagementStrategy] (main) JMX is disabled
2020-05-05 12:51:21,842 INFO  [org.apa.cam.imp.eng.AbstractCamelContext] (main) StreamCaching is not in use. If using streams then its recommended to enable stream caching. See more details at http://camel.apache.org/stream-caching.html
2020-05-05 12:51:21,844 INFO  [org.apa.cam.com.htt.HttpComponent] (main) Created ClientConnectionManager org.apache.http.impl.conn.PoolingHttpClientConnectionManager@1127f7828
2020-05-05 12:51:21,851 INFO  [org.apa.cam.imp.eng.AbstractCamelContext] (main) Route: csvProducerRoute started and consuming from: timer://csv
2020-05-05 12:51:21,851 INFO  [org.apa.cam.imp.eng.AbstractCamelContext] (main) Route: csvConsumerRoute started and consuming from: file://input
2020-05-05 12:51:21,851 INFO  [org.apa.cam.imp.eng.AbstractCamelContext] (main) Total 2 routes, of which 2 are started
2020-05-05 12:51:21,851 INFO  [org.apa.cam.imp.eng.AbstractCamelContext] (main) Apache Camel 3.1.0 (CamelContext: camel-1) started in 0.013 seconds
...

애플리케이션 실행 결과, Native image 형식으로 빌드한 자바 애플리케이션의 시작 시간은 0.013 초였습니다. 즉 Native image 자바 애플리케이션은 약 13 밀리초 만에 시작을 완료했습니다. 일반적인 자바 애플리케이션은 시작 시간이 수 초 이상입니다. 이 결과로 Native image 로 빌드된 자바 애플리케이션은 기존의 자바 애플리케이션보다 약 100 배 시작 시간이 빨라짐을 확인할 수 있었습니다.

애플리케이션의 실행 시간 (및 종료 시간) 이 빨라지면 신속한 배포가 가능하고, 배포에 따른 중단 시간을 단축할 수 있습니다. 이런 기술적 특징은 Serverless 등 컨테이너 기술에 필수적 조건입니다.

Container image 애플리케이션 실행

다음은 필자의 개발 PC 에서 빌드된 자바 애플리케이션 컨테이너 이미지를 실행한 결과입니다.

$ docker run -i --rm -p 8080:8080 quarkus/quarkus-camel-demo
__  ____  __  _____   ___  __ ____  ______ 
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ 
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \   
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/   
2020-05-07 10:19:24,690 INFO  [org.apa.cam.imp.eng.AbstractCamelContext] (main) Apache Camel 3.1.0 (CamelContext: camel-1) is starting
2020-05-07 10:19:24,690 INFO  [org.apa.cam.imp.eng.DefaultManagementStrategy] (main) JMX is disabled
2020-05-07 10:19:24,692 INFO  [org.apa.cam.imp.eng.AbstractCamelContext] (main) StreamCaching is not in use. If using streams then its recommended to enable stream caching. See more details at http://camel.apache.org/stream-caching.html
2020-05-07 10:19:24,693 INFO  [org.apa.cam.com.htt.HttpComponent] (main) Created ClientConnectionManager org.apache.http.impl.conn.PoolingHttpClientConnectionManager@7f9091eb2868
2020-05-07 10:19:24,699 INFO  [org.apa.cam.imp.eng.AbstractCamelContext] (main) Route: csvProducerRoute started and consuming from: timer://csv
2020-05-07 10:19:24,699 INFO  [org.apa.cam.imp.eng.AbstractCamelContext] (main) Route: csvConsumerRoute started and consuming from: file://input
2020-05-07 10:19:24,700 INFO  [org.apa.cam.imp.eng.AbstractCamelContext] (main) Total 2 routes, of which 2 are started
2020-05-07 10:19:24,701 INFO  [org.apa.cam.imp.eng.AbstractCamelContext] (main) Apache Camel 3.1.0 (CamelContext: camel-1) started in 0.010 seconds
2020-05-07 10:19:24,707 INFO  [io.quarkus] (main) =quarkus-camel-demo 1.0.0 (powered by Quarkus 1.4.1.Final) started in 0.027s. 
...

Container image 실행 결과도 Native image 형식으로 빌드한 자바 애플리케이션의 시작 시간은 0.013 초과 유사하게 0.027 초 였습니다. 즉 Container image 자바 애플리케이션은 약 27 밀리초 만에 시작을 완료했습니다. Native image 를 컨테이너 이미지로 빌드하면서 컨테이너 시작 단계가 포함돼 순수 Native image 시작 시간보다 길어지긴 했지만, Quarkus 를 이용해 Native image 을 Container image 로 전환한 자바 애플리케이션은 초 당 40 개 정도 실행 할 수 있다는 가능성을 확인할 수 있었습니다.

애플리케이션 실행 시간 비교

Quarkus 를 이용한 자바 애플리케이션 빌드 실행 시간 실험은 다음과 다음 결과로 요약됩니다.

빌드 결과물 시작 시간 설명
Jar 패키지 0.325 초 초당 3 개 애플리케이션 실행 가능
Native image 0.013 초 초당 80 개 애플리케이션 실행 가능
Container image 0.027 초 초당 40 개 애플리케이션 실행 가능

Qurakus 프로젝트 웹 사이트 설명

Quarkus 프로젝트에서 설명한 응답 시간 자료도 이번 실험 결과와 유사한 결과를 설명하고 있었습니다.

애플리케이션 메모리

Jar 애플리케이션

애플리케이션 메모리 상태를 확인하기 위해 Quarkus 를 이용해 빌드한 Jar 애플리케이션의 실행합니다.

$ java -jar target/quarkus-camel-demo-1.0.0-runner.jar
...

다른 실행 창에서 Jar 애플리케이션 메모리 사용을 확인합니다.

$ ps x -o pid,rss,vsz,command | grep quarkus-camel-demo-1.0.0-runner.jar
28338 342832 25454416 /usr/bin/java -jar target/quarkus-camel-demo-1.0.0-runner.jar

상주 메모리는 약 334 MB, 가상 메모리는 약 24 GB 정도 사용하는 것으로 표시됐습니다.

아래는 필자의 개발 PC 자원 모니터링 화면에서 본 Jar 애플리케이션의 메모리 사용 현황입니다.

위 결과는 271.9 MB 로 표시됐습니다.

Native image 애플리케이션

애플리케이션 메모리 상태를 확인하기 위해 Quarkus 를 이용해 생성한 생성한 Native image 애플리케이션을 실행합니다.

$ target/quarkus-camel-demo-1.0.0-runner
...

다른 실행 창에서 Native image 애플리케이션 메모리 사용을 확인합니다.

$ ps x -o pid,rss,vsz,command | grep quarkus-camel-demo-1.0.0-runner
28521  51016  4746796  target/quarkus-camel-demo-1.0.0-runner

상주 메모리는 약 51 MB, 가상 메모리는 약 4.6 GB 정도 사용하는 것으로 표시됐습니다.

아래는 필자의 개발 PC 자원 모니터링 화면에서 본 Jar 애플리케이션의 메모리 사용 현황입니다.

위 결과는 31.3 MB 로 표시됐습니다.

Native image 자바 애플리케이션은 Jar 애플리케이션보다 9 배 정도 적은 메모리로 구동되는 것을 확인 할 수 있었습니다. 그러므로 동일한 기능을 하는 애플리케이션을 Native image 로 빌드해 실행하면 동일 장비에 9 배 이상 밀집도를 갖고 실행 할 수 있으므로, Native image 빌드는 시스템 자원 효율을 9 배 이상 향상시킨다고 볼 수 있습니다.

애플리케이션 실행 메모리 비교

Quarkus 를 이용한 자바 애플리케이 빌드 메모리 사용은 실험은 다음과 다음 결과로 요약됩니다.

빌드 결과물 사용 메모리 설명
Jar 패키지 334 MB 일반적인 자바 애플리케이션 사용 메모리
Native image 51 MB 6 배 절감된 메모리 사용

Qurakus 프로젝트 웹 사이트 설명

Quarkus 프로젝트에서 설명한 메모리 자료도 이번 실험 결과와 유사한 결과를 설명하고 있었습니다.

라즈베리 파이(Raspberry PI)

자바 애플리케이션을 적은 메모리로 빠르게 실행할 수 있다면 , 라즈베리 파이 같은 사물 인터넷(IOT) 장비의 언어로 자바 언어가 상당히 매력적인 언어가 될 수 있을 것입니다. 필자는 이런 생각을 기반으로 Quarkus 개발 환경을 라즈베리 파이에 구성해 동일한 애플리케이션을 빌드해 시도해 보았습니다. 그러면서 아래와 같은 문제를 만났습니다.

  • 라즈베리 파이 32bit OS
  • ARM 프로세서
  • 시스템 메모리 한계 (최대 4 GB)

위 문제 중 32bit OS 문제는 라즈베리파이 용 Ububtu 64bit OS 를 설치해서 해결했고, ARM 프로세서 문제는 ARM 기반 Open JDK 11, GraalVM 을 설치해 해결했습니다. 그러나 시스템 메모리 부족 문제로 Native image 빌드는 라즈베리 파이 내에서 완료할 수 없었습니다. (그 이유는 Native image 빌드를 위해서는 14 G 이상의 시스템 메모리가 필요하기 때문입니다.) 그러나 ARM 기반 서버도 최근에는 쉽게 구할 수 있으므로 메모리가 충분한 ARM 기반 서버에서 Native image 를 생성해 라즈베리파이에서 실행하면 되므로 굳이 라즈베리 파이에서 Native image 빌드까지 하지 않더라도 활용하는 대는 문제가 없을 것으로 보입니다. 그러므로 자바 언어는 라즈베리 파이와 같은 IOT 영역에서도 곧 큰 힘을 발휘할 때가 올 것으로 전망합니다.

다음은 라즈베리 파이에서 Quarkus 로 통합 애플리케이션을 빌드한 결과입니다.

$ ls -alh target/
...
-rw-rw-r--  1 ubuntu ubuntu 278K  5월  4 13:25 quarkus-camel-demo-1.0.0-runner.jar
-rw-rw-r--  1 ubuntu ubuntu 8.9K  5월  4 14:12 quarkus-camel-demo-1.0.0.jar
drwxrwxr-x  2 ubuntu ubuntu  16K  5월  4 13:25 lib
...
$ du -sh target/lib
37M     target/lib

Quarkus 로 Jar 실행 패키지 빌드 결과는 macOS 나 CentOS 나 크게 다르지 않았습니다. 이 결과는 자바 언어가 플랫폼 독립적이라는 점을 잘 보이고 있는 것입니다.

다음은 라즈베리 파이에서 Jar 패키지 통합 애플리케이션을 실행한 결과입니다.

$ java -jar target/quarkus-camel-demo-1.0.0-runner.jar 
__  ____  __  _____   ___  __ ____  ______ 
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ 
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \   
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/   
2020-05-05 01:32:44,508 INFO  [org.apa.cam.sup.LRUCacheFactory] (main) Detected and using LURCacheFactory: camel-caffeine-lrucache
2020-05-05 01:32:49,434 INFO  [org.apa.cam.mai.BaseMainSupport] (main) Auto-configuration summary:
2020-05-05 01:32:49,438 INFO  [org.apa.cam.mai.BaseMainSupport] (main)  camel.context.name=quarkus-camel-demo
2020-05-05 01:32:49,669 INFO  [org.apa.cam.imp.eng.AbstractCamelContext] (main) Apache Camel 3.1.0 (CamelContext: quarkus-camel-demo) is starting
2020-05-05 01:32:49,682 INFO  [org.apa.cam.imp.eng.DefaultManagementStrategy] (main) JMX is disabled
2020-05-05 01:32:50,590 INFO  [org.apa.cam.imp.eng.AbstractCamelContext] (main) StreamCaching is not in use. If using streams then its recommended to enable stream caching. See more details at http://camel.apache.org/stream-caching.html
2020-05-05 01:32:52,444 INFO  [org.apa.cam.com.htt.HttpComponent] (main) Created ClientConnectionManager org.apache.http.impl.conn.PoolingHttpClientConnectionManager@2436ea2f
2020-05-05 01:32:53,352 INFO  [org.apa.cam.imp.eng.AbstractCamelContext] (main) Route: csvProducerRoute started and consuming from: timer://csv
2020-05-05 01:32:53,390 INFO  [org.apa.cam.imp.eng.AbstractCamelContext] (main) Route: csvConsumerRoute started and consuming from: file://input
2020-05-05 01:32:53,416 INFO  [org.apa.cam.imp.eng.AbstractCamelContext] (main) Total 2 routes, of which 2 are started
2020-05-05 01:32:53,428 INFO  [org.apa.cam.imp.eng.AbstractCamelContext] (main) Apache Camel 3.1.0 (CamelContext: quarkus-camel-demo) started in 3.749 seconds
...

라즈베리 파이에서 애플리케이션의 시작 시간은 3.749 초가 걸렸습다. 개발 PC 의 시작 시간 0.325 초에 비해 길지만 라즈베리 파이 환경에서 4초 이내 자바 애플리케이션이 완전히 실행됐다는 점에서 의미있는 시작 시간으로 볼 수 있습니다.
필자는 별도의 ARM 서버를 준비하지 못해, 라즈베리 파이에서 Native image 애플리케이션을 실행해 보지 못했지만, Native image 자바 애플리케이션을 라즈베리 파이에서 실행한다면, 시작 시간은 1초 이내고 메모리도 수십 MB 로 실행될 것을 예상할 수 있을 것입니다. 이 정도 실행 속도와 자원 사용 환경이라면, IOT 에서도 충분히 의미있는 애플리케이션을 개발, 운영할 수 있을 것입니다. 즉 자바 언어가 만든 유용한 라이브러리와 프레임워크 등을 IOT 영역에 도입하는데 큰 장애물이 사라졌다고 볼 수 있을 것입니다.

참고로 ARM 서버는 Amazon EC2 A1 인스턴스 에서 저렴하게 임대할 수 있습니다. 라즈베리 파이에서 Native image 자바 애플리케이션 실행의 검증은 관심있는 독자에게 맡깁니다.

맺음말

GraalVM 이 등장하면서, 자바 컴파일 방식에 큰 발전이 일어났습니다. 지금까지 자바 컴파일러가 바이트 코드를 생성했다면, GraalVM 컴파일러는 바이트 코드 뿐만 아니라, native image 를 생성할 수도 있게 됐습니다. 이에 따라 native image 자바 애플리케이션은 C 나 Golang 언어만큼 시작 속도와 메모리 사용이 빨라지고 작아지게 됐습니다.

지금까지 자바 언어는 JVM 에 의존하면서 서버 애플리케이션이으로서 탄탄한 상태계를 구축했지만, 컨테이너 시대가 도래하고, 새로운 언어들이 등장하고 발전하는 데, 느린 시작 시간과 많은 메모리 사용으로 유용성이 제한되는 문제가 있었습니다. 그러나 이제는 자바 언어가 native image 로 동작 가능하게 되면서, 컨테이너 시대에도 빠른 시작 시간과 적은 메모리 사용으로, 주력 언어의 지위를 계속 이어 갈 수 있게 됐습니다.

Quarkus 는 자바 언어를 컨테이너 시대에 사용하기 편리하게 해주는 프레임워크입니다. Spring Boot 가 웹 서버를 내장하면서 WAS(Web Application Server) 를 벗어나 자바 MSA(Microservice Architecture) 에 주력 프레임워크가 됐듯이, Quarkus 는 현재도 여전히 발전하는 프로레임워크로 Spring Boot 와 유사한 기능을 제공하고, native image 빌드를 위한 확장을 제공하므로, 컨테이너 MSA 환경에서 중요한 프레임워크가 될 것으로 기대해 봅니다.

Apache Camel 은 EIP(Enterprise Integration Patters) 의 구현체인 통합 프레임워크로 Spring Boot 뿐만 아니라 Quarkus 와도 잘 결합돼 컨테이너 MSA 환경에서 레거시 통합 및 MSA 통합에 주요한 프레임워크입니다. 더불어 Qurakus 기술과 결합해 MSA 통합에서 IOT 통합까지 다양한 응용 분야에서 중요한 기술로서 가능성이 있습니다.

필자가 GraalVM 기술을 처음 접했을 때, 왜 이 기술이 나올 때까지 20년 (자바 언어는 1995년 탄생) 이상이 걸렸는지 의아하기도 하고 또 반가웠습니다. 이제 자바로 Golang 처럼 빠른 실행과 적은 메모리를 사용하는 애플리케이션을 개발할 수 있다는 점은 자바 언어가 앞으로 10년 이상 건재할 것이라는 것을 의미하기 때문입니다.

GraalVM, Quarkus, Apache Camel, 그리고 Spring Boot 도 자바를 native image 세계로 인도하고 있습니다. 물론 아직은 완전히 성숙됐다고 볼 수 없어 실제 기술 검토를 하면, 여러 가지 문제를 만나게 됩니다. 그럼에도 지속적으로 발전하고 편해지고 있습니다. 그 중 Red Hat 은 Quarkus 와 Apache Camel 을 서브스크립션을 통해 기술 지원하므로 커뮤니티 오픈소스를 직접 사용함에 따른 다양한 시행착오와 기술 문제의 해결을 지원 받을 수 있다는 점은, 이 기술에 관심을 가진 기업이 비즈니스 애플리케이션에 이용하는 데 장벽을 낮춰줄 수 있을 것입니다. 이 글을 읽는 독자가 이 새로운 변화에 관심을 갖는다면 분명 이 시대 누구보다 앞선 엔지니어가 될 수 있을 것입니다.

사마천 사기 상군(商君) 열전 에 이런 말이 나옵니다.

기구일지라도 그 효용이 10 배가 되는 것이 아니면 바꾸지 않는 법이다.

끝으로 GraalVM 과 Qurakus 그리고 Apache Camel 의 결합은 10 배 이상의 효용을 줄 수 있을 것이라 감히 이야기 해봅니다.

참고 자료

  1. Quarkus Apache Camel Demo Source
  2. GraalVM
  3. GraalVM Downloads
  4. Quarkus
  5. Red Hat Qurakus
  6. Quarkus Getting Started
  7. Quarkus Code Starter
  8. Apache Camel
  9. Red Hat Fuse
  10. Camel in Action
  11. 기업 통합 패턴(Enterprise Integration Patterns)
  12. 바른모 블로그
  13. Spring Boot
  14. CentOS
  15. Raspberry PI
  16. Ubnutu server on Raspberry PI
  17. Docker desktop
  18. Windows 10 WSL2 설치 지침
  19. Amazon EC2 A1 인스턴스

2014년 11월 20일 목요일

Apache Camel을 이용한 대출 모집인 구현


이 글은 팁코 액티브엔터프라이즈(TIBCO ActiveEnterpise) 기반 통합 솔루션을 Apache Camel 기반 통합 솔루션으로 대체하는 방법을 설명합니다.

1. 들어가며

기업 통합 패턴(Enterprise Integration Patterns)의 "9장 사잇장: 복합 메시징"에서는 대출 모집인을 세가지 통합 도구를 각각 이용해 실질적인 통합 솔루션을 구현하는 예들을 설명한다. 첫째 예는 SOAP 프레임워크인 Apache Axis를 이용하고, 둘째 예는 마이크로소프트 MSMQ를 이용하고, 셋째 예는 상용 EAI 제품인 팁코 액티브엔터프라이즈(TIBCO ActiveEnterprise)를 이용한다. 이 책 9장에 등장하는 예들의 특징을 표로 보이면 다음과 같다.

구현
실행 방식
주소 지정
수집 전략
채널 형식
기술
1
동기
수신자 목록
채널
웹 서비스/SOAP
자바/아파치 액시스
2
비동기
수신자 목록
상관관계 식별자
메시지 큐
C#/마이크로소프트 MSMQ
3
동기,비동기
게시 구독 채널
상관관계 식별자
게시 구독
팁코 액티브엔터프라이즈

위 구현 예들은 중 "팁코 액티브엔터프라이즈를 이용한 구현"은 앞의 다른 예들과 달리 동기 요청과 비동기 메시징이 결합됐다는 점에서 차이가 있다. 즉 첫째 예는 SOAP을 이용한 동기 호출 기반의 솔루션이고, 둘째 예는 마이크로소프트 MSMQ를 이용한 비동기 메시징 솔루션인데, 셋째 예는 팁코 액티브엔터프라이즈를 이용해 요청은 동기로 수신하고 후속 처리는 비동기 메시징을 이용한다. 셋째 예의 메커니즘을 좀더 구체적으로 설명하면, "팁코 액티브엔터프라이즈를 이용한 구현" 예의 대출 모집인은 대출 클라이언트로부터 대출 견적을 동기로 요청 받아, 동기 호출로 신용 평가 기관에서 대출 클라이언트의 신용 점수를 얻고, 은행들에게 비동기 게시 구독 채널로 견적 요청을 브로드캐스트하고, 각 은행들로부터 대출 견적 응답을 비동기 메시지 큐(서브젝트)로 수집해, 최상 대출 견적을 대출 클라이언트에게 동기 응답으로 반환한다. 여기서 중요한 점은 상용 EAI 제품을 이용했다는 점이다.

그런데 기업 통합 패턴에서는 이런 메커니즘에 대한 해결책이 의도적이든 의도적이지 않든 부실하다. ("팁코 액티브엔터프라이즈를 이용한 구현"에서만 동기, 비동기의 결합에 대한 전체적인 예를 살펴볼 수 있다.) 기업 통합 패턴은, 비동기 메시징을 전제로 해결책들을 제시하는 책이므로, 비동기 인프라에서 동기 인프라에 접근하기 위한 패턴으로 서비스 액티베이터 패턴[EIP]을 설명하고, 애플리케이션이 비동기 인프라에 접근하기 위한 패턴으로는 메시징 게이트웨이 패턴[EIP]을 설명한다. 이와 같이 "팁코 액티브엔터프라이즈를 이용한 구현" 예를 벗어난 곳에서는 동기 인프라와 비동기 인프라가 결합된 종합적인 해결책을 비중 있게 다루지는 않는다. 상관관계 식별자 패턴[EIP]을 활용할 수도 있지만, 상관관계 식별자 패턴은 주로 비동기 요청의 응답을 상관시키기 위한 패턴이다.

이에 반해 팁코 액티브엔터프라이즈 같은 상용 EAI 제품들은 동기 호출과 비동기 응답을 상관시키는 종합적인 메커니즘을 내장한다. 이 기술은 간단한 것처럼 보이지만 실상은 그렇지 않다. 동기 호출은 응답을 반환할 때까지 동기 프로세스(스레드)가 (중간) 처리 결과와 처리 상태를 유지하는 반면, 비동기 메시징에서는 (중간) 처리 결과는 메시지와 필터 프로세스(스레드)들에 유지되고 처리 상태는 메시지가 위치한 채널로 관리한다. 즉 동기 호출과 비동기 메시징의 처리 결과와 상태 관리는 완전히 다른 방식이다.

그렇다면 동기 호출과 비동기 메시징을 결합하기 위해서는 상용 EAI 제품만 사용해야 하는 것일까? 그렇지 않다. 통합 프레임워크를 이용한다면 얼마든지 상용 EAI 제품처럼 동기 호출과 비동기 메시징을 결합할 수 있다. 그러므로 이 기능 때문에 상용 EAI 제품을 선택해야 한다는 기준은 전제하지 않아도 될 것이다.


2. 대출 모집인 솔루션 아키텍처 비교

기업 통합 패턴 9장의 "팁코 액티브엔터프라이즈를 이용한 구현" 예의 팁코 대출 모집인 솔루션 아키텍처는 다음과 같다.

위 아키텍처 그림에서 테스트 대출 클라이언트와 대출 모집인 사이와 대출 모집인과 신용 평가 기관 사이는 팁코의 동기 랑데부 전송을 사용하고, 대출 모집인과 은행들 사이는 비동기 랑데부 전송을 사용한다. 중간의 프로세스 관리자는 팁코의 시각적 도구와 스크립트와 메타 저장소 등을 이용하여 메시지 송수신 흐름과 처리 상태를 관리한다. 보다시피 동기 메커니즘과 비동기 메커니즘이 결합되어 있다. (이 솔루션 아키텍처는 기업 통합 패턴의 복합 메시징의 예를 보여주기 위해 모든 통신 인프라를 팁코 랑데부 통신으로 구성한 것이다. 실무적을 보면 대출 클라이언트는 원격지에서 인터넷으로 대출 모집인에 접속할 것이고, 신용 평가 기관이나 은행들도 원격지에 위치해 전용선이나 인터넷으로 대출 모집인을 연결할 것이다.)

위 솔루션 아키텍처에 대응되는 "Apache Camel을 이용한 대출 모집인 솔루션 아키텍처"는 다음과 같다.

"팁코 액티브엔터프라이즈를 이용한 구현"에서 달라진 점은 Apache Camel을 이용한 대출 모집인 솔루션은 팁코 동기 랑데부 전송을 좀더 범용적인 SOAP/HTTP 전송으로 대체하고, 팁코 비동기 랑데부 전송을 내부 메시징과 Apache ActiveMQ의 JMS Topic으로 대체하고, 프로세스 관리자를 프로세스 관리자(웹 서비스 빈) 라우팅과 응답 관리자 라우팅으로 대체한다는 점이다.


3. 대출 클라이언트 인터페이스


3.1. 대출 모집인

앞서도 간단히 언급했듯이 "Apache Camel를 이용한 구현"은 "팁코 액티브엔터프라이즈를 이용한 구현"의 팁코 동기 랑데부 전송을 SOAP/HTTP 동기 웹 서비스로 대체한다. 이를 위해 대출 모집인은 대출 견적 요청 서비스를 스프링 설정 파일에 SOAP 웹 서비스 인터페이스 빈으로 다음과 같이 노출한다.

brokerQouteServiceEndpoint 빈은 웹 서비스 인터페이스 객체고, brokerQouteServiceImp 빈은 웹 서비스 객체다. BrokerQouteServiceImp 빈 클래스가 웹 서비스를 실제 수행한다. 그러므로 BrokerQouteServiceImp 클래스가 실제 프로세스 관리자[EIP]다. 여기서는 서비스 우선 개발(service first development) 방법에 따라 웹 서비스 인터페이스를 먼저 정의했다. 이 설정으로 대출 클라이언트에 대한 대출 모집인의 SOAP 웹 서비스 노출을 완성된다. 서비스 우선 개발 방법의 자세한 설명은 "Apache Camel을 이용한 마이크로 웹 서비스 개발" 글을 참고하기 바란다.

BrokerQouteService 인터페이스는 다음과 같다.

BrokerQouteService 는 평범한 자바 인터페이스다. 이 인터페이스를 상속한 BrokerQouteServiceImp 빈 클래스는 프로세스 관리자 절에서 설명한다.


3.2. 대출 클라이언트

대출 클라이언트는 대출 모집인이 노출한 웹 서비스를 호출한다. 그런데 이 예는 대출 모집인과 대출 클라이언트가 하나의 스프링 설정 파일을 공유하므로, 대출 클라이언트는 위한 (호출 측) 웹 서비스 빈은 따로 설정하지 않는다. (대출 클라이언트는 (호출 측) 웹 서비스 빈으로 대출 모집인의 (서버 측) 웹 서비스 빈을 사용한다.)

웹 서비스를 호출하는 대출 클라이언트의 Camel 라우팅 설정은 다음과 같다.

대출 클라이언트 라우팅은 5초마다 bean:client 엔드포인트를 실행한다. 그러면 대출 클라이언트 빈은 대출 모집인의 웹 서비스를 호출한다.

LoanQouteClient 빈 클래스는 다음과 같다.

LoanQouteClient는 client 생산자 템플릿 객체를 이용해, 대출 모집인 웹 서비스로 동기 호출을 수행하고 반환된 결과를 화면에 출력한다.


4. 신용 평가 기관 인터페이스


4.1. 신용 평가 기관

신용 평가 기관도 동기 웹 서비스를 노출하므로, 신용 평가 기관은 신용 평가 서비스를 스프링 설정 파일에 SOAP 웹 서비스 인터페이스 빈으로 다음과 같이 노출한다.

creditScoreServiceEndpoint 빈은 웹 서비스 인터페이스 객체고, creditScoreServiceImp 빈은 웹 서비스 객체다. CreditScoreServiceImp 빈 클래스가 웹 서비스를 실제 수행한다. 여기서도 웹 서비스 개발에 서비스 우선 개발(service first development) 방법을 사용한다.

CreditScoreService 인터페이스는 다음과 같다.

CreditScoreService 는 평범한 자바 인터페이스다.

CreditScoreServiceImp 빈 클래스는 다음과 같다.

신용 평가 기관의 신용 점수 계산을 시뮬레이션 하기 위해 랜덤 메소드를 사용한다.


4.2. 대출 모집인

대출 모집인은 위에서 설명한 creditScoreServiceEndpoint 빈 설정을 그대로 사용해 신용 평가 기관에게 웹 서비스를 요청한다. 대출 모집인은 BrokerQouteServiceImp 빈에서 다음과 같이 creditBurea 생산자 템플릿을 이용해 신용 평가 기관에게 (동기) 웹 서비스를 호출한다. (BrokerQouteServiceImp의 전체 소스는 프로세스 관리자 절에 있다.)


5. 은행 인터페이스


5.1. 은행

이 예에서 각 은행들은 게시 구독 채널인 ActiveMQ의 JMS Topic에 가입한다. 그러므로 각 은행들은 스프링 설정 파일에 다음과 같은 JMS 수신자(가입자) Camel 라우팅 설정을 갖는다.

각 은행은 bank.loan.request 게시 구독 채널로부터 견적 요청을 수신해, bank? 빈 엔드포인트를 수행하고, 수행 결과 메시지를 bank.loan.relpy 채널로 응답한다.

BankQouteService 인터페이스는 다음과 같다.

BankQouteService 는 평범한 자바 인터페이스다.

BankQouteServiceImp 빈 클래스는 다음과 같다.

BankQouteServiceImp는 기업 통합 패턴 9장의 "MSMQ를 이용한 비동기 구현"을 참조했다. 요청마다 다른 견적을 응답하기 위해 랜덤 메소드를 사용한다.


5.2. 대출 모집인

대출 모집인은 BrokerQouteServiceImp 빈에서 다음과 같이 auction 생산자 템플릿을 이용해 bank.loan.request 게시 구독 채널로 견적 요청 메시지를 게시한다. (BrokerQouteServiceImp의 전체 소스는 프로세스 관리자 절에 있다.)


6. 대출 모집인 프로세스 개요

팁코 예의 대출 모집인 프로세스 정의는 다음과 같다.

Apache Camel 예는 위 프로세스 워크플로우를 프로세스 관리자 라우팅 설정인 borkerProcessManager와 응답 관리자 라우팅 설정인 brokerReplyManager로 대체한다. borkerProcessManager는 대출 클라이언트의 동기 요청을 처리하고, brokerReplyManager는 비동기로 은행들로부터 응답을 수신해 최상의 결과를 선택한다.

프로세스 관리자 borkerProcessManager의 라우팅 설정은 다음과 같다.

borkerProcessManager는 팁코 프로세스 워크플로우의 첫 번째부터 마지막까지 직선상에 위치한 액티비티들을 대체한다. borkerProcessManager는 은행들에게 대출 견적을 게시한 후 견적 결과가 완성될 때까지 실행 스레드를 중지한다. (자세한 설명은 뒤에 나온다.)

응답 관리자 brokerReplyManager의 라우팅 설정은 다음과 같다.

brokerReplyManager는 팁코 프로세스 워크플로우에서 오른쪽에 위치한 "Process Received Quote From Bank" 액티비티를 대체한다. JMS 큐로부터 은행들의 견적 응답을 수집해 최상 견적의 선택하고 borkerProcessManager를 재시작시킨다.


7. 대출 모집인 프로세스 상세


7.1. 프로세스 관리자

프로세스 관리자는 웹 서비스를 통해 대출 클라이언트로부터 대출 견적 요청을 수신하고, 신용 평가 기관 웹 서비스를 호출하고, 은행들에게 견적 요청을 게시하고, 최상의 대출 견적을 대출 클라이언트에게 반환한다.

다음은 프로세스 관리자를 위한 빈들과 borkerProcessManager 라우팅 설정이다.

BrokerQouteServiceImp 빈 클래스의 전체 소스는 다음과 같다.

BrokerQouteServiceImp은 프로세스 관리자로서 신용 평가 기관 인터페이스와 은행 견적 요청을 위한 게시 구독 채널을 멤버 변수로 갖고, 팁코 액티브엔터프라이즈의 대출 모집인 프로세스의 대부분의 액티비티들을 구현한다. BrokerQouteServiceImp은 게시 구독 채널로 은행 견적 요청을 브로드캐스트한 후 잡 저장소와 자바의 동기 메커니즘을 이용해 스레드를 중지하고 brokerReplyManager 라우팅이 job 객체를 깨울 때까지 대기한다.


7.2. 응답 관리자

응답 관리자는 은행들로부터 견적 응답을 수신하고, 견적 응답을 수집하고, 최상의 견적을 선택하고, 프로세스 관리자를 재시작시킨다.

다음은 응답 관리자를 위한 응답 수신 빈들과 brokerReplyManager 라우팅 설정이다.

수집기는 요청마다 10초동안 은행들로부터 견적들을 수집한다. bankReplyAggregator 빈은 은행들의 견적 응답을 bids 객체에 추가한다.

BankReplyAggregator 클래스는 다음과 같다.

이 수집기는 은행으로부터 수신한 메시지로부터 상관관계 식별자를 추출하여 상관 관계 식별자로 잡 저장소에서 잡을 꺼내와 bid 객체에 은행 응답을 추가하고, 다시 잡 저장소에 잡을 저장한다. AggregationStrategy을 상속해 Apache Camel의 수집 로직 전략 메커니즘을 이용했다.

ProcessReceivedQoute 클래스는 다음과 같다.

ProcessReceivedQoute 는 잡 저장소로부터 잡을 꺼내고, 은행 응답 객체를 생성하고, 완성된 bid 목록으로부터 최상의 견적을 찾아, 응답을 채운 후, 잡 객체의 자바 동기화 메커니즘을 이용해 BrokerQouteServiceImp 빈에서 중지된 스레드를 깨운다.


7.3. 잡 저장소

동기 프로세스와 비동기 메시징을 결합하려면 세션 객체 저장소가 필요하다. 이 저장소는 스프링 설정 파일에 빈과 Camel 라우팅을 이용해 설정한다.

잡 저장소의 스프링 빈과 Camel 라우팅 설정은 다음과 같다.

JobRepository 인터페이스는 다음과 같다.

JobRepository 인터페이스를 상속한 EhCacheRepository 클래스는 다음과 같다.

JobRepository 객체는 BrokerQouteServiceImp 클래스 등에서 멤버 변수로 참조된다.


8. 실행

대출 클라이언트는 5초마다 대출 모집인 웹 서비스에게 대출 견적을 요청하고, 대출 모집인은 대출 견적 요청을 은행들에게 게시하면, 5개 은행들은 각각 자신의 대출 견적을 응답하고, 대출 모집인은 5개의 대출 견적 응답 중 최상의 대출 견적을 선택해, 대출 클라이언트에게 반환한다. 아래는 이 솔루션을 실행한 결과다.

이 시뮬레이션 솔루션은 Camel 라우팅 설정으로 실행되며, 대출 클라이언트, 대출 모집인, 신용 평가 기관, 은행들을 모두 포함한다. Apache CXF 프레임워크가 포함되어 있으며, 테스트를 위해 Apache ActiveMQ가 함께 실행된다. 실행 결과의 아래를 보면 대출 클라이언트는 각 대출 견적 요청마다 다른 은행의 견적을 최상의 견적으로 반환 받을 것을 볼 수 있다.


9. 맺음말

필자는 이 글에서 Apache Camel로도 팁코 액티브엔터프라이즈로 구축한 통합 솔루션을 충분히 대체할 수 있음을 보였다. 게다가 팁코 액티브엔터프라이즈로 구축한 솔루션 예보다 Apache Camel로 구축한 솔루션 예가 설정이나 코딩을 더 적게 사용했다. 이렇게 작업량을 줄일 수 있었던 이유는 Apache Camel의 DSL(Domain Specific Language, 도메인 특화 언어)와 이미 완성된 컴포넌트들을 이용했기 때문이다. 웹 서비스를 위해 단지 몇 십 줄의 설정과 코딩이 필요했으며, JMS 서비스를 위해서도 몇 십 줄의 설정과 코딩을 추가했다. 이점이 Apache Camel이 갖는 강력한 생산성이다.

이와 같이 오픈 소스 통합 프레임워크인 Apache Camel을 이용하면 팁코와 같은 상용 EAI 제품으로 구축한 통합 솔루션을 대체하거나 통합 솔루션을 새롭게 구축할 수도 있으나, 여기에는 전제가 따른다. 기업 통합 패턴으로 아키텍처를 분석할 수 있어야 하며, 기존 솔루션의 아키텍처에 대한 이해도 충실해야 하고, Apache Camel과 Spring 프레임워크와 통합에 포함되는 다양한 기술에 대해서도 깊은 이해가 있어야 한다. 이런 준비가 없다면 기업 애플리케이션들을 제대로 통합할 수 없을 것이다. 물론 이런 전제는 상용 EAI 제품을 도입하더라도 동일하게 적용된다. 상용 EAI 제품도 어떤 기능을 제공한다고 해서 단순히 몇 줄의 설정이나 코딩으로 기능이 동작하는 것은 아니다. 상용 EAI 제품을 제대로 활용하려고 해도 통합을 위한 각종 어댑터들을 이해해야 한다. (실제로 어떤 EAI 상용 제품은 기업 통합 패턴의 번호표 패턴[EIP]를 제공하기 위해 수십 페이지 정도의 설정 절차를 숙지해야 하는 경우도 있다.) 그럼에도 상용 EAI 제품만이 해결할 수 있는 통합은 없으며, 오픈 소스를 잘 이용한다면 그에 못지 않는 통합 품질뿐만 아니라 솔루션 구조도 단순화시킬 수 있다는 점은 고무적인 것이다.

필자는 기업 통합 패턴과 통합 프레임워크에서 많은 가능성을 전망하고 있다. 기업 통합 패턴과 Apache Camel 통합 프레임워크는 이 글의 예처럼 상용 EAI인프라를 대체해 기업을 통합시킬 뿐만 아니라, ETL, 배치, 동기화 등 통합의 각론 분야에서도 충분히 가치를 발휘한다. 또한 새롭게 등장하는 개념의 마이크로 서비스(Microservice)를 개발하는 데 있어서도 Apache Camel은 가장 적합한 도구가 되어 가고 있다. 그러므로 아키텍트와 개발자들도 기업 통합 패턴과 Apache Camel에 관심을 갖기 바란다.


참고 사이트

2014년 11월 11일 화요일

Apache Camel을 이용한 마이크로 웹 서비스 개발


1. 들어가며

일반적으로 기업은 사업 상 멀리 떨어진 외부 협력사들이나 자사의 지점들과 정보나 업무를 주고 받아야 한다. 예를 들어 쇼핑몰은 배송지 주소와 물품 배송 정보 등을 인터넷을 통해 배송 업체와 주고 받아야 한다. 즉 쇼핑몰 전자 상거래 시스템은 배송 업체의 물류 시스템과 인터넷에서 원격으로 접속해서 정보를 주고 받아야 한다. 그런데 문제가 되는 점은 이들 개별 회사들이 인터넷의 표준 프로토콜인 TCP/IP 접속을 합의했다 손치더라도 애플리케이션 프로토콜이나 데이터 형식에 대해서는 그다지 합의하지 못했다는 점이다. 즉 각 사의 애플리케이션 프로토콜이나 데이터 형식은 각 사의 환경에 맞춰 각기 달리 발전함으로써 양 사가 통합하려면 한쪽이든 양쪽이든 프로토콜과 데이터 형식을 새로 합의해야 한다.

과거에는 (여전히 현재도) 인터넷 또는 TCP/IP 망에서 떨어진 두 시스템은 데이터 전송에 이미 합의한 TCP/IP 계층의 전송 프로토콜을 적극 이용하는 방향으로 발전해 왔다. 즉 전송을 위해 TCP/IP 소켓을 활용하고, 소켓 접속 수립에 양 애플리케이션이 합의한 절차를 추가하고, 데이터 형식은 전문이라 불리는 (일반적으로 고정 길이 필드를 가진) 메시지를 이용했다. 그런데 이런 방식으로 원격 시스템들을 통합하는 경우 개발자가 부담해야 할 개발 범위와 시간은 상당하게 된다. 통합을 위해 각 사에서 사용하는 다른 프로토콜과 데이터 형식을 서로 맞추거나, 한 쪽에 맞춰야 하고, 통합을 위한 테스트도 양 사가 동시에 수행해야 하기 때문이다. 기업들은 이런 문제를 해결하기 위해 통신과 전문을 편리하게 처리해 주는 통신/전문 라이브러리나 미들웨어 패키지들을 구매하기도 한다. 그러나 벤더 독자적 솔루션을 사용하는 기업은 상이한 솔루션을 사용하는 다른 원격지와 통합하려는 경우 다시 유사한 문제를 해결해야 한다.

이와 같은 문제로 인터넷의 원격지 시스템들 사이 통합에 모두가 동의할 수 있는 수많은 표준들이 제안되었고, 그 중 SOAP(Simple Object Access Protocol)을 많이 사용되고 있다. 그러나 SOAP이 처음 세상에 나왔을 때, SOAP은 이름처럼 간단함을 추구하려 했으나 실제 사용하려면 지나치게 자유도는 높고 사용 절차도 상당히 복잡하다는 문제를 가지고 있었다. 그 결과 관심을 가졌던 전문가들조차 SOAP 사용에 어려움을 호소하거나 심지어 SOAP을 외면하는 사태까지 이르렀었다. 그러나 시간이 지나면서 SOAP을 편리하게 사용할 수 있는 도구들과 프레임워크들이 시행착오를 통해 발전하면서 SOAP의 사용이 과거보다 상당히 편리해 졌고, 인터넷 상의 원격지 시스템들을 통합하는 데 이만큼 잘 정비된 표준도 없기에, 현재는 점차 SOAP을 원격지 통합에 사용해 가는 추세다.

SOAP 이외에 인터넷 상의 원격지 시스템들을 통합하는 표준이나 기술로 XML-RPC, JSON-RPC, Restful, Spring Remote, Camel Remote 등등 HTTP 프로토콜을 기반으로 애플리케이션 계층을 정의하는 여러 프로토콜들이 있다. HTTP 프로토콜은 요청 응답 패턴에 헤더와 본문을 가진 메시지를 사용하고 사용하는 TCP 포트도 기업 인터넷 방화벽이 접속을 허용하므로, 대부분의 애플리케이션 프로토콜들이 HTTP 프로토콜을 전송 프로토콜로 사용한다. 우리가 일반적으로 알고 있는 RPC(Remote Procedure Call) 기술인 CORBA, DCOM, 자바 RMI 등은 사실 인터넷과 같은 WAN 망을 위한 프로토콜이 아니다. 이런 프로토콜은 간단한 요청 응답 패턴 이상의 송수신이 발생하고, 경우에 따라서는 브로커가 개입해야 하고, 사용하는 TCP 포트도 일반적으로 인터넷 방화벽이 개방하지 않는다. 즉 이런 기술들은 LAN에서 사용되는 기술들이다. 그러므로 이런 기술을 WAN 망에 적용하면 도리어 성능이 떨어지거나 플랫폼에 종속되거나 상호 상호 접속이 불가능할 수도 있게 된다. 즉 RPC에 사용되는 프로토콜들도 접속 상황에 따라 적절하게 구분해서 사용해야 제대로 된 효용을 발휘할 수 있는 것이다.

최근에는 마이크로 서비스(Microservice)라는 개념이 확산되고 있다. 마이크로 서비스는 아주 작은 코딩으로 업무를 처리하는 기능 중심의 컴포넌트 애플리케이션을 말한다. 즉 약 100 줄 내외의 짧은 프로그램이나 간단한 설정으로 업무를 수행하게 하는 기술이다. 모든 기능을 집적한 강력한 단일형 애플리케이션에서 기능 별로 전문화된 컴포넌트 애플리케이션으로 분업화하는 기술이라고도 볼 수 있다. 이 기술의 장점은 서비스의 추가/삭제/수정에 다른 서비스들의 영향을 최소화 할 수 있다는 점이다. 모든 서비스를 함께 운영하는 단일형 애플리케이션의 경우 운영되는 서비스들 중 일부를 수정하려면 애플리케이션 내 모든 서비스들도 영향을 받거나 중지해야 하는 문제가 생길 수 있다. 이점은 기업 통합 패턴의 단단한 결합(tight coupling)과 느슨한 결합(Loose Coupling)의 원리 비교와 일맥 상통한다. 마이크로 서비스는 이제 막 등장하는 개념으로 앞으로 어떻게 발전/수정/쇠퇴할 지 좀더 지켜봐야 할 것이다. 그럼에도 필자는 이 글에서 SOAP 웹 서비스를 마이크로 서비스로 구현해 보려고 한다.

SOAP 프로토콜을 이용하려면 웹 서비스를 기술하는 WSDL도 알아야 하고 SOAP 구현체에 맞추어 프로그램도 개발해야 한다. 그러므로 SOAP을 이용한 개발 방법은 SOAP 구현체에 따라 상당히 달라진다. 그리고 SOAP은 웹 서비스에 주로 사용된다. 여기서 말하는 웹 서비스란 인터넷에 HTTP 프로토콜을 통해 노출된 서비스를 말한다. 즉 SOAP 웹 서비스는 인터넷에서 호출 가능한 서비스로 데이터 전송 프로토콜로 HTTP를 사용하고 데이터의 전송 형식과 원격 기능 호출 절차에 SOAP 프로토콜을 사용하는 서비스를 말한다. 그러므로 SOAP 웹 서비스를 구축하기 위해서는 웹 서버가 필요하고 SOAP 프로토콜을 지원하는 구현체(프레임워크)도 필요하다.

SOAP 웹 서비스는 두 가지 접근 방법으로 개발한다. 계약 우선 개발(contract-first development) 방법과 서비스 우선 개발(service-first development) (또는 코드 우선 개발(code-first development)또는 계약 나중 개발(contract-last development)) 방법이다. 계약 우선 개발(contract-first development) 방법이란 WSDL(Web Service Description Language)를 우선 사용해 인터넷 상의 웹 서비스를 정의한 후 웹 서비스 서버나 클라이언트들이 정의된 WSDL에 따라 웹 서비스를 개발 방법이다. 서비스 우선 개발(service-first development) 방법이란 인터넷 상에 웹 서비스를 노출하는 서버가 자바 같은 개발 언어로 서비스를 우선 개발하고 개발된 서비스를 웹 서비스로 노출하는 개발 방법이다. 이때 웹 서비스 서버는 노출된 웹 서비스의 WSDL도 자동으로 노출한다. 이후 웹 서비스 클라이언트들은 웹 서비스 서버가 노출한 WSDL을 사용해 계약 우선 개발 방법으로 웹 서비스 호출을 개발한다. 계약 우선 개발 방법의 장점은 WSDL을 이용해 서비스 구현 없이 서비스의 서명과 입출력을 정의할 수 있다는 점이다. 즉 웹 서비스 서버와 클라이언트가 웹 서비스 구현 없이도 웹 서비스를 문서만으로 우선 정의할 수 있다는 점이다. 반면 서비스 우선 개발은 웹 서비스 서버가 웹 서비스를 우선 구현한 후 웹 서비스 클라이언트에게 WSDL을 알려줌으로써 웹 서비스 클라이언트가 웹 서비스 호출을 구현할 수 있게 한다. 이 방법의 장점은 이미 만들어진 내부 서비스를 웹 서비스로 노출할 때 편리하고, 자동화된 웹 서비스 노출 관례를 따르므로 서비스 개발에 집중할 수 있다는 점이다. 반면 이 방법의 단점은 웹 서비스 노출 시, WSDL이 기계적으로 생성됨으로, WSDL을 직접 기술하는 것보다 웹 서비스 정의 융통성이 떨어질 수 있다는 점이다. 그러나 이런 문제도 웹 서비스 어노테이션이나 설정 등을 통해서 웹 서비스 노출 서명을 수정할 수 있으므로 사실 과거에 서비스 우선 개발이 가졌던 단점은 상당히 완화된다. 필자는 SOAP 웹 서비스 서버 개발에는 서비스 우선 개발 방법을 사용할 것이고, SOAP 웹 서비스 클라이언트 개발에는 계약 우선 개발 방법을 사용할 것이다.

첨언하자면 이 글은 Apache Camel Developer's Cookbook 12장 Web Services가 설명하지 않는 SOAP 웹 서비스의 서비스 우선 개발 방법에 대해 필자가 도전하면서 얻은 노하우를 설명하는 글이다.


2. 웹 서비스 서버 프로젝트 생성

웹 서비스를 구현하기 위해서는 SOAP 기반 웹 서비스 구현을 돕는 아파치 CXF(Apache CXF) 프레임워크와 마이크로 서비스를 가능하게 하는 아파치 카멜(Apache Camel) 통합 프레임워크와 애플리케이션 프레임워크인 스프링(Spring) 프레임워크가 필요하다. 그리고 이들 프레임워크들을 편리하게 추가하고 서비스도 간단히 실행하기 위해 maven(3.1 이상) 프로젝트로 웹 서비스 서버를 개발할 것이다. Maven을 이용해 쉘(명령창)에서 웹 서비스 서버 프로젝트를 다음 같이 생성한다.

생성된 프로젝트 디렉터리 아래 pom.xml 파일에 다음의 의존들을 추가한다.

포함되는 의존들은 CXF를 사용하기 위한 Camel CXF 컴포넌트와 자동으로 의존에 포함되는 CXF 프레임워크와 CXF 웹 서비스를 웹 상에 구동해 주는 CXF jetty 라이브러리들이다.


3. 서비스 입출력 정의

일반적으로 서비스를 구현하려면 클라이언트 요청과 서비스 응답을 정의해야 한다. (이곳의 입출력 클래스는 앞서 언급한 Apache Camel Developer's Cookbook의 웹 서비스 예를 참조했다. 그런데 Cookbook의 예는 계약 우선 개발 방법을 사용하므로 WSDL로부터 Cookbook의 웹 서비스 입출력 클래스 생성되나, 필자는 서비스 우선 개발 방법을 사용하므로 필자의 웹 서비스는 입출력 클래스를 우선 정의한다.)

그러므로 서비스 입력 출력 클래스를 다음과 같이 정의한다.

입출력 클래스는 com.brm.ws.payment.types 패키지 아래 놓았다. 이 패키지 이름은 나중에 SOAP WSDL 입출력 정의에서 네임스페이스로 사용된다. CXF 프레임워크는 자바의 리플렉션 기능을 사용해 위에 정의한 입출력 클래스를 WSDL 입출력 정의로 생성해낸다. 그리고 생성된 WSDL 입출력 정의는 SOAP 마샬링의 메타데이터로 활용된다. 그러므로 SOAP 원격 통신으로 전송되는 입출력 클래스는 마샬링 정보가 WSDL로 정의되므로 별도의 마샬링 메타데이터는 필요하지 않다. 그러나 예를 들어 자바 RMI나 JMS같은 SOAP 이외의 프로토콜을 이용하는 경우, 입출력 객체를 전달하고자 한다면 해당 기술에 맞도록 마샬링 메타데이터를 정의하거나, 입출력 클래스가 Serializable 인터페이스를 상속하도록 해야 한다. Serializable 인터페이스를 상속하는 방법은 기업 통합 패턴의 "9장 팁코 액티브엔터프라이즈를 이용한 비동기 구현" 예를 팁코 액티브엔터프라이즈 대신 아파치 카멜로 대체하는 "아파치 카멜을 이용한 비동기 구현"이란 글에서 JMS 통신을 위해 사용될 예정이니, 그 글을 참고하기 바란다.

SOAP은, 입출력 마샬링 메커니즘뿐만 아니라, 웹 서비스의 오류 정보를 전달하는 fault 메커니즘도 제공한다. 서비스 우선 개발 방법에서는 서비스 메소드가 하나의 반환 형식이 고정되므로, 이 기능은 예외를 이용해 전달한다. (이 점은 서비스 우선 개발 방법이 계약 우선 개발 방법과 비교하여 융통성이 떨어지는 부분이기도 하다. 그러나 웹 서비스 서버와 클라이언트는 fault 메커니즘 대신 서비스 응답에 '반환 코드'나 '오류 상세 메시지'를 포함하는 방식으로 입출력을 합의할 수 있다. 이 경우 SOAP 서비스 클라이언트는, fault 처리를 위한 별도 예외 처리 로직 대신, 서비스의 응답으로부터 '반환 코드'나 '오류 상세 메시지'를 추출해 예외를 처리한다. 게다가 이 방법은 충분히 편리하다. 그러므로 이 점이 꼭 서비스 우선 개발 방법의 약점이라고만은 할 수 없다.)

이 글에서는 기술적인 측면에서 SOAP fault를 웹 서비스 어떻게 클라이언트에게 전달할 수 있는지를 보여줄 것이므로, 다음과 같이 Fault 클래스를 정의한다.

위 Fault 클래스는 reason 멤버 변수를 하나 정의해 간단하게 오류를 설명한다. 서비스 오류를 설명하는 추가적인 정보가 필요한 경우 이곳에 필드를 추가할 수 있을 것이다.


4. 서비스 인터페이스 정의

서비스 입출력을 정의한 후 서비스 기능을 노출하는 서비스 인터페이스를 정의한다. 이 서비스는 위에서 정의한 서비스 입출력 정의의 입력 클래스와 출력 클래스를 사용한다. 여기서 주목할 점은 아직까지 웹이나 SOAP에 대한 고려가 없다는 점이다. 즉 서비스 인터페이스는 순전히 일반적인 자바 인터페이스와 메소드로 정의한다. 정의된 서비스 인터페이스는 다음과 같다.

위 서비스 인터페이스에서 TransferException 예외에 주목한다. 서비스 우선 개발에서는 예외를 이용해 웹 서비스 서버가 fault를 웹 서비스 클라이언트에게 전달한다. Fault 정보를 포함한 TransferException 클래스 정의는 다음과 같다.

CXF 프레임워크의 동작 메커니즘으로 보면 TransferException가 SOAP fault로 전달되지만 TransferException은 예외를 위한 클래스이므로 의미의 중첩을 피하기 위해 실제 SOAP fault의 상세 정보는 여기처럼 Fault 객체를 사용하는 것이 바람직하다. 이것으로 서비스 우선 개발 방법의 서비스 인터페이스 정의는 완료된다.


5. 서비스 빈 구현

서비스 인터페이스가 정의되면, 일반적인 POJO 스타일의 서비스를 구현한다. 여전히 웹 서비스라든지 SOAP이라든지에 대한 구현 상의 고려는 별달리 추가되지 않는다. 즉 이 일반적인 POJO 스타일의 서비스 빈은 손쉽게 웹 서비스로 전환될 것이다. 이 예는 웹 서비스의 뼈대 구현에 초점을 맞추므로, 서비스 빈은 이체를 성공했다고 가정하고 간단하게 성공을 응답한다. 그러므로 구현된 DefaultPayment 서비스 빈 은 다음과 같다.

위 서비스 빈은 백만 원을 넘는 이체 요청은 예외를 발생시킨다. 정상 거래와 비정상 거래를 모두 테스트 하기 위해 이체 한도를 서비스 기능에 포함시켰기 때문이다. 보다시피 위 서비스 빈은 전형적인 POJO 스타일의 자바 클래스다.


6. 웹 서비스 서버 구현

서비스 우선 개발 방법에 따라, 위와 같이 일반적인 POJO 스타일의 서비스 빈을 완성했으면 이제 이 평범한 서비스 빈을 웹 서비스로 노출시킬 차례다. 이를 위해서는 아파치 카멜이 제공하는 camel 네임스페이스뿐만 아니라 cxf 네임스페이스도 스프링 빈 정의 XML에 포함해야 한다. 이들 네임스페이스를 포함한 스프링 XML의 beans 엘리먼트는 다음과 같은 속성들을 갖는다.

위와 같이 카멜과 웹 서비스를 위한 네임스페이스를 추가했다면, 서비스 인터페이스를 웹 서비스로 노출시키는 웹 서비스 빈을 지정한다. 이 빈에 노출될 웹 서비스의 웹 URL과 서비스 인터페이스를 지정한다. 실제 서비스를 구현한 빈도 지정한다.

웹 서비스 빈과 구현 빈을 지정했다면, 이제 카멜 라우팅 정의에 이 두 빈을 포함시켜 웹 서비스 서버로 동작하게 한다. 웹 서비스 서버를 노출하는 카멜 라우팅 정의는 다음과 같다.

이 카멜 라우팅 정의는 카멜 CXF 컴포넌트를 이용해 PaymentServiceEndpoint 를 웹 서비스 소비자(서버)로 노출하고, 웹 서비스 클라이언트로부터 수신한 요청을 다시 paymentService 빈에게 전달하게 한다. 즉 from 엔드포인트는 웹 서비스 서버를 노출하고 to 엔드포인트는 웹 서비스 구현을 호출한다. 그리고 route에 handleFault 속성을 참으로 지정해 서비스 빈에서 발생한 예외를 SOAP fault 메시지로 전달한다. 카멜 라우팅 정의를 이용한 웹 서비스 노출은 이렇게 간단하다. 아래는 모든 설정을 포함한 완전한 스프링 빈 정의 XML이다.

이것으로써 POJO 스타일의 서비스가 SOAP 웹 서비스로의 확장이 완성됐다. 실질적으로 웹 서비스로 노출하기 위해 추가된 코드와 설정은 다 합해도 100줄도 채 되지 않았다. 카멜의 장점 중 하나는 이렇게 카멜 정의 파일만으로도 즉시 카멜 컨테이너를 실행할 수 있다는 점이다.


7. 웹 서비스 서버 실행

이제 웹 서비스 서버를 실행해 보자. 프로젝트 디렉터리에서 maven camel:run 골(goal)을 실행한다. camel:run 골을 실행할 수 있은 이유는 프로젝트 생성 과정에서 pom.xml에 camel-maven-plugin이 포함됐기 때문이다.

다음은 웹 서비스 서버 실행 결과다.

그런데 눈치 빠른 독자라면 무엇인가 이상한 점을 발견했을 것이다. 이 예의 SOAP 웹 서비스 서버는 Tomcat이나 그 밖의 WAS를 이용해 기동되지 않았을뿐더러, 웹 서비스 서버를 기동하기 위해 Jetty와 같은 내장형 서블릿 컨테이너를 스프링 빈으로 복잡하게 설정한 후 다시 여러 빈을 엮는 작업을 하지도 않았다. 웹과 관련된 유일한 설정은 “웹 서비스 구현”에서 cxf:cxfEndpoint를 정의할 때 주소로 지정한 http://localhost:9090/paymentService가 전부다. 위 로그를 보면 CXF 프레임워크가 ReflectionServiceFactoryBean 등을 이용해 cxf:cxfEndpoint에서 지정한 http://localhost:9090/paymentService 주소로 웹 서버를 자동으로 기동한 것을 볼 수 있다. 그리고 이 모든 과정이 관례(CoC, Convention Over Configuration)에 따라 자동으로 완성된다. 즉 개발자는 일반적인 POJO 서비스 빈을 30줄도 안 되는 아주 적은 설정을 사용해 SOAP 웹 서비스를 완성한 것이다. 이런 점에서 이 웹 서비스는 마이크로 웹 서비스다.


8. 웹 서비스 WSDL

CXF 프레임워크는, SOAP 웹 서비스 서버의 실행과 더불어, 웹 서비스의 SOAP 계약인 WSDL을 자동으로 노출한다. 그러므로 웹 서비스 서버를 실행한 후, 웹 브라우저는 웹 서비스가 노출한 웹 URL 주소 뒤에 “?wsdl”을 추가해 조회함으로 웹 서비스의 WSDL을 읽을 수 있다.


9. 웹 서비스 클라이언트 프로젝트 생성

웹 서비스 클라이언트 프로젝트는 웹 서비스 서버 프로젝트 생성에서 사용한 동일한 maven archetype을 사용한다. 서버와 달라지는 점은 'artifactId'가 wsc-example로 바뀌는 정도다.

생성된 프로젝트 디렉터리 아래 pom.xml에 다음과 같이 의존들을 추가한다.

jaxws-api 의존은 CXF 프레임워크의 wsdl2java가 생성한 클래스가 참조하는 의존이고, junit 의존은 웹 서비스 클라이언트를 JUnit 클래스로 만들기 위해 필요한 의존이다.


10. 웹 서비스 클라이언트 입출력 정의

앞서 웹 서비스 서버를 완성했으므로, 웹 서비스 클라이언트의 입출력 개발은 웹 서비스 서버가 노출한 WSDL을 이용해 보자. WSDL이 이미 완성된 경우, 계약 우선 개발 방법을 이용하는 것이 훨씬 수월하기 때문이다. 이 예에서는 WSDL로부터 자바 클래스들을 생성하도록 돕는 CXF 프레임워크의 wsdl2java 유틸리티를 이용해 웹 서비스 클라이언트가 필요한 자바 클래스들을 생성한다. wsdl2java를 다음과 같이 실행하면, 이 예의 웹 서비스 서버 WSDL로부터 웹 서비스 클라이언트 실행에 필요한 자바 패키지와 클래스들이 생성된다.

위 명령을 통해 생성된 패키지와 클래스들을 웹 서비스 클라이언트 프로젝트 디렉터리의 src/java/main 아래 복사한다. 복사하면 다음과 같은 패키지와 클래스들이 보일 것이다.

위와 같이 복사하면 웹 서비스 클라이언트의 입출력 정의는 완성된다. com.brm.ws.payment 패키지는 웹 서비스 서버 개발에서 Payment 인터페이스를 정의한 패키지 위치와 같다. (패키지 이름은 웹 서비스 서버 개발 시 웹 서비스 어노테이션을 사용해 명시적으로 지정할 수도 있다. 사실 요즘은 웹 서비스 어노테이션을 사용해 서비스 우선 개발이더라도 계약 우선 개발의 WSDL을 개발하는 것처럼 섬세하게 웹 서비스 구조를 정의할 수 있다. 즉 어노테이션을 이용해 네임스페이스에 대응되는 패키지 이름이나 서버의 웹 서비스가 정의된 패키지에 대응되는 웹 서비스 인터페이스 이름 등을 명시적으로 지정할 수도 있다. 문제는 어느 방법이 더 수월한 가다.) 패키지 아래 입출력 클래스인 TransferRequest.java, TransferResponse.java와 fault 클래스인 Fault.java를 볼 수 있다. 이렇게 언어별 소스 생성에 자동화 도구를 사용할 수 있다는 점이 계약 우선 개발 방법의 장점 중 하나다.


11. 웹 서비스 클라이언트 인터페이스 정의

웹 서비스의 클라이언트 인터페이스도 wsdl2java 유틸리티가 WSDL로부터 자동으로 생성한다. com.brm.ws.payment 패키지 아래 PaymentPortType.java가 클라이언트의 웹 서비스 인터페이스다.


12. 웹 서비스 호출 구현

wsdl2java 유틸리티를 사용해 웹 서비스 WSDL로부터 자동으로 웹 서비스 클라이언트의 입출력 클래스와 웹 서비스 인터페이스를 모두 생성했으므로 이제 웹 서비스를 호출하기 위한 웹 서비스 클라이언트를 구현해 보자. 웹 서비스 클라이언트를 구현하려면 스프링 빈 정의 XML 파일에 웹 서비스 클라이언트의 CXF 빈과 카멜 라우팅 정의를 설정해야 하고, 이 XML 파일을 이용해 웹 서비스를 호출하는 웹 클라이언트 단위 테스트 클래스를 개발해야 한다. (웹 서비스 클라이언트의 스프링 빈 정의 XML파일도 "웹 서비스 서버 구현"에서 사용한 네임스페이스와 같은 네임스페이스를 사용하므로 이에 대한 지정은 "웹 서비스 서버 구현"을 참조하거나 아래 구현된 camel-context.xml을 참조한다.) 우선 스프링 빈 XML 파일을 설정해 보자. Maven 프로젝트로 생성한 웹 서비스 클라이언트의 src/main/resources/META-INF/spring/camel-context.xml을 다음과 같이 설정한다.

위 스프링 빈 XML 파일에서 cxf:cxfEndpoint 설정은 웹 서비스 서버의 cxf:cxfEndpoint 설정과 구조가 동일하다. 차이점은 웹 서비스 서버의 address 속성 값은 이 주소로 웹 서비스를 노출하고, 웹 서비스 클라이언트는 address 속성 값은 이 주소로 웹 서비스를 요청한다는 점이다. serviceClass 속성에는 웹 서비스 서버의 WSDL로부터 생성된 웹 서비스의 클라이언트 인터페이스를 지정한다.

카멜 라우팅 정의에서 웹 서비스는 생산자 엔드포인트다. 웹 서비스 클라이언트는 카멜의 direct 소비자 엔드포인트를 통해 웹 서비스(웹 서비스 생산자 엔드포인트)를 호출한다. 카멜에서는 생산자 엔드포인트를 동기적으로 호출하는 일관된 방법으로 direct 소비자 엔드포인트를 이용한다. 테스트 클라이언트가 오류를 처리하므로 카멜이 오류를 중간에서 굳이 가로채지 않게 라우팅 정의에 errorHandlerRef 속성을 사용해 카멜이 오류를 무시하도록 했다. errorHandlerRef 속성 값은 오류를 무시하는 noErrorHandler 빈의 참조다.

웹 서비스 클라이언트는 정상 처리와 비정상 처리를 모두 확인하기 위해 다음과 같이 두 테스트 메소드를 포함한 JUnit 테스트 클래스로 작성했다.

이 단위 테스트 클래스는 이체 한도 이내의 이체 웹 서비스 요청과 이체 한도 초과의 웹 서비스 요청을 실행한다. 웹 서비스 단위 테스트 클라이언트 클래스가 카멜의 동기 요청 호출 메소드인 request…를 이용해 direct 소비자를 호출하면 카멜은 이 요청을 웹 서비스 생산자 엔드포인트로 라우팅한다.

이체 한도 이내의 이체 웹 서비스 요청 메소드인 testSmallAmount는 정상적은 응답 객체를 수신하고, 이체 한도 초과의 이체 웹 서비스 요청 메소드인 testLargeAmount는 fault를 수신한다. testLargeAmount 메소드 예외 처리과정을 좀더 자세히 살펴보자. 앞서 웹 서비스 서버의 Payment 서비스 인터페이스는 다음과 같이 정의했었다.

위처럼 웹 서비스 서버의 인터페이스는 TransferException를 transferFunds 메소드의 예외로 지정했었다. 즉 transferFunds 웹 서비스 호출 시 TransferException를 fault로 반환하도록 지정했다. 그리고 TransferException은 다시 Fault 클래스를 포함하고, 이 클래스가 실제 fault 메시지를 포함하도록 개발했었다. 여기서 카멜과 CXF 프레임워크의 오류 처리 관례를 제대로 이해하는 것은 아주 중요하다. 카멜과 CXF를 이용한 웹 서비스 개발의 관례를 제대로 이해하지 못한다면 웹 서비스 개발에 상당한 혼란을 초래할 수 있기 때문이다. 카멜과 CXF 프레임워크의 호출 처리 흐름은 다음과 같다. 정상적인 호출인 경우 입출력 객체만 활용되므로 특별한 어려움은 없다. 그러나 fault를 응답하는 경우 상당한 주위가 필요하다. 일반적으로 카멜은 라우팅 중 발생한 예외를 붙잡아 원인으로 포함시킨 후 CamelExecutionException을 발생시킨다. 이 예의 경우 카멜 예외의 원인은 웹 서비스 호출 중 발생한 TransferException_Exception 예외가 되며, 이 예외는 다시 실제 웹 서비스 서버가 발생시킨 fault를 (여기서는 TransferException을) 감싼다. 그러므로 카멜 메소드를 이용해 웹 서비스를 호출한 클라이언트는 예외가 발생했을 때 CamelExecutionException을 잡고 CamelExecutionException에서 TransferException_Exception를 추출한 후, TransferException_Exception에서 웹 서비스 서버가 발생시킨 fault를 (여기서는 TransferException을) 추출하고, fault에서 필드를(여기서는TransferException에서 Fault 객체를) 최종적으로 추출한다.

여기서 주목할 점은 CXF 프레임워크가 서비스 우선 개발로 작성한 서비스 메소드의 자바 예외 객체를 언어 중립적인 SOAP 입출력 구조가 되도록 WSDL 표현을 자동 생성한다는 점이다. 이 과정에서 CXF 프레임워크는 웹 서비스 서버의 자바 예외를 fault 필드로 표현이 가능하도록 멤버 객체들만 WSDL의 TransferException로 옮긴다. 즉 이 과정에서 자바 예외 클래스의 구조가 사라지고 예외 객체의 멤버들만 WSLD의 fault 필드에 포함된다. 이런 이유로 해서, 이 예의 웹 서비스 서버의 WSDL로부터 생성한 웹 서비스 클라이언트의 TransferException 객체는 자바 예외가 아닌 자바 빈(bean) 클래스가 된 것이다. (이런 혼란이 발생하지 않도록 좀더 섬세하게 웹 서비스를 구축하려면 웹 서비스 어노테이션과 카멜의 예외 핸들러를 이용해야 한다. 그러나 이런 기술을 포함하는 경우 처음 웹 서비스를 맛보거나 실용적인 웹 서비스 구축을 원하는 독자들에게 지나치게 복잡한 설명을 포함해야 할 것 같아, 이에 대한 설명은 포함하지 않았다.)


13. 웹 서비스 클라이언트 실행

이제 웹 서비스를 클라이언트를 실행해 보자. 웹 서비스 클라이언트를 실행하기 전에 웹 서비스 서버를 먼저 실행해야 한다. 글의 "웹 서비스 서버 실행"을 참조한다. 웹 서비스 서버를 실행했으면, maven을 이용해 웹 서비스 클라이언트 테스트를 실행한다. 우리는 maven의 관례에 따라 JUnit 테스트 클래스를 작성했으므로, 테스트 클라이언트의 실행은 다음과 같이 간단하다.

다음은 테스트 클라이언트의 실행 결과이다.

위 결과를 보면 웹 서비스 클라이언트 JUnit 클래스는 적은 금액의 이체 요청(testSmallAmount)의 결과로는 Response 객체에 reply로 OK 값을 반환 받았고, 큰 금액의 이체 요청(testLargeAmount)의 결과로는 예외를 반환 받았다. 그리고 예외를 반환 받은 경우 웹 서비스 클라이언트는 예외 객체 안에서 실제 Fault 정보를 획득할 수 있었다. 기대했던 결과를 얻은 것이다.


14. 맺음말

우리는 이 글에서 서비스 우선 개발(service-first development) 방법으로 SOAP 웹 서비스 서버를 개발했고, 계약 우선 개발(contract-first development) 방법으로 SOAP 웹 서비스 클라이언트를 개발해 보았다. SOAP 웹 서비스 서버는 일반적인 POJO 서비스를 개발하는 절차에 카멜과 CXF 프레임워크 설정을 포함하는 30줄 이내의 스프링 빈 정의 XML을 작성하고, maven으로 camel:run 골(goal)을 실행함으로 완성했다. SOAP 웹 서비스 클라이언트는 웹 서비스 서버의 WSDL로부터 패키지와 클래스들을 생성하고, 카멜과 CXF 프레임워크 설정을 포함하는 30줄 이내의 스프링 빈 정의 XML을 추가하고, 웹 서비스 클라이언트 JUnit 테스트 클래스를 개발하여 maven의 test 단계를 실행함으로 웹 서비스 클라이언트를 테스트 할 수 있었다.

실질적으로 웹 서비스 서버를 구축하는 데 추가된 스프링 빈 정의 XML의 설정은 30줄 이내였고, 웹 서비스 클라이언트가 웹 서비스를 호출하도록 하는 데 추가된 스프링 빈 정의 XML의 설정도 30줄 이내였다. 이런 정도의 개발량으로 웹 서비스를 구축하는 방법으로는 카멜을 활용하는 것 이외는 달리 없을 것이다. 이점이 바로 카멜이 갖는 강력함이다.

이와 같이 짧은 소스나 간단한 설정으로 구축되는 기능 중심의 서비스를 마이크로 서비스(Microservice)라 부른다. 물론 마이크로 서비스가 갖는 여러 가지 특징들이 있긴 있으나, 현재 이 용어는 완전하게 정립되지는 않았다. 그럼에도 아파치 카멜은 마이크로 서비스를 구현하는 데 가장 적합한 도구라는 것을 필자를 포함해 통합 전문가들이 동의하고 있다. 앞으로는 중앙 집중식 단일 시스템에 의한 서비스들의 제공이 아닌 기능 별 분산 자치식 시스템에 의한 마이크로 서비스로의 패러다임이 다가 올 것이다. 그리고 마이크로 서비스 인프라 솔루션을 구축함에 있어, 기업 통합 패턴의 패턴 언어는 중심 방법론이 될 것이고, 아파치 카멜은 중요한 구축 도구로서 사용될 것이다.

이 글은 기업 통합 패턴 "9장 팁코 액티브엔터프라이즈를 이용한 비동기 구현"을 아파치 카멜로 대체하는 방법을 모색하던 중, 동기 호출 부분을 웹 서비스로 대체하는 과정에서 획득한 기술적인 노하우를 별도의 내용으로 정리한 글이다. 기업 통합 패턴 "9장 팁코 액티브엔터프라이즈를 이용한 비동기 구현"을 아파치 카멜로 대체한 글에 이 기술을 활용할 것이므로 이 글 이후 이후 조만간 게시할 글도 관심 있게 지켜봐 주길 바란다.


참고 사이트