2014년 2월 28일 금요일

D3.js 라이브러리 소개

그래프를 보려면 파이어폭스 또는 크롬 브라우저를 이용합니다. 인터넷 익스플로러에서는 블로그에서 그래프가 보이도록 사용한 HTML iframe 태그와 문제가 있는 듯 합니다. 인터넷 익스플로러에서는 이곳에서 그래프를 직접 봅니다.

요즈음 빅 데이터가 유행하면서 인포그래픽에 대한 관심이 많아지고 있다. 인포그래픽이 정보를 시각화해 정보의 이해를 쉽게 해주기 때문이다. 이런 인포그래픽을 표현하기 위한 도구들과 라이브러리들은 다양하다. 이 글은 그 중에서 전혀 새로운 방식으로 인포그래픽을 지원하는 D3.js 라는 라이브러리를 소개하고자 한다.

D3.js 는 데이터 기반으로 문서를 처리하는 JavaScript 라이브러리이다. D3란 이름도 D3(Data-Driven Documents)에서 유래한다. 이 라이브러리는 HTML, CSS, SVG 도큐먼트 객체를 데이터 기반으로 접근하는 범용 기술로 부수적이지만 놀라운 방식으로 데이터의 시각화를 지원한다.

HTML로 간단한 페이지를 만들거나, 구글링을 하면서 CSS로 웹 페이지에 효과를 넣어보거나, JavaScript 책을 보면서 JavaScript를 작성하거나 소스를 분석하는 정도의 기술을 가진 필자도 D3.js 기술을 분석하여 그래프를 그리는 데 크게 어렵지는 않았다.

D3.js 라이브러리를 처음 알게 된 계기는 필자의 전문 분야인 Apache Camel, Apache ActiveMQ, Apache ServiceMix와 같은 기업 통합 제품들을 위한 새로운 웹 관리자 프로젝트인 hawt.io 프로젝트를 통해서이다. 이 프로젝트에서는 실시간 상태 감시 그래프를 위해 D3.js 를 사용한다. 그런데 D3.js 기술에 대해 좀더 알아 보니, D3.js 기술은 단순히 차트를 그리는 정도가 아닌, 그 이상으로 인포그래픽을 위한 라이브러리이면서 다른 라이브러리의 기반 기술임을 알게 되었다. D3.js 프로젝트 홈 페이지에 방문해 보면 독자들도 필자의 말을 어떤 의미인지 쉽게 납득할 수 있을 것이다.

D3.js 홈페이지에 가면 수많은 인포그래픽 데모들과 소스들을 볼 수 있다. D3.js 로 개발된 인포그래픽 소스들은 처음 보면 당연히 잘 이해가 되지 않는다. 필자도 JavaScript에 익숙하지 않고 데이터 기반이라는 전혀 낯선 접근 방식의 라이브러리였기 때문이다. 그러나 이렇게 멋진 인포그래픽을 표현해 주는 기술이 있다는 사실만을 알고 그냥 지나치기 보다는 좀더 학습할 필요가 있다고 생각했다.

이미 D3.js 기반으로 좀더 추상화한 많은 상위 라이브러리들이 존재한다. 그러나 오픈 소스 프로젝트들이 일반적으로 그러하듯이 학습을 위한 문서가 상당히 부족하다. 그리고 기반 기술을 이해하지 못하고 상위 기술만 익힌다는 것은 사상누각의 기술이 될 수도 있다. 그래서 D3.js 에 대한 필자의 접근 방법은 D3.js 기술을 먼저 직접 익히는 것이었다.

D3.js 기술을 학습하기 위해 좀더 구글링을 해보니 "D3 Tips and Tricks"라는 책을 찾을 수 있었다. 이 책은 0.99 달러에서 0 달러까지 독자가 스스로 값을 매겨 자유롭게 다운받을 수 있는 책이다.

필자도 이 책을 다운 받아 이클립스(또는 인텔리제이)로 동적 웹 프로젝트를 만들고 책에 나온 예제 HTML 을 입력해 보았다. 물론 이 책이 영어로 되어 있다. 그러므로 영어가 서투른 사람들은 읽기 쉽지 않을 수도 있다. 그러나 프로그램은 개발자들에게 세계 공통어이니까, 프로그램 부분 이외 설명 부분의 단어들은 모르면 사전을 찾아 가며 충분히 읽어 나갈 수 있을 것이다. 또 잘 모르면 구글 번역기를 돌려도 된다. 중요한 것은 배우고자 하는 의지이다. 필자도 기업 통합 패턴이라는 책을 번역했지만 여전히 이런 방식으로 영문을 읽고 해석한다. 그러므로 독자들도 영어에 두려움을 갖지 말자. 어느 정도 영어를 극복해야 소프트웨어 전문가가 될 수 있다.

책에 나온 예제를 따라 HTML 을 입력하면 D3.j의 원리를 이해하면서 간단한 그래프를 그릴 수 있게 된다. 하지만 잘 동작하지 않을 수 있다. 일부 소스는 혼란스럽거나, 틀린 부분도 있는 것 같다. 그러나 수백 페이지에 달하는 책을 독자의 선택이기는 하지만 무료로까지 배포하는 저자를 생각한다면 이런 정도는 당연히 극복해야 할 어려움일 것이다. 어떤 경우든지 시행착오를 겪으며 습득하는 지식이 진짜 지식이다. 이런 과정이 없다면 진정한 전문가가 될 수 없다. 필자도 마찬가지로 책에 나온 소스를 하나하나 입력해가며 시행착오를 거치면서 사용된 방법이나 기술들을 익혔다. 그리고 필요하면 구글링도 하면서 보완했다.

필자가 겪은 문제들은 책에 나온 그래프 데이터가 D3.js 의 날짜 포맷과 달라서 웹 페이지 로딩 중에 D3.js 안에서 에러가 감지되기도 했고, TSV 포맷의 헤더에 데이터 필드의 이름을 반드시 적어 주어야 된다는 사실을 뒤늦게 알게 됐고, 스케일 변수 x 와 D3.js 내의 x 함수의 혼란으로 프로그램이 제대로 실행되지 않기도 했다. 그 외에도 이런 저런 문제들을 만났었다.

이렇게 문제들을 해결해가면서 마침내 완성된 예를 만들었다. 이렇게 완성된 예를 만들어 보니, 이제 D3.js 에서 등장하는 도메인(이 개념은 중학교 때 배운 함수의 정의역이다.), 레인지(이 개념은 중학교 때 배운 함수의 치역이다.), 스케일(이 개념은 고등학교 때 배운 함수이다.)들에 대해 좀더 이해할 수 있게 되었고 몇 가지 입력 값들을 변경하면서 그래프에 효과를 추가하거나 표시 방식을 변경해 보기도 했다. 결과적으로 책에 예제를 좀더 보강하여 필자만의 완성한 예를 완성했다. 그러면서 부수적으로 CSS와 JavaScript에 대해 좀더 더 잘 이해하게 되는 성과(?)도 얻었다.

이 글에 게시된 완성된 예가 작성되기까지 "D3 Tips and Tricks" 책의 95 페이지까지 읽었다. 시간은 약 일주일이 걸린 것 같다. 물론 업무 중에 짬짬이 진행했고, 집에 가서도 조금 더 작업했다. 그리고 이 블로그를 작성하는 데 약 삼 일이 걸렸다. 그러므로 필자는 약 십여 일 투자해서 웹 페이지에 멋진 그래프를 그릴 수 있는 기초를 갖게 된 것이다. 만약 이 글을 읽는 독자도 필자처럼 십여 일을 노력해 D3.js 기술을 검토해 본다면 독자들도 멋진 인포그래픽을 그릴 수 있는 기초를 완성할 수 있을 것이다. 이 정도 시간 투자면 누구라도 한번 도전해 볼 수 있지 않을까? 생각된다.

이 글은 D3.js 기술에 대해 처음 관심을 가진 독자가 D3.js 기술에 접근했을 때, 어떤 마음가짐을 가지고 어느 정도 시간을 투자해야 하는지를 보여주고, "D3 Tips and Tricks"책을 공부하면서 독자가 입력한 예제가 잘 동작하지 않을 때, 비교해 볼 수 있도록 동작하는 "D3 Tips and Tricks"의 예제 소스를 제공하기 위해 씌어졌다. 그러므로 D3.js 기술을 검토하면서 "D3 Tips and Tricks" 책을 읽는 독자들은 아래 참고 사이트의 필자 샘플을 참고하기 바란다.



참고 사이트

다음은 필자가 D3.js 를 검토하면서 참조한 사이트들이다.

  • 1) D3.js 홈 페이지
  • 아름답고 다양한 그래프로 눈이 휘둥그래진다. D3.js 로 표현 가능한 인포그래픽을 확인해 보라.

  • 2) D3 Tips and Tricks
  • 필자가 D3.js 기술을 익힌 책이다. 이 글의 그래프도 이 책의 예다.

  • 3) NVD3 라이브러리
  • 필자가 다음으로 검토하려는 D3.js 기반의 JavaScript 차트 라이브러다.

  • 5) http://bl.ocks.org/
  • Gist의 웹 페이지 소스를 D3 그래프 페이지로 그려주는 사이트이다. 필자도 이 사이트를 이용해 첫 머리에 그래프를 포함시켰다. Gist의 소스를 그래프로 포함시키는 방법은 이 사이트의 설명을 참조한다.

  • 6) 필자 샘플 소스
  • 첫 머리에 보이는 "D3 Tips and Tricks"의 샘플이다. 첫 머리 그래프를 표시하는 소스는 index.html이다.

2013년 11월 11일 월요일

메시징 매퍼


메시징 매퍼 패턴


기업 통합 패턴(Enterprise Integration Patterns)은 메시징 매퍼(Messaging Mapper) 패턴에 대해 설명한다. 이 패턴은 다음과 같은 상황을 해결한다.



도메인 객체와 메시징 인프라의 독립성은 유지하면서, 이들 사이에 데이터를 이동시키려면 어떻게 해야 할까?


위에서 "도메인 객체와 메시징 인프라의 독립성은 유지하면서"란 말은 메시징 매퍼는 도메인 객체와 메시징 인프라 모두에게 독립적으로 동작해야 한다는 말이다. 즉 도메인 객체에 메시징 매퍼와 관련된 API 등을 포함하지 말아야 하고, 메시징 인프라도 특정 메시징 매퍼에 의존하도록 결합되면 안된다는 의미이다. 그리고 "이들 사이에 데이터를 이동시키려면"이란 말은 도메인 객체를 메시지로 상호 변환할 수 있어야 한다는 말이다. 그 결과 메시징 매퍼는 전략 패턴(Strategy Pattern)에서 알고리즘처럼 도메인 계층과 메시징 인프라 계층 사이에 삽입되며, 대체 가능한 구조를 가지게 된다.

메시징 매퍼(Messaging Mapper) 패턴과 메시지 변환기(Message Translator) 패턴은 모두 변환에 관련된 패턴이다. 기업 통합 문제를 해결함에 있어, 이 두 패턴을 정확히 이해하고 사용해야 한다. 그렇지 않으면 필요한 패턴을 서로 반대로 사용하게 될 수도 있기 때문이다. 이 두 패턴은 계층 내의 이동과 계층 사이의 이동을 기준으로 구분된다. 즉 메시지 변환기 패턴은 메시징 인프라 계층 내에서 메시지가 또 다른 메시지로 변환될 때 사용되고, 메시징 매퍼 패턴은 메시징 인프라 계층의 메시지가 도메인 계층의 객체로 변환될 때 사용된다. 이 구분을 그림으로 표현하면 다음과 같다.

그러나 메시지 변환에 여러 요소들이 개입되거나 복잡한 변환이 요구되는 경우, 출발 메시지를 도메인 객체로 변환한 후 내용 보탬이(Content Enricher)이나 내용 필터(Content Filter)를 이용하여 메시지 내용을 보강한 후, 목적 메시지로 매핑할 수도 있다. 이 경우 메시지 변환의 중간 단계에서 메시징 매퍼가 필요할 수 있다. 이런 경우 두 패턴은 서로 밀접하게 관련된다.

도메인 객체와 메시지의 매핑은 계층 사이의 변환이므로 동일 계층 내의 메시지 변환보다 더 많은 제약 사항이 따른다. 예를 들어 도메인 객체의 참조를 메시지의 필드 값으로 변환해야 하고, 필요한 경우 메시지의 필드 값을 객체 참조로 변환해야 한다. 일반적으로 메시지는 객체처럼 참조 정보를 자유롭게 포함할 수 없기 때문이다. 또 객체는 가변적인 크기를 갖는 컬렉션 형식의 데이터도 표현이 자유로운 반면, 메시지로는 가변적 데이터를 다루기가 쉽지 않을 수 있다.

메시지에 대응되는 객체마다 별도의 프로그램으로 메시징 매퍼를 구현할 수 있다. 이 방법은 가장 간단하기는 하지만, 매핑해야 할 대상이 많은 경우, 개발하기도 어렵고 유지 보수도 어려워진다. 일반적으로 메시지와 객체 사이의 매핑에는 일정한 규칙이 있다. 그리고 특별한 규칙이 있을 수 있고, 필요한 경우 데이터 형식의 변경을 요구할 수도 있다. 만약 이런 정보를 매핑 정의 데이터로 작성하고, 이 정의 데이터를 이용하는 매핑 프레임워크가 개발할 수 있다면, 메시징 매퍼 패턴에 부합되는 구조를 만들기가 더 쉬워질 것이고, 프레임워크화된 메시징 매퍼를 사용하는 프로그램은 좀더 쉽게 메시지와 객체를 매핑할 수 있게 될 것이다. 그러나 매핑 구조를 정의하는 것이 간단하지 않고, 이 정의를 이용하는 프레임워크를 개발하는 것은 많은 시간과 비용이 필요한 일이다.

그동안 메시징 매퍼를 일반화하기 위해 즉 라이브러리나 프레임워크 등 표준 기술을 만들기 위해 많은 노력이 있어 왔다. 그 중에 우리나라의 상황을 돌이켜 보면, 우리나라는 이상하리만치 매핑 정의를 프로그램 로직이나 데이터베이스 테이블로 관리하는 경우를 많이 보게 된다. 일반적으로 소규모 프로젝트는 프로그램 로직이 많이 사용되는데, 이 경우 규모에 맞지 않게 도리어 개발과 유지 보수에 많은 비용과 시간이 들게 된다. 대형 프로젝트에서는 매핑 정의에 데이터베이스를 활용하는 경우가 많은데, 이 경우 데이터베이스 서버를 이용하지만 변경에 따른 프로그램들 사이의 결합을 낮추지도 못해 메시지 포맷이나 객체 구조가 변경된 경우 변경해야 할 대상이 추가적으로 많아져 변경 비용만 추가되는 부작용들이 발생하게 된다. 즉 개발과 유지 보수에 모두 특별한 생산성을 제공해 주지 못한다. 물론 데이터베이스 매핑 정의는 관리 화면을 통해 관리할 수 있는 장점이 있을지 모르지만, 전문의 등록과 변경은 그리 빈번한 작업이 아닌 경우가 많고, 일반적으로 매핑 정의의 변경은 도메인 객체와 도메인 로직의 수정도 수반한다.

메시지 변환에 대해 가장 합리적인 접근은 첫째 객체로 변환하기 쉬운 구조의 메시지 구조를 정의하는 것이고, 둘째는 도메인 객체의 구조를 단순하게 유지하는 것이고, 셋째는 매핑 정의 데이터가 단순하고 이해하기 쉬운 구조를 가지게 하는 것이고, 넷째는 매핑 엔진의 성능을 최적화 하는 것이고, 마지막으로 각 요소의 결합도를 낮추어 느슨한 결합구조를 갖게 하는 것이다. 이런 방향에 대한 많은 노력으로 만들어진 해결책으로는 TLV(Tag Length Value) 구조, ASN.1, BER, DER, Java Serialization, Protocol Buffers, XML, XSLT, JAXB, DOM, SAX, JSON 등등의 기술들이다. 그러나 불행하게도 우리라에서는 여전히 고정 길의 메시지 구조가 자주 선호되고 있고, 매핑 정의는 데이터베이스에 관리되는 방식이 선호되고 있고, POJO 또는 Bean과 같은 단순한 도메인 객체에 대해서는 잘 알지 못하고 있다. 최근에 가장 선호되는 메시징 인프라 계층의 메시지 구조는 JSON 포맷이다. JSON 포맷은 XML보다 도메인 객체로 매핑하는 속도가 현저히 빠르고, JSON 메시지는 별다른 도구 없이도 직접 해석도 가능하고, 웹 환경에서 서버와 클라이언트 모두 잘 이해하는 포맷이기 때문이다.

새로운 기업 인프라나, 차세대 프로젝트나, 리노베이션 프로젝트를 시작하는 경우, 기업은 미래 지향적인 메시지 포맷을 정의하는 것이 합리적일 것이다. 그러나 이런 경우에도 레거시 시스템과 인터페이스해야 하는 경우, 이미 서비스 중인 외부 기관과 통신해야 하는 경우, 해당 시스템의 메시지 포맷을 준수해야 할 필요가 있다. 그러므로 기업 통합의 입장에서 다양한 메시지 포맷의 지원은 필수불가결한 요소이다.


메시징 매퍼의 구현


원론적인 내용은 실무에서 그다지 중요하지 않을지라도 원론에 대한 이해를 바탕으로 실무를 적용해야 합리적인 실무를 구성할 수 있다. 이런 점 때문에 앞서 메시징 매퍼에 대해 원론적인 문제를 잠시 언급했다. 이제 실무적으로 접근해 보자. 이제 개발자들이나 솔루션 아키텍트들에게 도움이 될만한 프로그램에 대해서 설명할 것이다.

실제적인 접근을 위해 실무에서 사용 중인 고정 길이 메시지에 대한 메시징 매퍼를 구현해 보려고 한다. 이 글의 메시징 매퍼는 금융보안연구원에서 발행한 "OTP 전문 기술규격 v1.2"에서 사용되는 메시지들 중 256 바이트 고정 길이의 "OTP 인증 업무 전문"을 도메인 객체로 매핑해 볼 것이다. "OTP 인증 업무 전문"의 메시지 구조는 다음과 같다.

구분 NO DATA 항목 TYPE 길이 SET 비고
요구 응답
공 통
정보부
0 전문길이 N 4 * 0252
1 트랜잭션코드 AN 6 *
2 전문종별코드 N 4
3 거래구분코드 N 6 *
4 테스트구분 AN 1 *
5 송수신코드 AN 1
6 미통지존재여부 N 1 -
7 응답코드 AN 4 -
8 요구기관코드 AN 5 *
9 기준일자 N 8 *
10 전문일련번호 N 8 *
11 업무일련번호 N 8 *
12 응답기관코드 AN 5 *
13 전문전송일자 N 8 *
14 전문전송시간 N 6 *
15 세션정보 N 5 *
16 센터예비필드 AN 15 *
17 기관예비필드 AN 20 *
업무
공통부
18 OTP인증벤더코드 AN 3 *
19 사용자식별코드 AN 40 *
20 OTP일련번호 AN 12 *
업무
개별부
21 OTP 응답값 AN 8 -
22 타기관 사고회복 여부 N 1 -
23 오류횟수 N 2 -
24 Offset AN 16 - -
25 마지막인증성공일자 N 8 - -
26 마지막인증성공시간 N 6 - -
27 마지막제출OTP응답값 AN 8 - -
28 예약 AN 37 - -

참고) A: 영문자 (Alphabetic Characters), N: 숫자 (Numeric Characters)

이 글의 메시징 매퍼는 Apache Camel, Spring, BeanIO, Apache Commons Lang 등의 프레임워크와 라이브러리를 사용한다. 그리고 처리 흐름과는 상관없지만 camel-stream를 이용하여 라우팅 중의 메시지를 표준 출력으로 출력한다. 이 프레임워크와 라이브러리들을 위해 다음 Maven 의존이 필요하다.

메시징 매퍼의 소스는 camel.example.beanio 패키지 아래 있다. Eclipse 환경에서 프로그램을 컴파일하고 실행할 수 있도록 프로그램 소스를 Eclipse 프로젝트로 GitHub에 올려 놓았다. 아래 참고 사이트의 프로그램 소스를 참고하기 바란다. Maven을 설치했다면, Eclipse 환경에서 프로그램 소스를 열지 않고도, 다음과 같이 명령창에서 Maven을 이용하여 컴파일과 실행이 가능하다.

먼저 메시징 매퍼를 호출하는 클라이언트 프로그램을 보자. 아래 클라이언트 프로그램은 Apache Camel을 이용하여 서버의 서비스를 호출한다. 이 과정의 내부에서 메시징 매퍼가 동작한다.

클라이언트 클래스는 main 메소드에서 Camel 프레임워크가 제공하는 Main 객체를 생성하고, 발신(생산자) 객체인 ProducerTemplate을 획득한 후, 요청 도메인 객체를 생성하고, 요청 도메인 객체에 필요한 값을 지정한 후, ProducerTemplate 객체를 이용하여 요청 도메인 객체를 서비스 서버를 호출하고, 응답 도메인 객체를 반환 받는다. 이 클라이언트 클래스는 일반적인 POJO 구조의 클래스로 도메인 객체만 인식하고 메시징 매퍼나 메시지 인프라는 전혀 인식하지 않는다. 즉 코드에 메시지나 서비스 서버에 대한 API들이 전혀 등장하지 않는다.

이 클라이언트의 실행 결과는 다음과 같다.

요청 도메인 객체, 요청 메시지, 응답 메시지, 응답 도메인 객체의 정보가 표준 출력으로 출력된다. 중간에 문자열로 표시된 두 메시지가 요청 메시지와 응답 메시지이다. 이 출력에서 요청 메시지는 메시징 매퍼가 요청 객체를 마샬링(매핑)한 결과이고, 응답 메시지는 메시징 매퍼가 응답 객체를 마샬링(매핑)한 결과이다.

메시징 매퍼의 구현보다 메시징 매퍼의 클라이언트와 실행 결과를 먼저 설명하는 이유는, 필자가 구현하는 메시징 매퍼와 프로그램 구조를 따르면 클라이언트는 메시지나 메시징 인프라에 대해 전혀 인식하지 않아도 된다는 점을 강조하기 위해서이다. 즉 클라터이언트 도메인 계층과 메시지 매핑을 포함한 메시징 인프라 계층은 완전히 느슨한 결합(loose coupling) 구조를 가지게 된다.

실행 결과를 확인했으므로 이제 메시징 매퍼의 구현에 대해 구체적으로 살펴보자. 메시징 매퍼를 구현하기위해 BeanIO 프레임워크를 사용한다. BeanIO 프레임워크는 자바 Bean을 Flate 구조의 스트림으로 변환하는 프레임워크이다. 이 프레임워크는 Apache License를 따른다. 이 프레임워크와 기능이 유사한 Smooks 프레임워크가 있는데, 이 프레임워크는 GNU 라이선스이다. 그러므로 BeanIO가 Smooks보다 라이선스의 활용 면에서 더 자유롭다. 그리고 메시징 매퍼 패턴의 입장에서 BeanIO가 Smooks보다 좀더 현대적인 구조의 매핑 정의 구조를 가지고 있다. 그리고 Apache Camel에서도 BeanIO에 대한 언급이 더 많다. 아마도 라이선스 문제 때문에 GNU 라이선스 기반인 Smooks를 Camel 프로젝트에 포함하여 언급하기 힘든 면도 있을 것이다. 그리고 재미있는 사실은 이 두 프레임워크 모두 자신들 스스로도 메시징 매퍼의 구현체이라는 것을 정확인 인지하고 있지 못하다는 점이다. 물론 이 두 프레임워크는 메시징 매퍼로서 뿐만 아니라 파일 스트림, 문자열, CVS, XML, EDI 등 다양한 외부 데이터에 대한 매핑도 지원한다. 그러므로 패턴이란 문제 상황에 대한 일관되고 반복적인 해결책인 점을 생각해 본다면 이들 프레임워크들의 구현 방법이 거꾸로 패턴으로 인식된 것으로도 볼 수 있을 것이다.

BeanIO는 메시지와 도메인 객체 사이의 매핑 정의에 XML 파일을 이용한다. 위 "OTP 인증 업무 전문"을 BeanIO의 매핑 정의로 표현하면 다음과 같이 표현된다.

매핑 정의 파일에서 공통 정보부는 모든 메시지가 공통으로 포함하는 헤더 부분이므로 템플릿(template)으로 만들었다. 템플릿을 활용하면 메시지 헤더와 같은 공통 부분의 매핑 정의가 단순해진다. BeanIO 매핑 정의에서 각 필드는 필요에 따라 정렬 방식, 공백 채움 문자도 정의 할 수 있고, 데이터의 포맷도 정의할 수 있다. 메시지가 반복부를 가지는 경우를 표현하는 최소, 최대 발생 값을 지정할 수도 있다. 메시지는 record 태그를 이용하여 도메인 객체로 매핑한다. segment 엘리먼트를 이용하면 메시지의 일부도 도메인 객체로 매핑할 수 있다. Record와 segment 태그에 class 속성으로 도메인 객체의 클래스를 지정한다. 이 매핑을 그림을 표현하면 다음과 같이 표현된다.

위 그림에서 Msg0200200101 객체는 요청 도메인 객체이고, Msg0210200101 객체는 응답 도메인 객체다. 각 도메인 객체는 내부에 공통 정보부의 값을 포함하는 Header 객체, 업무 공통부 값을 포함하는 BodyCommon 객체, 업무 개별부를 포함하고 있는 Body200101 객체를 포함한다.

이제 도메인 객체의 구조에 대해 살펴보자. 도메인 객체 클래스인 Msg0200200101의 정의는 다음과 같다. (이곳에서는 클래스를 정의하는 방법에 대해서 설명하므로, 나머지 클래스들에 대해서는 참고 사이트의 프로그램 소스를 참고한다.)

Msg0200200101 클래스는 요청 객체 클래스로 요청 메시지의 세 개의 영역에 대응되는 멤버 객체들을 포함한다. 여기에서 주목할 점은 Msg0200200101 클래스의 일반적인 자바의 Bean 클래스 정의와 달리 마치 C 구조체 정의처럼 설정자(setter)와 획득자(getter)가 없이 public 멤버들만 포함한다는 점이다. 이와 같이 객체를 정의해도 자바의 리플렉션을 활용하는 BeanIO 프레임워크는 해당 멤버 객체의 설정과 획득을 자동으로 처리한다. (매뉴얼에는 설정자와 획득자를 지정하고 Bean 생성자도 포함하라고 언급되어 있지만 위와 같이 간략하게 클래스를 정의해도 잘 동작한다.) 그리고 또 하나 주목해야 할 부분은 객체를 문자열로 변환하는 toString 메소드이다. 이 메소드는 Apache Commons Lang 라이브러리를 이용하는데, 이 라이브러리의 ReflectionToStringBuilder 메소드는 자바 리플렉션을 이용하여 각 멤버들을 자동으로 문자열로 변경해 준다. 위 실행 결과에서 Request Object와 Response Object의 표준 출력 결과과 ReflectionToStringBuilder 메소드가 만든 출력의 결과이다. 이와 같은 방법으로 도메인 객체 클래스를 정의하면 도메인 객체 클래스 정의가 굉장히 수월해 진다. 게다가 C 구조체와 유사한 Bean(POJO) 구조의 도메인 객체를 사용함으로, 도메인 객체 구조로 많이 사용되는 맵(Map)이나 DOM보다 객체 당 메모리 사용도 절약되고 IDE의 자동 코드 오류 검증도 가능하게 되어 개발 생산성이 향상된다.

다음은 Msg0200200101 클래스의 멤버 변수 중 하나인 Header 객체의 클래스 정의이다.

Header 클래스도 특이한 멤버들을 포함하고 있다. 즉 한글 이름을 가진 멤버 객체들을 가지고 있다. 자바는 유니코드 기반의 언어이므로 이렇게 한글로 변수를 정의해도 정상적으로 동작한다. 그리고 이 클래스도 Msg0200200101 클래스와 마찬가지로 C 구조체와 유사한 Bean(POJO) 구조의 클래스이다. 이 한글 멤버 변수는 전문 규약에서 사용되는 메시지 필드의 한글명과 직접 대응된다. 이렇게 데이터의 저장이나 이동을 위한 도메인 객체의 멤버 변수를 한글로 지정하면, 프로그램의 가독성도 높아지고 영문 작명에 따른 불필요한 노력도 제거된다. 이 클래스에는 정수, 문자열, 날짜 등의 데이터 형의 멤버 변수들이 정의되는데, BeanIO의 mapping 정의에 따라 메시지 필드가 지정된 형식의 데이터 형의 멤버 객체로 변환되거나 관례(CoC: Convention over Configuration)에 따라 자동으로 메시지 필드가 멤버의 데이터 형으로 변환된다.

Msg0200200101 객체는 서비스를 처리하는 Service0200200101 클래스에서 다음처럼 사용된다.

Service0200200101 클래스는 POJO 형식의 클래스로 Msg0200200101 객체를 입력 파라미터로 받아 서비스를 처리한 후 생성한 Msg0210200101 객체를 응답으로 반환한다. 위 예에서는 단순히 요청 객체를 응답 객체의 참조로 사용하면서 필요한 부분의 응답 값들을 지정했다. 그리고 이 과정에서 각 멤버 객체의 속성을 획득자(getter)없이 직접 참조했다. 이것이 가능한 이유는 도메인 객체로 사용되는 클래스들을 C 구조체와 비슷하게 public 멤버를 포함한 Bean(POJO) 클래스로 정의했기 때문이다. 그리고 이 서비스 클래스도 앞서 설명한 클라이언트 클래스처럼 메시지 구조에 대해서도 전혀 알지 못한다는 점을 주목해야 한다. 즉 서비스 클래스가 동작하는 도메인 계층과 메시지가 이동하는 메시징 인프라가 완전히 분리되어 있다. 도메인 계층과 메시징 인프라와 완전히 분리되면 도메인 계층의 객체들은 원격지의 서버나 클라이언트 없이도 Unit 테스트가 가능해진다. 이 경우 서비스 개발자는 서비스 클래스를 클라이언트 개발자는 클라이언트 클래스를 상대방의 개발 일정이나 진척 상황에 상관없이 각자 독립적으로 개발하면서 Unit 테스트까지 진행할 수 있게 된다. 그리고 두 개발자들 사이의 통합 테스트는 각자의 Unit 테스트가 완료된 후 진행하면 된다. 즉 도메인 객체만을 이용하는 POJO 형식의 클래스는 개발자들 사이의 결합도를 낮춤으로 동시에 병렬 개발을 가능하게 하고 Unit 테스트를 집중적으로 진행할 수 있게 함으로 개발 생산성을 높인다.

지금까지 메시지가 매핑 정의에 따라 도메인 객체로 변환되고 서비스 객체가 실행되는 과정을 설명하였다. 그런데 이런 매핑(변환)과 호출을 매개하는 프로그램은 어떻게 작성해야 하는가? 가장 직관적인 방법은 BeanIO 프레임워크에서 제공하는 API를 이용하는 것이다. 그런데 이 경우 입력과 출력 엔드포인트의 외부의 데이터 흐름에 매핑 로직을 추가해야 한다. 그리고 이 과정에서 메시징 매퍼 패턴을 따르는 구조를 만들어야 한다. 일 넘어 또 일인 것이다. 그러므로 이런 문제를 해결하기 위해 메시징 매퍼의 구현에 통합 프레임워크인 Apache Camel을 사용한다. Apache Camel을 이용하면 메시지나 도메인 객체의 매핑(변환)에 대한 처리 흐름과 서비스 호출을 DSL(Domain Specific Language)로 간단하게 만들 수 있다. 메시징 매퍼 프로그램의 Camel 라우팅 정의 DSL은 Spring Bean 정의 XML 기반의 DSL을 사용했다. 다음은 Camel의 라우팅 DSL을 포함하는 메시징 매퍼 프로그램의 Spring Bean 정의 XML이다.

이 정의 파일에서 라우팅 정의는 세 부분으로 구성된다. 첫째, toServer 라우팅 정의는 수신한 도메인 객체를 메시지로 마샬링(매핑)하여 서버(direct:server) 엔드포인트로 메시지를 전송한다. 둘째, Server 라우팅 정의는 수신한 메시지를 도메인 객체로 언마샬링(매핑)하고 도메인 객체를 처리하는 서비스를 호출한다. 처리된 결과 도메인 객체는 응답 메시지로 마샬링(매핑)되어 클라이언트(direct:client) 엔드포인트로 전송된다. 셋째, toClient 라우팅 정의는 수신한 응답 메시지를 응답 도메인 객체로 언마샬링(매핑)한다. 중간에 메시지는 stream 라우팅을 통해 표준 출력으로 출력된다. BeanIO의 매핑 정의는 Camel의 beanio 태그에서 참조되고, marshal과 unmarshal 태그를 통해 자동으로 호출된다. 서비스 클래스는 Bean(POJO) 클래스이므로 Spring Bean으로 정의되고, Camel은 이 서비스를 요청 도메인 객체를 파마미터로 포함하여 호출하고 그 반환 객체를 응답 메시지로 언마샬링한다.

이 글에서 구현한 메시징 매퍼는 각 엔드포인트로를 direct 컴포넌트로 사용했는데, 클라이언트와 서버로 분리된 네트워크에서 사용하려면 direct 컴포넌트 대신 Camel의 Mina나 Netty 컴포넌트를 사용할 있다. 그리고 복수 개의 전문도 수용하지 않는 구조를 가지고 있다. 즉 인증 전문 하나에 대한 호출과 매핑과 서비스를 구현하였다. 복수 개의 전문을 서비스들과 매칭시키려면 메시지 디스패처(Message Dispatcher)가 필요하다. 이 글은 완전한 메시지 서비스 솔루션이 아닌 메시징 매퍼의 구현에 초점을 맞추었으므로 메시지 디스패처까지는 다루지 않는다. BeanIO의 매핑 정의를 수정한다면 고정 길이 전문으로의 매핑 뿐만 아니라 XML이나 CSV와 같은 메시지로도 쉽게 매핑할 수 있다. 즉 이상적인 상황이라면 도메인 계층의 프로그램 수정 없이 고정 길이 전문 서비스에 사용된 동일한 도메인 객체를 Web Service 서비스를 요청하는 곳에도 사용할 수 있게 된다.


맺음말


필자는 이 메시징 매퍼 프로그램을 통해 그동안 필자가 설계, 개발 등으로 경험했던 전문 서비스 프레임워크, 전문-HTTP 인터페이스 서버, 메시지 포워딩 프록시 서버, 비동기 서비스, 메시징 매퍼 등등이 가진 개발 생산성의 문제, 유지 보수의 문제, 불필요한 개발 및 운영 요소 등을 될 수 있으면 기업 통합 패턴을 통해 합리적으로 해결해보려고 시도해 보았다. 설명된 내용이 다소 부족하고 이해하기 어려울 지 모르겠으나, 메시징 매퍼를 통해 다음과 같은 것들을 시도하였다. 기업 통합 패턴의 관점에서 메시징 매퍼가 가져야 할 계층 분리와 은닉성에 대한 특징을 언급했고, 기업 통합 패턴 프레임워크를 이용해 메시지 매핑에 필요한 요소들의 손쉬운 결합을 보이려고 했고, 잘 정의된 매핑 프레임워크의 매핑 정의는 가독성을 높이고 개발 생산성 및 유지 보수성을 좋게 한다는 것을 보이려고 했고, C 구조체 스타일의 자바 클래스와 리플렉션의 장점을 설명하려고 했고, POJO 기반의 서비스 개발 모델은 불필요한 연동 테스트의 낭비를 제거할 수 있다는 것을 설명하려 했다. 메시지 통신이 포함된 애플리케이션의 개발은 시스템 프로그램의 영역과 비즈니스 영역의 프로그램이 모두 필요한 부분이고 개발 및 테스트가 상당히 까다로운 분야이다. 그러나 좋은 실행 예를 참조한다면 메시지 애플리케이션을 개발하는데 합리적인 기술을 선택하고 불필요한 시행착오를 줄여 개발 생산성과 유지보수 비용을 줄일 수 있을 것이다. 그리고 거창한 솔루션을 도입하는 것이 능사가 아님도 알아야 한다. 필요한 곳에 필요한 전문가가 필요한 기술을 적절히 활용하는 것이 기술 종속을 피하는 길이고 필요 이상의 비용도 절약하는 방법일 것이다.

메시징 패턴에 대한 이 글을 모티브로 자신들의 프로젝트에 맞는 메시징 매퍼 패턴을 적용하는 개발자나 아키텍트가 있다면 기쁠 것이고, 메시지 매핑과 통신 및 기업 통합에 대하여 필자의 도움이 필요한 곳이 있다면 필자의 자문을 받는 것도 메시지 애플리케이션 프로젝트에 도움이 될 것이라 믿는다.


참고 사이트

2013년 11월 6일 수요일

기업 통합 패턴 목록


필자가 올리는 기업 통합 패턴(Enterprise Integration Patterns) 관련 글을 읽는 독자들이 기업 통합 패턴의 목록이라도 알 수 있도록, 기업 통합 패턴의 전체 패턴 목록을 정리해 보았다. 기업 통합 패턴은 기업 통합을 위한 메시징 기반의 패턴으로 다음과 같은 상황(context)에 대한 방법론을 패턴으로 제공한다. 이 패턴들에 대한 상세한 방법론에 대해 좀더 관심이 있는 독자라면 원서를 읽어보거나, 출판될 필자의 번역서를 참조할 수 있을 것이다.



기업 통합 패턴 개요




메시징 엔드포인트(Messaging Endpoints)


메시지 엔드포인트(Message Endpoint)
메시징 시스템을 통해 메시지를 수신하고 발신하려면 애플리케이션은 어떻게 해야 할까?
메시징 게이트웨이(Messaging Gateway)
애플리케이션의 나머지 부분으로부터 메시징 시스템에 대한 액세스를 캡슐화하려면 어떻게 해야 할까?
메시징 매퍼(Messaging Mapper)
도메인 객체와 메시징 인프라의 독립성은 유지하면서, 이들 사이에 데이터를 이동시키려면 어떻게 해야 할까?
트랜잭션 클라이언트(Transactional Client)
클라이언트는 메시징 시스템과 함께 어떻게 트랜잭션을 제어할 수 있을까?
폴링 소비자(Polling Consumer)
준비된 애플리케이션만이 메시지를 소비하게 하려면 어떻게 해야 할까?
이벤트 기반 소비자(Event-Driven Consumer)
애플리케이션은 어떻게 사용 가능한 메시지를 자동으로 소비할 수 있을까?
경쟁 소비자(Competing Consumer)
메시징 클라이언트가 복 수개의 메시지들을 동시에 처리하려면 어떻게 해야 할까?
메시지 디스패처(Message Dispatcher)
하나의 채널에 대한 복수 소비자들은 자신들의 메시지 처리를 어떻게 조정할 수 있을까?
선택 소비자(Selective Consumer)
수신하려는 메시지만 선택하려면, 메시지 소비자는 어떻게 해야 할까?
영속 구독자(Durable Subscriber)
구독자의 수신 중지 동안, 발생 가능한 메시지 누락은 어떻게 방지할 수 있을까?
멱등 수신자(Idempotent Receiver)
메시지 수신자는 중복 메시지를 어떻게 처리할 수 있을까?
서비스 액티베이터(Service Activator)
애플리케이션은 메시징 기술과 비 메시징 기술 모두를 통해 호출되는 서비스를 어떻게 설계할 수 있을까?


메시지 구축(Message Construction)


메시지(Message)
메시지 채널로 연결된 두 애플리케이션은 어떻게 정보를 교환할까?
명령 메시지(Command Message)
애플리케이션들은 프로시저 호출에 어떻게 메시징을 사용할 수 있을까?
문서 메시지(Document Message)
애플리케이션들은 데이터 전송에 어떻게 메시징을 사용할 수 있을까?
이벤트 메시지(Event Message)
애플리케이션들은 이벤트 전송에 어떻게 메시징을 사용할 수 있을까?
요청 응답(Request-Reply)
애플리케이션은 어떻게 요청 메시지를 발신하고 응답 메시지를 수신할 수 있을까?
반환 주소(Return Address)
응답자는 응답 메시지를 전송할 채널을 어떻게 알까?
상관관계 식별자(Correlation Identifier)
요청자는 수신한 응답으로 어떤 요청에 대한 응답인지를 어떻게 알 수 있을까?
메시지 순서(Message Sequence)
많은 양의 데이터를 메시징을 사용하여 어떻게 전송할 수 있을까?
메시지 만료(Message Expiration)
메시지가 오래되어 사용 중단이 필요한 때를 발신자는 어떻게 지정할 수 있을까?
포맷 표시자(Format Indicator)
변경에 잘 대응하려면 메시지의 데이터 포맷은 어떻게 설계돼야 할까?


메시지 라우팅(Message Routing)


파이프 필터(Pipes and Filters)
독립성과 유연성을 유지하면서 메시지에 대한 복잡한 처리도 수행할 수 있으려면 어떻게 해야 할까?
메시지 라우터(Message Router)
개별 처리 단계들의 결합을 제거하여 메시지를 조건에 따라 서로 다른 필터로 전달할 수 있게 하려면 어떻게 해야 할까?
내용 기반 라우터(Content-Based Router)
단일 로직 기능이 여러 시스템에 물리적으로 분산되어 있는 경우 어떻게 처리해야 할까?
메시지 필터(Message Filter)
불필요한 메시지를 컴포넌트는 어떻게 수신하지 않을 수 있을까?
동적 라우터(Dynamic Router)
효율성을 유지하면서도 목적지에 대한 라우터의 종속성을 없애려면 어떻게 해야 할까?
수신자 목록(Recipient List)
수신자들이 가변적인 경우 어떻게 메시지를 라우팅할까?
분할기(Splitter)
메시지에 포함된 요소들을 각각 처리하려면 어떻게 해야 할까?
수집기(Aggregator)
서로 관련성이 있는 개별 메시지들은 어떻게 묶어 처리할 수 있을까?
리시퀀서(Resequencer)
순서가 뒤바뀐 메시지들 어떻게 올바른 순서로 되돌릴 것인가?
복합 메시지 처리기(Composed Message Processor)
서로 다른 처리를 요구하는 복수 개의 요소들을 포함한 메시지를 처리하면서도, 전체 메시지 흐름을 유지하려면 어떻게 해야 할까?
분산기 집합기(Scatter-Gather)
수신자들 각각에게 메시지를 발신하고 수신해야 하는 경우, 전체 메시지의 흐름은 어떻게 관리할까?
회람표(Routing Slip)
결정되지 않은 일련의 처리 단계들로 메시지를 라우팅하려면 어떻게 해야 할까?
프로세스 관리자(Process Manager)
설계 당시에는 필요한 단계가 알려지지 않았고 순차적이지 않을 수 있는 복합 처리 단계로 메시지를 라우팅하려면 어떻게 해야 할까?
메시지 브로커(Message Broker)
메시지 흐름의 중앙 제어를 유지하면서, 어떻게 메시지와 목적지의 결합을 제거할 수 있을까?


메시지 변환(Message Transformation)


메시지 변환기(Message Translator)
다른 데이터 포맷을 사용하는 시스템들이 메시징을 사용하여 서로 통신하려면 어떻게 해야 할까?
봉투 래퍼(Envelope Wrapper)
메시지 헤더 필드, 암호화 같은 특별한 포맷을 가진 메시지 교환에 기존 시스템을 참여시키려면 어떻게 해야 할까?
내용 보탬이(Content Enricher)
수신한 메시지에 필요한 데이터 항목이 완전하지 않은 경우 어떻게 다른 시스템과 통신할 수 있을까?
내용 필터(Content Filter)
큰 메시지에서 일부 데이터만 필요한 경우, 메시지 처리를 어떻게 단순화할까?
번호표(Claim Check)
시스템을 가로질러 전송되는 메시지의 데이터 크기를 정보 손실 없이 줄이려면 어떻게 해야 할까?
노멀라이저(Normalizer)
의미는 같지만 다른 포맷으로 수신된 메시지는 어떻게 처리할까?
정규 데이터 모델(Canonical Data Model)
다른 데이터 포맷을 사용하는 애플리케이션들을 통합할 때, 어떻게 하면 의존성을 최소화할 수 있을까?


시스템 관리(System Management)


제어 버스(Control Bus)
여러 플랫폼에 걸쳐 분산되어 있는 메시징 시스템을 효과적으로 관리하려면 어떻게 해야 할까?
우회기(Detour)
검증, 테스트, 디버깅 등을 수행하는 단계로 메시지를 통과시키려면 어떻게 라우팅해야 할까?
와이어 탭(Wire Tap)
포인트 투 포인트 채널을 지나가는 메시지는 어떻게 검사할 수 있을까?
메시지 이력(Message History)
느슨하게 결합된 시스템에서 메시지의 흐름을 어떻게 효과적으로 분석하고 디버깅할 수 있을까?
메시지 저장소(Message Store)
메시징 시스템의 느슨한 결합과 임시 보관적 특성을 방해하지 않으면서 어떻게 메시지 정보를 보고할 수 있을까?
스마트 프록시(Smart Proxy)
요청자가 지정한 반환 주소로 응답 메시지를 게시하는 서비스의 메시지는 어떻게 추적할 수 있을까?
테스트 메시지(Test Message)
컴포넌트가 메시지를 처리하면서 내부 오류로 인해 잘못된 메시지를 내보낸다면 어떤 일이 생길까?
채널 제거기(Channel Purger)
테스트 또는 운영 시스템이 교란되지 않게 채널 위에 남겨진 메시지들을 관리하려면 어떻게 해야 할까?


참고 사이트

2013년 10월 10일 목요일

Apache Camel, Hello, world!


1. 들어가며

Apache Camel은 기업 통합에 없어서는 안될 중요한 통합 프레임워크이다. Camel 프레임워크는 일반적인 애플리케이션에 내장 가능한 경량 프레임워크로, 프레임워크 내부에 라우터 엔진, 프로세서, 컴포넌트, 메시징 시스템을 포함하여, 애플리케이션의 내부를 외부 세계와 손쉽게 인터페이스할 수 있게 해준다. 즉 Camel 프레임워크는 애플리케이션, 시스템, 서비스들 사이에서 데이터(Data)와 기능(Function)을 통합(인터페이스)하는 중재자(Mediator)로서 역할한다. 이 글은 Camel 프레임워크가 어떻게 애플리케이션의 통합에 기여하는지를 간단한 "Hello, world!" 애플리케이션의 통합 과정을 통해 보여줄 것이다.

일반적으로 애플리케이션은 외부 세계와 인터페이스하기 위해 다양한 기술을 필요로 한다. 예를 들어 파일을 복사하기 위해서는 Java File Stream API를 사용해야 하고, 데이터베이스를 이용하기 위해서는 JDBC 드라이버를 사용해야 하고, 웹 서비스에 접속하기 위해서는 Apache HttpClient 라이브러리를 사용해야 하고, 이메일을 발신하기 위해서는 JavaMail API를 사용해야 한다. 게다가 새로운 Twitter 서비스를 이용하려 한다면 OAuth에 기반한 Twitter 서비스를 이용해야 한다. 즉 외부 애플리케이션이나, 서비스, 시스템들과 인터페이스 하려는 애플리케이션은 각 인터페이스에 맞는 기술을 애플리케이션 안에 모두 포함해야 한다. 따라서 애플리케이션을 개발하는 개발자가 외부와 인터페이스하는 각각의 기술에 대한 사용하는 방법을 알아야 한다. 그런데 일반적으로 애플리케이션 개발자는 비즈니스 로직 개발자들이다. 그러므로 외부 세계와 인터페이스에 많은 어려움을 호소하곤 한다. 실제로 인터페이스가 연결되지 않아 비즈니스 로직을 개발이 지연되는 경우가 상당히 많이 발생한다. 다음은 애플리케이션이 다양한 외부 시스템들과 인터페이스하는 방식을 그림으로 표현한 것이다.

그러나 애플리케이션이 Camel을 이용하는 경우, 애플리케이션은 Camel을 통해 외부 세계와 인터페이스할 수 있게 된다. 이 경우 Camel이 애플리케이션을 대신해 외부 세계와 인터페이스하게 된다. 이런 구조를 갖게 되면 애플리케이션은 Camel의 인터페이스 기술만으로, 어떤 외부 세계와도 인터페이스 할 수 있게 된다. 즉, 비즈니스 애플리케이션 개발자는 Camel 개발자하고만 의사소통하고, Camel 개발자는 인터페이스 하려는 외부 시스템의 개발자와 소통한다. 이 경우 비즈니스를 개발하는 애플리케이션 개발자의 외부 인터페이스에 대한 개발 부담은 현저하게 줄게 될 것이다. 물론 그 부담을 Camel 개발자가 떠안게 되지만, Camel은 외부 인터페이스 연동을 위해 이미 수백 가지 컴포넌트를 제공하고 있으므로, Camel 개발자는 외부와의 인터페이스에 새로운 프로그램을 작성하기 보다 Camel이 제공하는 컴포넌트를 활용할 수 있다. 이런 개발 과정이 얼마나 극적인 효과를 주는 지 곧 보게 될 것이다. 다음은 Camel을 사용한 애플리케이션의 인터페이스 방식을 그림으로 표현한 것이다.


2. Hello, world to Console

이제 Camel을 사용하는 간단한 애플리케이션을 작성해 보자. "The C Programming Language"에 처음 등장하는 "Hello, World!"를 콘솔에 출력하는 프로그램을 평범한 POJO 형식의 Java 프로그램과 Camel을 이용한 POJO 형식의 Java 프로그램으로 작성해 보자. 먼저 콘솔에 "Hello, World!"를 출력하는 Java 프로그램은 다음과 같다.

위 소스는 Java 개발자라면 모두 이해할 수 있는 간단한 프로그램이다. 이제 위 Java 프로그램과 동일한 결과를 출력하는 프로그램을 Camel를 이용해 작성하면 다음과 같다.

위 프로그램 소스를 살펴보자. 위 소스에서는 기본 Java 프로그램에서는 없었던, Spring Bean 정의 XML 파일인 ToConsole.xml을 Camel Main 객체에 지정한다. (Spring Bean 정의 XML을 사용한 이유는 느슨한 결합(loose coupling)이 가능하도록 Camel을 설정하기 위해서이다. 결합도(coupling)를 고려하지 않는다면 Java 프로그램 소스안이 이 XML을 프로그램적으로도 삽입 할 수 있다.) 프로그램의 main 메소드는 Camel Main 객체를 이용하여 Camel 컨텍스트를 시작한 후, Main 객체로부터 Camel과 통신할 수 있는 생산자(발신자) 객체인 ProducerTemplate를 얻어, "direct:start" URL과 "Hello World!" 문자열을 파라미터로 ProducerTemplate 객체의 requestBody 메소드를 호출한다. (생산자(producer) 또는 발신자(sender)는 기업 통합 패턴에서 사용하는 용어로 메시지를 발신하는 개체를 말한다.) 그리고 main 객체를 종료한다. Camel Context의 초기화와 종료를 제거하고 보면 처음의 Java 프로그램에 비해 그렇게 복잡하지 않다. 그렇다면 ToConsole.xml 파일이 혹시 복잡한 것은 아닐까? ToConsole.xml을 살펴 보자.

위 XML 설정을 보면 Camel Context를 정의하고 그 안에 라우팅 로직을 하나 지정했다. 이 설정은 "direct:start" 엔드포인트에서 출발하여 "stream:out" 엔드포인트로 도착하는 라우팅을 지정한다. ToConsole.xml을 기업 통합 패턴(EIP) 다이어그램으로 보면 다음과 같다.

여기에서 "direct:start" 엔드포인트는 Camel이 애플리케이션으로부터 동기 호출을 수신하는 출발 지점이다. "stream:out" 엔드포인트는 Camel이 표준 콘솔로 메시지를 발신하는 지점이다. 즉 위 라우팅은 Camel의 "direct:start" 엔드포인트로 수신된 메시지를 "stream:out" 엔드포인트로 전달하라는 정의를 담고 있다.

프로그램을 컴파일하고 실행하기 위해서는 다음의 Maven 의존이 필요하다.

하나는 Camel을 Spring 프레임워크와 함께 사용하기 위해 필요한 의존이고, 하나는 Camel의 Stream 컴포넌트 라이브러리에 필요한 의존이다.

Eclipse 환경에서 프로그램을 컴파일하고 실행할 수 있도록 프로그램 소스를 Eclipse 프로젝트로 GitHub에 올려 놓았다. Maven을 설치했다면, Eclipse 환경에서 프로그램 소스를 열지 않고도, 다음과 같이 명령창에서 Maven(mvn)을 이용하여 컴파일과 실행이 가능하다.


3. Hello, world to Log

지금까지 작성한 Console 출력 애플리케이션을 log4j를 통해 로그로 기록하는 프로그램으로 수정해 보자. 수정된 프로그램 소스는 다음과 같다.

위 소스에서 Console로 출력하는 프로그램과 달라진 점은 Spring Bean 정의 XML 파일이 ToConsole.xml에서 ToLog.xml로 달라진 것 밖에 없다. Spring Bean 정의 파일을 새로 지정한 이유는 단지 이곳의 예를 위해서 필요했기 때문이다. 실제 프로그램에서는 애플리케이션 소스를 수정하지 않고 Spring Bean 정의 XML 파일의 라우팅 정의를 수정함으로 애플리케이션의 출력을 즉시 Console에서 Log로 변경할 수 있다. ToLog.xml을 살펴 보자.

라우팅 정의에서 출발지는 "start:direct"로 ToConsole.xml에서와 같고 도착지의 "log:ToLog?level=WARN"로 ToConsole.xml의 "stream:out"와 다르다. Camel에서는 라우팅의 엔드포인트(도착지, 출발지)에 따라 인터페이스하는 외부 세계가 달라진다. 즉 ToLog.xml의 외부 세계는 이제 로그 라이브러리가 된 것이다. 상단의 logFormatter Bean 객체의 정의는 로그 기록 포맷을 지정하는 포맷터로 로그 기록 포맷을 자유롭게 커스터마이징할 수 있게 해 준다. 이곳에서 사용한 HelloFormatter는 메시지 본문의 문자열을 로그로 기록한다. HelloFormatter 소스는 프로그램 소스가 올라가 있는 GitHub를 참조한다. ToLog.xml의 EIP 다이어그램은 다음과 같다.

프로그램을 컴파일하고 실행하기 위해서는 다음의 Maven 의존이 필요하다.

Log 컴포넌트는 Camel Core 라이브러리에 포함되어 있으므로 stream 컴포넌트처럼 별도의 라이브러리가 필요하지 않다. Eclipse에서 프로젝트를 열지 않는 경우, 다음과 같이 명령창에서 Maven(mvn)을 이용하여 컴파일과 실행이 가능하다.


4. Hello, world to Mail

지금까지는 별로 특이한 사항은 없다. 이제 좀더 특별한 애플리케이션을 만들어 보자. 애플리케이션에서 메일을 발신하려고 한다. 애플리케이션은 어떻게 수정돼야 할까? 메일을 발신하는 ToMail.java는 다음과 같다.

위 소스도 ToCosole이나 ToLog 애플리케이션과 동일하다. 단지 Spring Bean 정의 XML 파일이 ToMail.xml로 달라진 것 밖에 없다. 즉 애플리케이션은 "Hello, world!"를 메일로 발신하기 위해서도 수정되지 않는다. ToMail.xml을 살펴 보자.

라우팅 정의에서 "start:direct" 엔드포인트는 이전 프로그램들 설정들과 동일하고 도착지 엔드포인트가 메일 엔드포인트인 "smtp:barunmo.com?username=testuser&password=testpassword"로 바뀌었다. 메일을 발신하기 위해서는 SMTP 서버와 메일 주제, 발신자, 수신자 등이 추가적으로 필요한데, 이런 정보들은 XML에 설정으로 지정했다. ToMail.xml의 EIP 다이어그램은 다음과 같다.

이전 라우팅 정의와 달리 중간에 setHeader 태그로 지정된 부분은 메시지 변환기(Message Translator)로 표시되었다. 이 메시지 변환기는 EIP 패턴 다이어그램 중 하나이다. 더 많은 패턴 다이어그램이 기업 통합 패턴(Enterprise Integration Patterns)에 정리되어 있다.

프로그램을 컴파일하고 실행하기 위해서는 다음의 Maven 의존이 필요하다.

Camel Mail 컴포넌트는 내부적으로 JavaMail API를 이용한다. Camel Mail 컴포넌트의 의존이 필요한 관련 라이브러리를 자동으로 추가해 줌으로 애플리케이션에서는 별도로 추가할 의존은 없다. Eclipse에서 프로젝트를 열지 않는 경우, 다음과 같이 명령창에서 Maven을 이용하여 컴파일 및 실행이 가능하다. 단 GitHub의 소스에서 내려 받은 소스에는 메일 정보들이 가상의 값으로 채워져 있으므로, 이 예를 실행 전에 메일 수신자와 발신자 정보 그리고 smtp 엔드포인트의 SMTP 서버 정보, 사용자, 패스워드를 테스트 하는 시스템과 개발자의 정보로 수정해야 한다.

다음은 ToMail 프로그램을 실행하여 필자가 수신한 메일이다.

ToMail 애플리케이션도 Spring XML 설정을 가리키는 부분 이외의 프로그램의 수정이 없으면서, 메일을 발신하는 기능을 갖게 되었다. 다음은 마지막으로 Twitter와 인터페이스하는 애플리케이션을 작성해 보자.


5. Hello, world to Twitter

요즈음 애플리케이션들은 소셜 네트워크와 뗄래야 뗄 수 없는 환경에 있다. 그러므로 이제 우리의 애플리케이션도 트위터로 트읫을 전달하게 만들어 보자.

트위터 애플리케이션을 만들기 위해서는 https://dev.twitter.com/apps/new 사이트에서 애플리케이션을 등록하고 OAuth 등의 정보를 획득해야 한다. 필자도 이 사이트에 접속하여 Barunmosoft 계정으로 접속하는 애플리케이션을 등록했다. 이 애플리케이션을 테스트하기 위해서는 독자들도 이 사이트에 접속하여 트위터 애플리케이션을 등록해야 한다. 다음은 Hello, world를 트윗하기 위한 ToTwitter.java의 소스이다.

위 소스도 ToCosole이나 ToLog나 ToMail 애플리케이션과 마찬가지로 소스상에 변화는 없다. 단지 Spring Bean 정의 XML 파일이 ToTwitter.xml로 달라진 것 밖에 없다. 즉 애플리케이션은 트윗을 사용하기 위해서도 수정이 필요 없다. ToTwitter.xml을 살펴 보자.

라우팅 정의는 이전 프로그램들처럼 도착지 엔드포인트가 트위터 엔드포인트인 "twitter://timeline/user"로 바뀌었다. 트윗하기 위해서는 트위터에 애플리케이션을 등록해야 하는데, 이 등록 과정을 진행하고 나면 트위터 사이트는 consumerKey, consumerSecret, accessToken, accessTokenSecret 값을 생성해 준다. 이 생성된 값을 twitter 컴포넌트의 Bean 정의에 속성으로 지정한다. 위 설정에서는 트위터 관련 토큰들이 엔드포인트 URI에 키-값으로 지정됨으로 엔드포인트 URL이 길어지는 것을 방지하기 위해, twitter 컴포넌트의 팩토리 Bean에 해당 값들을 속성으로 지정했다. 위 라우팅 정의의 EIP 다이어그램은 다음과 같다.

프로그램을 컴파일하고 실행하기 위해서는 다음의 Maven 의존이 필요하다.

Camel Twitter 컴포넌트는 내부에서 Twitter4J 를 이용한다. Camel Twitter 컴포넌트의 의존이 필요한 관련 라이브러리를 자동으로 추가해 줌으로 애플리케이션에서는 별도로 추가할 의존은 없다. Eclipse에서 프로젝트를 열지 않는 경우, 다음과 같이 명령창에서 Maven을 이용하여 컴파일 및 실행이 가능하다. 단 프로그램 실행 전에 트위터 사이트로부터 받은 consumerKey, consumerSecret, accessToken, accessTokenSecret 값을 Twitter.xml에 올바르게 지정해야 한다.

다음은 ToTwitter 프로그램을 실행하여 필자가 수신한 트윗의 타임라인이다..


6. Camel의 애플리케이션 적용 패턴

지금까지 프로그램은 JVM 환경의 Java 애플리케이션에 Spring 프레임워크와 결합된 Camel을 내장하는 Camel의 사용 패턴을 사용했다. 이것은 Camel이 애플리케이션에 얼마나 쉽게 내장할 수 있는지를 보여 주는 하나의 예에 불과하다. Camel Core는 약 2.5M bytes 정도의 작은 크기를 가지면서도 POJO 방식을 지원하여 애플리케이션에 쉽게 내장될 수 있다. 이 작은 프레임워크는 애플리케이션에 내장되든, 독립된 애플리케이션으로 동작하던 애플리케이션들을 손쉽게 통합할 수 있게 해 준다. 이점이 기존 애플리케이션 통합 제품인 EAI 제품들이 일반적으로 메시징 미들웨어를 기반으로 통합하는 방식과 다른 점이다. 다음은 애플리케이션에서 Camel을 적용하는 몇몇 패턴들을 보여준다.


7. 맺음말

이 글에서는 애플리케이션이 Apache Camel 프레임워크로 외부 세계와 소통하는 예로 "Hello, world!" 메시지를 콘솔에 출력하는 애플리케이션에서부터 "Hello, world!" 메시지를 트위터의 타임라인으로 트윗하는 애플리케이션까지 개발해 보았다. 이 과정에서 애플리케이션은 일관되게 Camel의 ProducerTemplate 객체를 사용하여 "Hello, world!" 메시지를 전송했으며, Camel 설정의 수정을 통해 이 "Hello, world!" 메시지는 점점 더 복잡한 프로토콜을 가진 외부 시스템으로 전송되었다. 이 글의 마지막에 보인 애플리케이션처럼 애플리케이션을 트위터 시스템과 연동하게 하는 방법에 있어서, Camel보다 더 간단하게 애플리케이션을 트위터 시스템과 연동하게 할 수 있는 솔루션이나 EAI 제품이 있을까?

이 글을 꼼꼼히 읽은 독자라면 Camel이라는 통합 프레임워크의 가능성을 잘 이해했을 것이다. 즉 Camel 프레임워크를 이용해 애플리케이션에게 일관된 통합 인터페이스를 제공했고, 애플리케이션으로부터 통합 로직을 분리했으며, 외부 시스템의 변경은 설정으로 대응하도록 했다. 그러나 이 글에 등장하는 패턴은 기업 통합 패턴 중 하나인 메시징 게이트웨이(Messaging Gateway) 패턴을 적용한 것으로 볼 수 있으며, 또한 Camel의 많은 기능들 중 극히 일부분 이곳에 사용되었다. 이 글에서는 Camel의 능력을 과시(?)하기 위해 외부 시스템에 대한 인터페이스에 집중해서 Camel의 사용을 보여 주었지만, 사실 중요한 것은 어떻게 인터페이스할 것인가 보다 어떻게 통합할 것인가 이다. 즉 나무보다는 숲을 보는 통합 아키텍처 관점이 더 중요하다. 그런 관점에서 이 곳에 설명한 프로그램 예는 단지 기업 통합이라는 신세계의 입구를 본 것뿐이다.

일반적으로 우리는 프로시저 호출(procedure call) 방식의 동기 패러다임의 아키텍처에 익숙하다. 그러나 기업 통합 패턴(Enterprise Integration Patterns)은 메시지 전달(Messaging) 방식의 비동기 패러다임으로 기업 아키텍처를 접근한다. 이런 관점의 전환이 Apache Camel이라는 통합 프레임워크를 탄생시켰고, 지금도 난제로 여겨지고 있는 애플리케이션 통합에 신선한 전환점을 만들어 주고 있다.

Apache Camel은 Red Hat의 JBoss Fuse Middleware에도 포함되어 있다. JBoss Fuse는 오픈 소스로 구성된 메시징 서버(ActiveMQ), ESB 엔진(ServiceMix), 통합 프레임워크(Camel), OSGi 컨테이너(Karaf)의 애플리케이션 통합 미들웨어 제품군이다. JBoss Fuse는 기존 기업 내 애플리케이션 통합에 사용되던 독점적 폐쇄적 EAI 제품을 대체할 수 있다. 그리고 Red Hat에서 제품으로 출시되고 있으므로 필요한 경우 상용 제품과 동일한 수준의 기술 및 제품을 지원 받을 수 있다. 그러므로 오픈 소스인 Apache Camel에 대해 관심을 가진 누구라도 오픈 소스를 직접 다운받아 활용해 보거나, Red Hat을 통해 기업 통합에 필요한 컨설팅을 받을 수 있을 것이다. 필자의 회사도 Red Hat과 Fuse 제품의 기술 지원 파트너이다.

Spring 프레임워크의 창시자인 Rod Johnson이 2012년에 출판된 "Spring Integration In Action" 책의 서문 첫 문장에 다음과 같이 썼다. "Integration is currently a hot topic" (통합은 현재 뜨거운 주제이다.) 즉 미국에서도 현재 애플리케이션들 사이의 통합이 뜨거운 주제인 것이다. 그리고 Spring Source에서도 Apache Camel 보다는 늦었지만 Spring Integration 프레임워크를 만들어 열심히 발전 중에 있다. (참고로 필자는 Spring Integration의 채널 중심의 통합 접근 방법을 별로 좋아하지는 않는다.) 그리고 Camel이나 Spring Integration이나 그 사상은 모두 기업 통합 패턴(Enterprise Integration Patters) 책에 기반한다. (이 책은 필자가 번역해 "기업 통합 패턴, 에이콘 출판"으로 출간됐다.) 우리나라 사정은 어떤가? 우리나라도 애플리케이션들 사이의 통합이 심각한 문제이나, 전체 아키텍처 측면에서는 어떻게 접근해야 하는지 별다른 방안을 갖지 않는 것 같다. (물론 독점적 EAI 제품을 도입한다거나 스파게티 구조의 인터페이스를 가지기도 한다.) 통합에 있어서, 인터페이스를 개별적으로 고민하지 말고 전체 아키텍처를 고민해야 한다. 그래야 인터페이스의 추가에 따르는 개발 비용과, 유지보수에 따르는 비용을 절감할 수 있게 된다. 이런 통합 문제를 해결하기 위한 중심에 기업 통합 패턴(Enterprise Integration Patterns)이 있고 Apache Camel이 있다.


참고 사이트