2014년 7월 4일 금요일

실시간 트위터 이미지 사이트 개발기 3


웹소켓(WebSocket)

웹 브라우저에서 오랫동안 사용한 통신 프로토콜은 HTTP다. HTTP는 동기 방식의 요청-응답 프로토콜로 반드시 클라이언트인 웹 브라우저가 요청해야만 서버인 웹 서버가 응답하는 프로토콜이다. 그러므로 HTTP 접속은 요청으로 시작해서 요청-응답을 반복하다가 반드시 응답으로 끝난다. 이 방식은 최초에 웹 혁명을 이끄는 데 있어서 중요한 단순성을 제공했다. 단순한 요청-응답 방식은 극단적인 수준까지 요청-응답을 처리하도록 서버를 개선하도록 했다. 범용 웹 사이트를 가능하게 하기 위해 TCP/IP 스택은 비약적으로 개선됐고, 아파치 웹 서버 같은 견고하고 성능도 좋은 서버들도 개발됐다. 사실 웹 기술이 보급되기 전까지 전통적인 클라이언트 서버 방식의 서버는 수십 클라이언트들의 접속을 처리할 수 있다면 할 일을 충분히 한다고 인정받던 시절도 있었다. 그러나 웹의 보급과 발전 덕분에 서버는 동시에 수천 접속을 유지하고 수만 요청도 처리할 수 있도록 비약적으로 진화했다. C10k 문제(일만 동시 접속 문제)라는 용어가 등장할 정도로 다수 동시 접속 처리 문제는 개발자들을 골치 아프게 했지만, 현재는 다양한 아키텍처 패턴을 이용해 이 문제를 극복하고 있다. 그러나 시대가 발전하면서 요청-응답으로는 처리하기 어려운 시나리오들도 웹 브라우저를 이용해 처리하고자 하는 요구가 끊임없이 확대됐다. 예를 들어 웹에서 실시간으로 변하는 증권 시세를 표시하려면 요청-응답 방식만으로는 해결할 수 없었다. 해결했다손 치더라도 속도나 성능 효용성이 충분하지 못했다. 그래서 자바 애플릿이나 액티브X 컨트롤을 이용해 소켓 통신으로 문제를 우회하려고도 했다. 그러나 이런 플러그인 기술들도 만족스럽지 못했고 보안 취약성이 또 다른 문제가 되기도 했다. 또 다른 예로 웹 채팅 애플리케이션이 있다. 웹 채팅을 구현하려면 Ajax 같은 복잡한 기술을 사용해야 그런대로 가능했다. 이런 방법들은 온전한 비동기 통신 기술이 아니다. 채팅은 상대방이 입력한 글이 웹 브라우저의 요청 없이도 즉시 본인의 웹 브라우저에 도착해야 하는 전형적인 비동기 통신 모델이다. 그런데 Ajax도 반드시 요청해야만 응답을 (비동기로) 수신할 수 있으므로 온전한 비동기 통신 방식은 아니다. 결국 요청-응답 방식의 HTTP 프로토콜 덕에 가질 수 있었던 단순성이 웹의 성공을 이끌었던 반면, 이 단순성은 웹 기반 비즈니스 시나리오가 확대됨에 따라 점점 부족함으로 작용하게 됐다. 완전한 비동기를 웹 브라우저에서 사용하려고 할 때, 단순함이 도리어 복잡한 Ajax 방식을 사용하게 강요한 면도 있었다. 이와 같이 확장되는 웹 기반 비즈니스 시나리오를 충족시키기 위해서는 좀더 편리한 통신 방법이 필요했다. 그리고 그 산물로 완전한 양방향 통신을 지원하는 웹소켓 프로토콜이 등장했다.

웹소켓 프로토콜은 상당히 최근에 등장했다. 2011년에서야 IETF는 웹소켓 프로토콜을 정의한 RFC 6455 문서를 표준으로 제정했다. 이에 따라 웹 브라우저들은 2011년부터 상호 호환성이 보장되는 RFC 6455 웹소켓 표준으로 웹소켓 지원을 통일했다 크롬 16, 사파리 6, 파이어폭스 11, 인터넷 익스플로러 10 이후 웹 브라우저들은 RFC 6455 웹소켓 표준을 준수한다. 웹소켓은 글자 그대로 소켓과 거의 같은 기능을 제공한다. 즉 일반적인 소켓이 제공하는 양방향 통신을 제공한다. (그러나 TCP/IP 계층보다 상위 계층 프로토콜로 독자적인 패킷 구조를 갖는다.) 그러므로 HTTP 프로토콜과 달리 접속이 완성된 후 TCP 소켓 접속처럼 서버의 우선 전송도 가능해 진다. 즉 요청-응답 순서가 클라이언트에서 서버뿐만 아니라 서버에서 클라이언트로도 가능하다. 그러므로 웹소켓은 클라이언트 풀(pull) 뿐만 아니라 서버의 푸시(push)도 어렵지 않게 제공한다. 그 결과 웹소켓은 동기 방식뿐만 아니라 비동기 방식 통신 프로토콜로 완벽하게 구현하게 하는 기반을 제공한다.

웹소켓은 HTTP 프로토콜에서 프로토콜 갱신 핸드쉐이크를 통해 웹소켓 프로토콜로 전환이 되는 구조를 가지고 있어, HTTP 표준 포트인 TCP 80 포트를 그대로 활용할 수 있다는 장점을 가진다고 한다. 그러나 이 부분에 있어서 필자는 다른 견해를 갖는데, 그 이유는 TCP 80 포트를 바인드하는 웹 서버가 HTTP 프로토콜과 웹소켓 프로토콜을 동시에 처리하려면 내부 아키텍처가 상당히 다른 두 프로토콜 스택을 서버 안에 함께 포함해야 한다. 웹소켓은 Stomp 같은 상위 프로토콜의 하부 프로토콜로 사용되기도 하는데 이 경우 또 다른 Stomp 프로토콜 스택도 웹 서버에 포함시켜야 하는 문제가 발생한다. 프로토콜 스택 개발은 웹 서버 개발과는 다른 문제일 수 있다. 그리고 아직 웹 서버들이 충분이 웹소켓을 다루는 기술이 성숙되지 않은 문제도 있다. 그러므로 웹소켓을 사용해야 한다면 웹 서버와 별도로 전용 웹소켓 서버를 운영하면서 TCP 포트를 별도로 개방하거나 방화벽 등의 정책으로 인해 접속 문제가 있을 수 있다면 별도 서버에 또 다른 TCP 80 포트를 개방해 웹소켓 서버를 운영하는 방법이 합리적이지 않을까 생각된다. 아직까지는 웹소켓의 사용 방식에 대해 뚜렷한 일관적인 방식이 등장하고 있지는 않은 것 같다.

웹소켓의 또 다른 문제는 웹소켓 위의 애플리케이션 프로토콜이 충분히 성숙돼지 않았다는 점이다. 웹소켓은 TCP 계층에 아주 얇은 층을 추가한다. 그러므로 웹소켓을 직접 사용하는 것은 마치 TCP 소켓을 직접 사용하는 것과 같다. 일반적으로 개발자들은 TCP 소켓을 직접 사용하기보다 상위 애플리케이션 프로토콜을 이용한다. 이런 예로 Telnet, Ftp, Ssh, HTTP, AMQP, OpenWire, Stomp, MQTT 등등이 애플리케이션 프로토콜이다. 그러므로 웹소켓을 충분히 잘 활용하려면 웹소켓 위의 애플리케이션 프로토콜이 많아지고 또 쓰임새도 정비돼야 한다.


실시간 트위터 이미지 사이트의 웹소켓 사용

웹소켓을 먼저 설명한 이유는 실시간 트위터 이미지 사이트도 웹소켓을 사용하기 때문이다. 실시간 트위터 이미지 사이트는 웹소켓을 아주 쓸모 있게 사용한다. 실시간 트위터 이미지 사이트의 전체적인 정보 흐름과 통신 프로토콜들은 다음과 같다.

1) 미국에 위치한 실시간 트위터 이미지 사이트의 백그라운드 Camel 서비스는 트위터에서 고양이로 트윗을 검색해 결과들을 수신한다. 2) 그 중 이미지를 포함한 트윗을 추출해 한국에 위치한 ActiveMQ 서버에 OpenWire 프로토콜을 사용해 메시지로 전달한다. 3) 메시지를 수신한 ActiveMQ 서버는 메시지를 게시 구독 방식의 토픽에 게시한다. 실시간 트위터 이미지 사이트에서 페이지를 조회한 웹 브라우저는 이미 웹소켓 위의 Stomp 프로토콜로 ActiveMQ 서버에 접속한 토픽 구독자다. 4) 토픽에 게시된 고양이 이미지 트윗 메시지 사본은 웹소켓 위의 Stomp 프로토콜을 통해 ActiveMQ 서버로부터 웹 브라우저로 비동기적으로 전송된다. 웹 브라우저는 비동기적으로 수신된 메시지를 웹 브라우저 화면에 고양이 쎔네일 이미지로 출력한다.

미국에 위치한 실시간 트위터 이미지 사이트 웹 서버의 Camel 서비스와 대한민국에 위치한 ActiveMQ 서버는 메시징 프로토콜로 OpenWire 프로토콜을 사용했다. OpenWire 프로토콜은 다양한 언어나 플랫폼에서 ActiveMQ에 접속할 수 있게 하는 메시징 프로토콜이다. ActiveMQ는 OpenWire, AMQP, XMPP, MQTT 등 다양한 메시징 프로토콜을 지원한다. 웹 브라우저와 ActiveMQ 서버는 웹소켓 위의 Stomp 프로토콜을 사용한다. Stomp 프로토콜을 사용한 이유는 WebSocket 위의 Stomp 프로토콜을 지원하는 STOMP.js라는 JavaScript 라이브러리가 있고, ActiveMQ 서버도 웹소켓 위의 Stomp 프로토콜을 잘 지원하기 때문이다. 참고로 TCP/IP 위의 Stomp 프로토콜과 웹소켓 위의 Stomp 프로토콜은 호환되지 않는다. TCP/IP 위의 Stomp 프로토콜은 Stomp 프로토콜이 TCP 계층 바로 위에 구현되는 반면, 웹소켓 위의 Stomp 프로토콜은 TCP 계층 위에 웹소켓 계층이 있고 그 위에 Stomp 프로토콜이 구현된다. 그러므로 Stomp 프로토콜을 지원한다고 해도 직접적으로 웹소켓 위의 Stomp 프로토콜로 통신할 수 없다. 문자 기반 프로토콜이어서 인지, 웹소켓 위에서 가장 구현하기 쉬워서 인지 상당히 이른 시기에 웹소켓 위의 Stomp 프로토콜은 등장했다. 한편 RabbitMQ의 기본 프로토콜인 AMQP 프로토콜이 웹소켓 위의 JavaScript 라이브러리로 개발이 된다면 웹 브라우저에서도 AMQP 프로토콜을 사용할 수 있을 것이다. 그러나 현재 RabbitMQ 쪽에서는 Node.js와 연결에 주력하고 있는 것 같다. RabbitMQ는 Node.js에서 동작하는 AMQP JavaScript 라이브러리까지만 지원하고 있다. 아마도 이 JavaScript 라이브러리는 TCP/IP 위에서 구현된 라이브러리일 것이다. 즉 AMQP 프로토콜의 하부 통신 계층이 TCP일 것이다. 그러므로 현재 웹소켓 위에서 사용할 수 있는 메시징 프로토콜 라이브러리는 STOMP.js이 유일하다. (글을 쓰면서 좀더 조사해 보니 최근 들어 웹소켓 위의 MQTT 프로토콜도 사용 가능한 것으로 보인다. 이에 대한 검토는 독자들에게 맡긴다!) 여기서 주목할 점은 메시징 프로토콜과 메시지 채널(큐, 토픽)은 서로 다른 종류의 개체라는 점이다. 즉 같은 메시지 채널이라도 여러 프로토콜로 발신, 수신, 게시, 구독이 가능하다. 실시간 트위터 이미지 사이트도 동일한 토픽에 대해 메시지 전송은 OpenWire 프로토콜을 메시지 수신은 웹소켓 위의 Stomp 프로토콜을 사용했다.

웹소켓을 사용하면 웹 브라우저를 기업 통합 솔루션의 비동기 메시징 컴포넌트의 일원으로 포함시킬 수 있게 된다. 실시간 트위터 이미지 사이트는 트위터 이미지 메시지를 비동기로 전송하는 용도로 간단하게 웹소켓을 사용했지만, 앞서 언급했듯이 웹소켓은 웹 브라우저의 요청-응답 또는 폴링 방법으로는 해결하기 어려운 실시간 비동기 정보를 손쉽게 처리할 수 있게 해준다. 예를 들어 실시간 뉴스, 실시간 대시보드, 실시간 제어, 실시간 게임, 실시간 그래프 애플리케이션 같은 것들도 웹 브라우저로 구현할 수 있는 가능성을 열어준다. 이를 위해서는 SVG(SVG(Scalable Vector Graphics)나 Canvas 같은 HTML5의 다른 기능들도 활용해야 할 것이다. 또한 필자도 웹소켓이 요청-응답 형식의 기존 HTTP 프로토콜을 완전히 대체할 수 있다고는 생각지 않는다. 기존 HTTP 프로토콜은 화면 조회, 웹 서비스, Restful, Ajax RPC 등 다양한 요청-응답 패턴을 여전히 잘 수용한다. 그러므로 웹소켓의 역할은 요청-응답 방식의 HTTP 프로토콜로는 쉽게 해결할 수 없었던 실시간 비동기 통신을 웹 브라우저에 제공함으로 웹 브라우저의 사용 범위를 넓혀주는 것으로 봐야 한다. 즉 실시간 비즈니스 시나리오가 포함된 멋진 웹 애플리케이션을 만들려면, HTTP 프로토콜과 웹소켓 프로토콜 중 어느 하나만을 고집하는 것이 아니라 서로의 장점을 함께 활용해야 한다. 편식은 언제나 몸을 해롭게 한다.

웹소켓은 표준이 2011년에 완성됐고 표준을 지원하는 브라우저도 2011년 이후 버전들이고 특히 대한민국에서 많이 사용하는 인터넷 익스플로러는 버전 10부터 지원되므로 아직은 웹소켓을 사용한 사례가 많지 않은 것도 사실이다. 그러나 웹소켓을 범용 서비스가 아닌 기업 통합 등 기업의 특화된 서비스에 이용한다면 실시간 정보의 사용에 있어서 전용 클라이언트 애플리케이션 못지 않게 훌륭한 웹 브라우저 애플리케이션을 개발할 수 있을 것이라 기대한다. 이 글을 통해 독자들도 웹소켓의 가능성에 주목할 수 있는 계기가 됐으면 한다.



참고 사이트


2014년 7월 2일 수요일

분산 비즈니스 트랜잭션과 오류 해결 방법


애플리케이션에서 우리가 일반적으로 트랜잭션이라 부르는 용어는 실제로 두 가지로 구분된다. 첫째, 시스템 트랜잭션(system transaction)[EAA]이다. 시스템 트랜잭션은 일반적으로 단일 데이터베이스 수준에서 처리되는 단일 트랜잭션을 말한다. 둘째, 비즈니스 트랜잭션(business transaction)[EAA]이 있다. 비즈니스 트랜잭션이란 하나 이상의 요청을 처리하기 위해 여러 시스템 트랜잭션을 포함한 트랜잭션을 말한다. 예를 들어 여행 예약을 진행하는 경우 항공 예약, 호텔 예약, 렌터카 예약, 기차 예약 등 여행하면서 타거나 머물거나 묵게 될 각 장소가 모두 예약돼야 완성되는 트랜잭션이 비즈니스 트랜잭션이다. 비즈니스 트랜잭션은 하나 이상의 요청과 일련의 시스템 트랜잭션을 포함하므로 완성되기까지 긴 시간이 필요하다.

일반적으로 기업은 위 두 트랜잭션과 더불어 분산 시스템 트랜잭션(distributed system transaction)과 분산 비즈니스 트랜잭션(distributed business transaction)이 필요한 경우가 있다. 우선, 분산 시스템 트랜잭션은 분산된 자원들이 트랜잭션 프로토콜에 따라 마치 단일 시스템 트랜잭션처럼 보이도록 하는 기술로 2단계 커밋(two-phase commit) 기술로 구현된다. 비즈니스 로직은 분산 트랜잭션을 시작한 애플리케이션에 집중된다. 반면, 분산 비즈니스 트랜잭션은 하나 이상의 요청에 분산된 비즈니스 애플리케이션들이 여럿 참여하고 참여한 애플리케이션이 일련의 시스템 트랜잭션들을 포함할 수 있는 트랜잭션을 말한다. 분산 비즈니스 트랜잭션은 시스템 트랜잭션이 하나 이상 여러 시스템에서 발생한다. 그러므로 분산 비즈니스 트랜잭션은 나머지 다른 모든 트랜잭션을 부분 집합으로 가질 수 있는 가장 일반적인 형태의 트랜잭션이다. 분산 비즈니스 트랜잭션은 비즈니스가 복잡한 만큼 다양한 형태로 등장할 수 있으므로 정형화된 아키텍처 패턴을 갖지 않는다. 분산 비즈니스 트랜잭션은 애플리케이션 수준에서 ACID를 보장하는 방안을 제공해야 한다.

트랜잭션들의 특성을 보면 다음과 같다.

구분
시스템 트랜잭션
비즈니스 트랜잭션
분산 시스템 트랜잭션
분산 비즈니스 트랜잭션
처리 시간
단시간(수초 이내) 장시간 단시간(수초 이내) 장시간
참여자
단일 로컬 시스템 단일 로컬 시스템 복수 분산 시스템 분산 복수 시스템
원자성
알고리즘 보장 애플리케이션 보장 프로토콜 보장 애플리케이션 보장
영속성
알고리즘 보장 애플리케이션 보장 프로토콜 보장 애플리케이션 보장
고립성
알고리즘 보장 애플리케이션 보장 프로토콜 보장 애플리케이션 보장
무결성
알고리즘 보장 애플리케이션 보장 프로토콜 보장 애플리케이션 보장
장애 조치
리두 로그,
롤백 세그먼트 등
오류 해결 전략 수작업 복구 오류 해결 전략
장애 지점
적음
(CPU, 메모리, 디스크)
적음
(CPU, 메모리, 디스크)
많음
(자원, 네트워크, 관리)
많음
(자원, 네트워크, 관리)

일반적으로 분산 환경은 로컬 환경에 비해 여러 가지로 불리하다. (시스템 커밋이 발생하는 위치를 기준으로 분산 시스템 트랜잭션과 분산 비즈니스 트랜잭션을 분산 트랜잭션이라 하고, 시스템 트랜잭션과 비즈니스 트랜잭션을 로컬 트랜잭션이라 하자.) 분산 트랜잭션은 참여 시스템들이 분산됨으로, 경우에 따라 처리 시간이 길어짐으로, 네트워크가 개입함으로, 시스템 관리 주체가 달라짐으로 약점들을 갖게 된다. 만약 분산 트랜잭션에 참여하는 시스템들이 고성능이고, 네트워크도 시스템 버스만큼 성능과 안정성이 보장되고, 업무 흐름이 처리 중에는 절대로 다운이나 정비로 참여 시스템들을 멈추게 하지 않는다면 분산 트랜잭션은 로컬 트랜잭션과 동일한 트랜잭션이 될 것이다. 그러나 현실의 분산 환경은 절대로 로컬 환경과 같을 수 없다. 그러므로 분산 트랜잭션과 로컬 트랜잭션을 동일하게 가정을 하는 것은 현실에 맞지 않는다.

로컬 트랜잭션과 분산 트랜잭션은 로컬 프로시저 호출(Local Procedure Call)과 원격 프로시저 호출(Remote Procedure Call)[EIP]과 많이 닮아 있다. 로컬 프로시저 호출과 원격 프로시저 호출도 단일 프로그램, 단일 시스템, 단일 메모리 공간에서 클라이언트와 서버 프로그램, 네트워크로 연결된 두 시스템으로 실질적으로 다른 인프라 구조를 갖는다. 마찬가지로 로컬 트랜잭션과 분산 트랜잭션도 실질적으로 다른 인프라 구조를 갖는다. 그러므로 로컬 프로시저 호출에서 원격 프로시저 호출로 넘어 갈 때 고려해야 할 수많은 문제들이 로컬 트랜잭션에서 분산 트랜잭션으로 넘어 갈 때도 그대로 등장한다.

기업에서 네트워크를 통해 분산된 애플리케이션이나 시스템(자원)을 결합해 하나의 비즈니스 흐름을 완성하는 애플리케이션 통합은 비일비재하다. 그런데 이 경우 비즈니스 흐름에 참여하는 각 요소들이 원격지에 존재한다는 사실이 쉽게 간과된다. 그리고 로컬 프로그래밍에 익숙한 개발자들은 원격지의 서비스나 자원도 가능하면 로컬 메소드나 자원을 활용하는 것처럼 사용하고 싶어하는 경향이 강하다. 이런 요구로 등장하는 것들이 동기 방식의 원격 프로시저 호출이나 2단계 커밋 같은 기술들이다. 이들은 개발자에게 원격지 서비스나 자원을 마치 같은 시스템의 자원처럼 활용하게 해준다. 이 기술은 언뜻 보기에는 사용하기 편리한 기술처럼 보이지만 비즈니스의 안정성을 위해 상당한 기술적 복잡성과 성능을 희생하게 된다. 즉 참여 시스템(자원)들은 서로 단단히 결합(tight coupling)된다. 그러나 경우에 따라 제품의 도입을 주도하는 이해관계로 인해 이런 사실은 잘 알려지지도 알리려고도 하지 않는다.

분산 시스템의 주요 취약점들에 대해 좀더 살펴보자. 분산 시스템들의 통신은 로컬 시스템 버스 통신이 아닌 이더넷과 같은 저수준 프로토콜을 이용한 원격 통신이다. 그런데 네트워크는 본질적으로 불안정하다. 물론 최근 네트워크 장비들은 일년에 몇 시간도 안 되는 다운 타임을 보장하기도 한다. 그러나 장애 확률은 얼마나 많은 시스템들이 업무 흐름에 참여했느냐에 따라 개별 시스템들의 장애 확률의 합으로 나타난다. 즉 참여 시스템이나 장비들이 많을수록 확률은 높아진다. 예를 들어 업무 흐름에 포함된 10개의 시스템이나 장비가 2시간/년의 장애 시간을 보장하더라도 전체적으로 보면 20시간/년의 장애 시간을 갖는 것이다. 시스템은 게다가 각 시스템의 관리 담당자도 별도로 운영될 가능성이 높다. 이 경우 각 시스템의 담당자는 필요에 따라 시스템을 중지시키거나 다른 작업을 실행시킴으로 비즈니스를 보장하기 위한 가용성이나 성능을 보장받지 못하게 될 수도 있다. 더 중요한 점은 장애 발생 시 장애를 찾거나 복구하는데 점검하고 대처해야 할 지점의 증가로 장애 회복 시간이 가늠 없이 길어 질 수 있다는 점이다. 이런 문제에 대해 시스템을 오래 운영해 본 운영자들은 대부분 동의할 수 있을 것이다. 그러므로 분산 시스템은 분명이 단일 시스템과 다르게 접근해야 한다.

분산 환경이 갖는 취약성으로 인해 분산 시스템 트랜잭션은 해결하기 곤란한 문제를 야기할 수 있다. 즉 처리 시간 동안 참여 시스템이나 자원 중 하나라도 문제가 발생하면 거래는 실패한다. 그러므로 분산 시스템 트랜잭션 기술인 2단계 커밋도 완전한 기술이 아니다.[DTT] 2단계 커밋 기술은 단일 자원만 사용하는 단일 시스템 트랜잭션과 본질적으로 다르다. 2단계 커밋이 안전하다고 생각하는 것은 잘못된 생각이다.

일반적으로 2단계 커밋은 다음과 같은 문제들을 갖는다.

    1) 투표와 커밋 단계 사이 장애로 2단계 커밋에 참여한 전체 시스템의 무결성 훼손이 발생할 수 있으며, 이 경우 무결성을 복원하기 위한 복잡한 수작업이 필요하다.

    2) 2단계 커밋을 지원하지 않는 기존 시스템들과 협업하기 어렵다.

    3) 하나 이상의 요청과 장시간이 걸리는 애플리케이션 통합에 적합하지 않다.

첫째, 통신 장애는 분산 기술에 내재된 속성이다. 네트워크는 신호는 본질적으로 불안정하다. 2단계 커밋의 조정자가 아무리 잘 트랜잭션을 관리하더라도 투표와 커밋 단계 사이에서 네트워크나 참여 자원의 장애가 발생하는 경우 무결성은 파괴된다. 2단계 커밋에서 장애가 발생하면 심각한 문제가 뒤따른다. 일단 2단계 커밋의 오류가 발생하면 애플리케이션은 참여 단계를 프로그램적으로 특정하기 어렵다. 그러므로 2단계 커밋에 참여한 모든 시스템의 상태를 확인해야 하고 일부 시스템의 완료된 커밋과 일부 시스템의 미완료된 커밋을 어떻게 할 것인가에 대해 비즈니스 담당자와 데이터베이스 관리자들과 애플리케이션 담당자들이 모두 합의하는 결론을 도출해야 한다. 참여 자원의 완료된 커밋 조합에 따라 대응 결론이 달라질 수도 있다. 그러므로 2단계 커밋을 마치 단일 시스템 트랜잭션처럼 간단히 생각하고 애플리케이션에 포함시키는 것은 해결할 수 없는 문제를 안고 있는 애플리케이션을 운영하는 것이다. 2단계 커밋을 사용하더라도, 분산 시스템들의 무결성은 정교한 비즈니스 시나리오를 통해 애플리케이션이 해결해야 한다. 둘째, 기업 내 2단계 커밋을 지원하는 시스템이 실제로 많지 않을 수 있다. 그러므로 2단계 커밋을 지원하지 않는 애플리케이션과 협업할 때는 2단계 커밋을 결국 사용하지 못하게 된다. 그러므로 이 경우도 역시 분산 무결성을 정교한 비즈니스 시나리오 분석을 통해 애플리케이션이 해결해야 한다. 셋째, 일반적으로 비즈니스 프로세스는 일련의 시스템 트랜잭션을 포함하는 장시간 업무 흐름을 통해 완성된다. 장시간 처리 비즈니스 프로세스도 2단계 커밋을 적용하면 자원의 무결성과 고립성 문제는 어느 정도 해결할 수 있으나 참여 애플리케이션들 간 성능이나 부하 차이로 인해 전체 성능에 지나친 병목을 발생시킬 수 있다. 이런 경우 2단계 커밋이 제공하는 장점보다 성능 병목으로 인한 단점이 더 커지게 된다. 그러므로 애플리케이션들이 협업하는 애플리케이션 통합에는 2단계 커밋을 적합하지 않을 수 있다.

그러므로 2단계 커밋을 사용해도 좋은 분야가 있고 그렇지 않은 분야가 있다는 것을 반드시 명심해야 한다. 사용의 판단 기준은 애플리케이션의 특성을 먼저 고려한 후 사용 여부를 판단해야 한다. 예를 들어 참여 시스템의 수, 참여 시스템의 성능, 참여 시스템의 2단계 커밋 지원 여부, 비즈니스 프로세스 완성에 필요한 시간, 참여 시스템의 안정성 등등을 고려해야 한다. 일반적으로 2단계 커밋은 비즈니스 애플리케이션 로직이 최소화된 성능과 안정성이 보장된 데이터베이스 시스템들을 하나의 트랜잭션으로 엮는 경우 잘 동작한다.

결국 2단계 커밋을 사용하더라도 비즈니스 트랜잭션의 단계 별 상태를 정교하게 분석해야 하고 일부 성공, 일부 실패에 대한 대응책도 준비해야 한다. 그런데 우리가 2단계 커밋을 사용하는 이유는 이런 무결성 문제를 회피하기 위해서인데 결국 일련의 시스템 트랜잭션들에 대한 성공, 실패 대응책을 고려해야 한다면 2단계 커밋을 사용할 이유가 있겠는가? 혹은 필자가 자주 발생하지도 않는 2단계 커밋의 오류를 너무 확대 해석한 것일 수도 있고, 오류가 발생한다면 무시해 버리면 될 것이 아닌가 반문할 수도 있을 것이다. 그러나 만약에 2단계 커밋이 단순히 극히 짧은 순간의 무결성 보장을 위해 사용된다면 굳이 2단계 커밋을 사용할 이유는 또 무엇인가? 그리고 무결성이 훼손되더라도 무시해 버린다면 더더군다나 2단계 커밋을 사용할 이유는 무엇인가?

2단계 커밋을 사용하지 않더라도 비즈니스 트랜잭션의 무결성과 고립성은 낙관적 오프라인 잠금 패턴(Optimistic Offline Lock pattern)[EAA]이나 비관적 오프라인 잠금 패턴(Pessimistic Offline Lock pattern)[EAA], 성긴 잠금 패턴(Coarse-Grained Lock pattern)[EAA], 암시적 잠금 패턴(Implicit Lock pattern)[EAA] 등을 통해 별도로 보장할 수 있다.

애플리케이션을 통합해야 경우 충분히 고려되지 않은 2단계 커밋을 바로 적용하기 보다 비즈니스 시나리오를 좀더 신중하게 분석해야 한다. 또한 각 참여 시스템의 트랜잭션 단계마다 오류 대응책도 마련해야 한다. 그럼 트랜잭션 오류는 어떻게 해결해야 할까? 애플리케이션 개발자들이 ACID를 보장하기 위해 동기 방식이면서 미들웨어에 의존하는 시스템 트랜잭션이나 2단계 커밋의 관점이 아닌, 비동기 메시징과 애플리케이션 상태 관점에서 비즈니스 트랜잭션 오류에 대한 대응책을 생각해 보자. 비동기 메시징에서는 본질적으로 전체 비즈니스 흐름에 트랜잭션을 적용하기가 사실상 불가능하므로 발생한 오류는 비즈니스적으로 해결한다. 비즈니스 트랜잭션에 발생한 오류를 해결하는 전략들은 다음과 같다.

    1) 취소 전략: 비즈니스 트랜잭션의 오류 해결책으로 가장 간단한 전략이다. 즉 지금까지 진행된 개별 트랜잭션들을 모두 취소시킨다. 어떻게 보면 이 전략은 2단계 커밋의 롤백 전략과 유사하다. 그리고 별로 좋은 전략 같아 보이지도 않는다. 그러나 현실 세계에서는 그런대로 쓸만하다. 취소함으로 발생하는 손실이 거래를 복구함으로 얻는 이익보다 큰 경우 특히 유효하다. 애플리케이션 개발 시 실행(삽입, 갱신) 기능과 취소(삭제, 갱신)기능을 함께 고려하고, 트랜잭션 조정자는 오류를 감지한 후 각 참여 자원의 취소 기능을 요청한다. 2단계 커밋의 롤백과 다른 점은 하나 이상의 요청이 트랜잭션 중간에 개입될 수 있고 긴 시간의 트랜잭션에서 발생한 오류를 해결하는 전략으로, 이런 문제는 2단계 커밋의 롤백으로는 해결되지 않는다.

    2) 재시도 전략: 재시도를 통해 비즈니스 트랜잭션이 성공할 수 있는 업무인 경우 다시 동일한 비즈니스 기능을 시작한다. 예를 들어 외부 자원이 일시적으로 중지된 경우 재시도를 통해 극복될 수 있다. 업무 규칙을 위반한 비즈니스 기능의 요청인 경우 이 전략을 사용하면 안된다. 참여 시스템이 멱등 수신자(Idempotent Receiver)[EIP]가 된다면, 이 전략은 좀더 효과를 발휘한다. 멱등 수신자란 동일한 요구에 대해 수신자의 상태가 변하지 않는 수신자를 말한다. 즉 재시도 전략을 비즈니스 트랜잭션 오류의 전략으로 선택한 경우 각 참여 자원을 멱등 수신자로 만들어야 한다. 멱등 수신자 패턴에 대한 자세한 설명은 [EIP]나 [SDP]를 참조한다. 재시도에는 재시도 횟수를 지정할 수 있다. 재시도 횟수를 넘어선 오류인 경우 다른 해결 전략으로 오류 해결 방법을 변경해야 한다.

    3) 보상 전략: 이미 처리된 비즈니스 오류를 별도의 비즈니스 기능으로 보상하는 전략이다. 예를 들어 은행이 고객의 이체 처리 중 출금은 성공했으나 입금에 실패한 경우 출금 액만큼 은행이 고객에게 입금해주는 전략이다. 이를 위해서는 트랜잭션 오류 상태를 해결 상태로 전이할 수 있는 기능(메소드)을 참여 애플리케이션에 포함해야 한다. 비즈니스 트랜잭션 오류가 발생하면 비즈니스 트랜잭션 조정자는 보상 비즈니스 기능을 참여 시스템에 요청해 비즈니스 트랜잭션의 오류 상태를 정상 상태로 전이시킨다.

비즈니스 트랜잭션을 설계할 때 위 전략들 고려해서 오류 대응책을 마련한다면, 트랜잭션 조정자 애플리케이션이나 트랜잭션 참여 서비스나 자원 애플리케이션들의 역할도 분명해 질 것이고, 2단계 커밋의 미해결 상태의 위험성도 자동적으로 배제할 수 있을 것이다.

분산 애플리케이션 통합의 경우 비즈니스 트랜잭션의 성격을 정확히 분석해 취소, 재시도, 보상 전략 중 해당 트랜잭션에 적합한 전략을 결정해야 한다. 그리고 나서 비즈니스 트랜잭션 조정자, 참여 시스템들에 필요 기능을 구현해야 한다. 이 과정은 생각보다 간단한 과정은 아니다. 비즈니스 트랜잭션의 오류 해결에 대한 명확한 요구를 현업이 정립해야 하고 이에 따라 아키텍트나 개발자들은 트랜잭션 상태 관리, 오류 분석, 전략 실행, 오류 보고, 재시도 제어 등 필요한 요소들을 설계 및 구현해야 한다. 이 때 "기업 애플리케이션 아키텍처 패턴"[EAA]이나 "기업 통합 패턴"[EIP]이나 서비스 디자인 패턴[SDP] 등 기업 애플리케이션을 위한 패턴들을 적절히 활용하고, 필요한 경우 메시징 시스템이나 웹 서비스 시스템을 이용하고, 적절한 프레임워크를 이용할 수도 있을 것이다. 그러므로 이 접근은 시간이나 자원을 좀더 소비한다. 반면 분산 시스템 트랜잭션(2단계 커밋)의 무책임한 사용은 설계 구현을 간단하게 해 시간이나 자원을 절약하게 할지는 몰라도 서비스 중 발생하는 분산 비즈니스 트랜잭션의 오류를 완전히 해결하지 못한다.

비즈니스 트랜잭션 중 무결성을 훼손하는 일부 트랜잭션 오류가 발생 했을 때, 위 세 전략을 언제 적용할지를 생각해 보자. 아마도 애플리케이션 개발자는 가능하면 즉시 해결되도록 전략을 가동시키려 할 것이다. 그러나 시스템이나 네트워크 장애가 길어지는 경우 해결 요청은 어떻게 유지될 수 있을 것인가? 장애가 한 시간가량 지속됐고 무결성이 훼손된 분산 비즈니스 트랜잭션이 10건이라면? 이 오류들을 해결하기 위해 트랜잭션 조정자가 동기 원격 프로시저 호출을 지속적으로 호출해도 참여 시스템이나 네트워크가 복구되지 않는 한 해결되지 못할 것이다. 그러므로 이 문제를 해결하는 가장 좋은 방법은 비동기 방식을 사용하는 것이다. 즉 오류 해결 요청 메시지를 메시징 시스템 큐에 추가하는 것이다. 그러면 참여 시스템은 메시지 큐에서 요청 메시지를 수신해 필요한 해결 기능을 수행할 수 있을 것이다. 그런데 비즈니스 트랜잭션의 오류 해결 기능이나 정상적인 비즈니스 트랜잭션의 기능 수행이나 애플리케이션에서 본다면 별로 다르지 않은 기능이다. 그러므로 정상 비즈니스 트랜잭션은 동기 원격 프로시저 호출 방식으로 오류 해결 비즈니스는 비동기 메시징으로 개발하는 것은 같은 기능을 두 번 전혀 다른 패러다임을 사용해 구현하는 모양새가 된다. 그러므로 둘 중 한 가지 방법을 선택해야 한다. 필자는 비동기 메시징 방식의 애플리케이션 통합을 권장한다.

"기업 통합 패턴(Enterprise Integration Patterns)"의 저자 그레거 호프는 "스타벅스는 2단계 커밋을 사용하지 않는다."[Hohpe]란 제목의 글을 블로그에 게시한 적이 있다. 필자가 언급한 분산 비즈니스 트랜잭션 오류 해결 전략도 그의 글에서 인용한 것이다. 그러므로 기업 애플리케이션 통합에 분산 비즈니스 트랜잭션이 포함될 때, 2단계 커밋을 사용하려는 경우 2단계 커밋이 꼭 필요한지, 2단계 커밋을 사용할 수 있는지, 2단계 커밋의 오류 시 해결할 대응 전략이 있는지, 애플리케이션 수준에서 트랜잭션을 해결할 수 있는지, 트랜잭션 오류에 대한 해결 전략은 완비됐는지 등을 충분히 고려해야 할 것이다.


참고 자료


2014년 4월 8일 화요일

메시지 디스패처

제14회 한국자바개발자 컨퍼런스에서 필자가 발표한 "100줄로 완성하는 웹 서비스 플랫폼"은 원래 기업 통합 패턴(Enterprise Integration Patterns)의 메시지 디스패처 패턴(Message Dispatcher Pattern)을 설명하기 위한 프로젝트로 블로그 게시를 준비 중이다가 컨퍼런스에서 먼저 발표한 주제였다. 발표 당시 사실 전하고 싶었지만 제한된 시간으로 제대로 전하지 못했던 메시지 디스패처 패턴에 대한 이야기를 이 글을 통해 전하고자 한다.

기업 통합 패턴(Enterprise Integration Patterns)은 메시지 디스패처 패턴(Message Dispatcher Pattern)을 설명한다. 이 패턴은 다음과 같은 상황을 해결한다.


하나의 채널 위에서 메시지를 처리하는 복수의 소비자들을 조종하려면 어떻게 해야 할까?



Enterprise Integration Patterns, Gregor Hohpe, Bobby Woolf; Addison-Wesley 2004, 509쪽 그림 인용

메시지 디스패처는 경쟁 소비자 패턴(Competing Consumer Pattern)과 비슷하지만, 각 경쟁 소비자가 갖는 대등한 소비 패턴보다 정교하게 소비자들을 조종할 수 있는 패턴이다. 일반적으로 경쟁 소비자는 독립된 프로세스로 동작하는 반면 메시지 디스패처의 소비자는 스레드로 동작한다.

일반적으로 디스패처는 익숙한 용어이다. 가장 익숙하게 사용되는 곳은 Web MVC 프레임워크이다. 그러나 디스패처는 Web MVC보다 하부 구조인 서블릿 컨테이너의 매핑 정의에서부터 사용된다. 즉 서블릿 컨테이너의 배치 서술자(deployment descriptor)인 web.xml 파일에 <servlet-mapping> 태그의 URL 패턴과 서블릿을 지정하는 부분이 바로 서블릿 컨테이너에서 요청 URL과 서블릿을 연결하는 디스패치 매핑의 정의다. Spring MVC는 이 하부 디스패처 매핑에 다시 MVC 패턴을 위한 Spring의 자체 DispatcherServlet 클래스를 추가하여 서블릿 컨테이너가 수신한 웹 요청을 Spring 서비스(컨트롤러)로 디스패치한다.

웹에서 사용하는 디스패처는 웹의 요청과 응답을 중점으로 처리한다. 웹 디스패처는 수신한 요청으로부터 처리할 서비스를 탐색하고 탐색된 서비스로 요청을 전달하고 실행하여 실행 결과를 응답으로 반환하는 일련의 실행 작업을 처리한다. 반면 기업 통합 패턴의 메시지 디스패처는 메시지 중심이다. 기업 통합 패턴의 메시지 디스패처는 요청된 메시지를 실행자(performer)에게 디스패치한다. 메시지 디스패치 패턴에서 메시지 디스패처가 동기 방식으로 사용되는 경우 웹 디스패처처럼 메시지 디스패처의 요청 채널과 응답 채널은 동일하고, 비동기 방식으로 사용되는 경우 요청 채널과 응답 채널은 분리된다. 그러므로 메시지 디스패처를 웹 디스패처보다 더 포괄적인 패턴이라고 볼 수 있다. 즉 웹 디스패처도 메시지 디스패처의 일종으로 볼 수 있다.

웹 디스패처는 이벤트 기반 소비자로 일반적으로 POSA2의 반응자 패턴(Reactor Pattern)으로 구현된다. 반면 메시지 디스패처는 능동적인 폴링 소비자 패턴(Polling Consumer Pattern)이거나 수동적인 이벤트 기반 소비자 패턴(Event-Driven Consumer Pattern)도 될 수 있다. 반응자 패턴을 사용하는 웹 디스패처는 클라이언트의 요청에 대해 수동적으로 반응한다. 그러므로 클라이언트의 요청이 급증하는 경우 웹 디스패처는 병목이 발생할 수 있고, 웹 서비스가 장애인 경우 클라이언트는 웹 요청을 전달하지 못할 수가 있다. 이 문제를 해결하려면 별도의 부하 관리 로직을 웹 디스패처에 추가하여 부하가 급증하는 상황에 대처할 수 있다. 그러나 부하가 급증하는 상황을 우회하는 것은 그렇게 간단한 문제가 아니다. 웹 서비스에 장애가 발생하지 않게 하려면 웹 서비스의 안정성에 많은 노력을 기울여야 한다. 반면 메시지 디스패처는 능동적인 폴링 소비자가 될 수 있으므로, 자신의 상황에 맞추어 메시지의 처리량을 조정할 수도 있게 된다. 요청 메시지의 부하가 급증하거나 서비스를 중지하더라도 처리를 대기하는 메시지는 채널 즉 메시징 시스템의 큐에 일정시간 보관되기 때문이다. 즉 메시지 디스패처 패턴은 웹 디스패처 패턴보다 포괄적이기 때문에, 웹 디스패처 패턴의 부하 관리 방법에서는 고려되지 않는 비동기 시스템을 이용한 흐름 제어를 포함할 수 있다.

B2C를 위한 웹 서비스는 브라우저 화면 렌더링 위주의 컴퓨팅 시간이 길지 않은 단순 업무이고, 고성능 서버와 부하 분산기(Load Balancer)와 요청 부하의 예측을 통해 웹 서비스의 병목의 문제를 대비한다. 그리고 부하가 급증하는 경우나 애플리케이션에 장애가 발생한 경우, 네트워크 장비를 이용하여 웹 요청을 단순히 우회시키기도 한다. 그러나 B2B 웹 서비스에서는 위에 언급한 부하 급증이나 애플리케이션 장애 상황에서도 서비스를 중단할 수 없는 경우가 있다. 이런 장애 상황에서도 요청 기업은 장애 상태에 대한 구체적인 응답을 요구할 수도 있고, 요청의 수신도 요구할 수도 있다. 즉 기업 간 서비스는 일반 고객 서비스보다 거래의 중요도가 훨씬 더 높을 수 있기 때문이다. 이런 요구를 해결하기 위해서는 기업 통합 패턴의 보장 전송 패턴(Guaranteed Delivery Pattern)이 필요하다.

디스패처를 구현하는 일은 간단하지 않다. 디스패처는 독립적으로 동작하는 것이 아니라 요청 수신, 수행자 탐색, 할당, 전달, 처리, 응답 발신이 모두 포함돼야 제대로 동작할 수 있는 패턴이기 때문이다. 디스패처를 패턴으로 해석하는 방식도 제 각각이다. J2EE의 Dispatcher-View 패턴, POSA1의 Client-Dispatcher-Server 패턴, 기업 통합 패턴의 Message Dispatcher 패턴 등 Dispatcher를 포함하는 패턴은 다양하게 존재한다. 이 패턴들은 각각 강조하는 점과 쓰임새가 다르다. 그 중 "100줄로 완성하는 웹 서비스 플랫폼"에서는 메시지 디스패처의 관점의 디스패처를 구현했다.

JCO에서 발표한 “100줄로 완성하는 웹 서비스 플랫폼”은 발표 자료, 동영상, 프로그램 소스가 모두 공개되어 있으므로 통합 프레임워크가 웹 서비스를 구현하기 위해 어떻게 사용되었고 기업 통합 패턴의 메시지 디스패처가 어떻게 구현되었는지 해당 자료들을 참조하면 좀더 잘 이해할 수 있을 것이다.


참고 사이트


2014년 4월 4일 금요일

실시간 트위터 이미지 사이트 개발기 2


시스템 구성

실시간 트위터 이미지 사이트의 웹 서버는 미 동부에 위치한 레드햇 오픈시프트 클라우드에서 운영된다. 그러나 이 서비스에 참여하는 메시징 시스템은 대한민국에 위치한다. 즉 실시간 트위터 이미지 사이트는 범세계적인 분산 시스템이다. 이 실시간 트위터 이미지 사이트의 시스템 구성은 다음과 같다.

위 구성도는 트위터 이미지 사이트의 서버 배치와 실행 순서를 설명한다. 미국 동부에 위치한 오픈시프트에서 운영되는 웹 서버는 트위터 서버로부터 실시간으로 고양이 이미지를 포함한 트윗을 추출하고 메시지로 변환하여, 이 트윗 메시지를 대한민국에 위치한 ActiveMQ 서버의 토픽으로 실시간 전송한다. 웹 브라우저는 미국 동부에 위치한 웹 서버의 트위터 이미지 웹 페이지를 조회하면서 동시에 대한민국에 위치한 ActiveMQ 서버에 웹소켓으로 접속하여 ActiveMQ의 토픽을 구독한다. ActiveMQ 서버에서 트윗 메시지는 실시간으로 토픽에 게시 중이므로 토픽을 구독하는 웹 브라우저는 ActiveMQ 서버의 토픽으로부터 트윗 메시지를 실시간으로 수신한다. 이렇게 실시간 트위터 이미지 사이트에서 “고양이를 보여줘” 웹 페이지에 접근한 웹 브라우저는 두 대륙에 걸쳐 분산되어 있는 서버들을 이용한다.

실시간 트위터 이미지 사이트의 아키텍처가 일반적인 웹 서비스의 아키텍처와는 다른 점은 분산 아키텍처를 위해 ActiveMQ라는 메시징 시스템과 웹소켓 기술을 추가로 사용했다는 점이다.

이 글을 읽는 독자는 왜 일반적인 웹 서비스 아키텍처를 사용하지 않고, 메시징 기반의 아키텍처를 추가로 이용한 것일까? 라는 의문을 가질 수 있을 것이다. 일반적으로 웹 서비스는 요구-응답 방식에 적합한 아키텍처이다. 실시간 트위터 이미지 사이트처럼 지속적으로 데이터 스트림을 처리해야 하는 아키텍처 모델로는 적합하지 않다. 적합하지 않다라는 뜻은 웹 서비스의 요구-응답 통신은 동기 통신 기술로 지속적인 메시지 흐름을 갖는 비동기 통신을 구현하기 위해서는 기술적인 어려움과 복잡성을 부가해야 한다라는 말이다. 또한 요구-응답 방식의 서비스로는 게시-구독 방식의 서비스를 구현하는 것이 쉽지 않다. 더 나아가 메시지의 흐름 제어를 처리하는 것도 쉽지 않다.

반면 메시징 시스템을 이용하는 경우, 참여 시스템들은 느슨한 결합 아키텍처(loosely coupled architecture) 구조를 갖게 된다. 메시징 시스템을 통해 비즈니스 로직들이 서로 분리되어 시스템들 사이 상호 의존성이 감소한다는 말이다. 실시간 트위터 이미지 사이트는 ActiveMQ 메시징 시스템을 이용하여 웹 브라우저와 웹 서버 사이 단단하게 결합될 수 있었던 트위터 비즈니스 로직을 독립적으로 동작 가능하게 분리했다. 그 결과 실시간 트위터 이미지 사이트는 웹 서버의 중단이나 이전 없이 비즈니스 로직을 다른 서버로 이전하거나 심지어 다른 대륙에 위치시킬 수도 있게 됐다. 필자는 실시간 트위터 이미지 사이트를 디버깅하는 과정에서 필자의 PC로 비즈니스 로직을 이동시켜 디버깅을 진행하기도 했다. 디버깅을 위해 비즈니스 로직을 이동시킨 가장 큰 이유는 PAAS 환경이 디버깅이 용이하지 않은 점이 있었기 때문이었다. 단단히 결합된 아키텍처(tightly coupled architecture)를 가진 웹 서비스 방식이었다면, 디버깅을 위해 서비스를 옮기는 경우, 좀더 많은 설정이나 프로그램의 변경이 수반됐을 것이다. 또한 메시징 시스템을 이용하면 실시간 데이터의 비동기적 처리가 가능하다. 즉 데이터 생산자와 데이터 소비자가 메시징 시스템을 매개로 분리됨으로 메시지 수신과 발신이 독립적으로 발생할 수 있게 된다. 그 결과 발신자는 수신자의 대기 없이도 지속적으로 생성한 메시지를 메시징 시스템으로 전송할 수 있고, 수신자는 발신자의 메시지 발신 속도와 무관하게 자신의 처리 속도에 따라 메시징 시스템으로부터 비동기적으로 메시지를 수신하고 처리할 수게 된다. 메시징 시스템을 이용하면 게시-구독 서비스도 구현하기가 쉽다. 가장 대표적인 게시-구독 패턴은 그룹 채팅이다. 그리고 이전 글에서도 언급했듯이 실시간 복수 소비자들이 주가 변화를 지속적으로 수신하는 것과 같은 비즈니스를 쉽게 구현할 수 있게 해 준다. 그리고 ActiveMQ 메시징 시스템의 경우 새롭게 등장한 웹소켓과의 결합도 쉽게 해준다. 실시간 트위터 이미지 사이트에 접속한 웹 브라우저는 웹소켓의 상위 프로토콜로 메시지 프로토콜인 STOMP 프로토콜을 이용하여 대한민국에 위치한 ActiveMQ 메시징 시스템에 직접 접속한다. 웹 브라우저는 이 접속 채널을 통해 비동기적으로 트윗 메시지를 ActiveMQ 메시징 시스템으로부터 지속적으로 수신하여 웹 페이지에 이미지를 표시한다.

실시간 트위터 이미지 사이트는 어떤 패턴의 비즈니스로 발전할 수 있을 지를 생각해 보자. 첫째, 실시간 트위터 이미지 사이트는 트위터에서 실시간으로 트윗을 추출 방법을 보여 준다. 그러므로 이 사이트는 트위터에서 발생하는 유행들을 실시간으로 분석하거나 추출, 저장하는 용도로 쉽게 변경될 수 있다. 필자는 개발 과정에서 사용자가 입력한 검색어를 이용하여 실시간으로 트위터에서 이미지들의 추출하여 표시하는 웹 페이지도 개발했다. 그러나 이 웹 페이지는 사용자들의 과도한 검색으로 트위터의 채널 할당량 정책을 초과할 수 있어 공개하지는 않았다. 검색어를 이용해 트윗 결과를 실시간으로 획득할 수 있는 기능을 이용하면 트위터로부터 필요한 유행 정보를 좀더 쉽게 획득할 수 있을 것이다. 둘째, 실시간 트위터 이미지 사이트는 ActiveMQ 메시징 시스템이 대륙 간의 메시지 통신에도 잘 사용될 수 있다는 것을 보여준다. 그러므로 여러 지역에 분산된 본점과 지점들 사이 메시지 통신이 필요한 비즈니스 모델에 ActiveMQ 메시징 시스템을 이용할 수 있다. 셋째, 실시간 트위터 이미지 사이트는 웹소켓을 이용하여 웹 페이지에 실시간으로 이미지를 보여줄 수 있다는 것을 보여준다. 그러므로 웹 페이지에 실시간 정보를 표시하는 비즈니스는 웹소켓 기술을 이용할 수 있을 것이다. 예를 들어 웹 페이지에 채팅 메시지 서비스를 제공한다든지, 웹 페이지에 주가를 실시간으로 표시하는 서비스를 제공한다든지, 웹 페이지에 비즈니스 이벤트를 모니터링하는 서비스를 제공한다지 등등 웹 페이지에 실시간으로 데이터 전달이 필요한 어떤 비즈니스에도 응용될 수 있을 것이다.

이 글을 통해 독자들은 실시간 트위터 이미지 사이트가 보기와 달리 범 대륙적인 분산 시스템으로 메시징 시스템과 웹소켓을 이용해 이미지가 포함된 트위터를 실시간으로 추출 및 전송하고 웹 화면에 이미지를 실시간으로 표시하는 시스템이라는 것을 알았을 것이다. 이상으로 실시간 트위터 이미지 웹 사이트의 시스템 구성에 대한 설명을 마친다.



참고 사이트


2014년 3월 28일 금요일

실시간 트위터 이미지 사이트 개발기 1


들어가며

우리는 가끔 노래 가사처럼 덜컹거리는 기차를 타고 어디론가 떠나며, 차창 밖에 보이는 풍경을 느긋이 감상하고 싶을 때가 있다. 우리는 가끔 우리가 좋아하는 관심사를 보며 마음을 정화시키기도 한다. 일반적으로 사람들은 관심사를 중심으로 세상의 흐름을 읽는다. 사랑, 행복, 월드컵, 올림픽, 전쟁, 선거 등등 그러나 이런 관심사들을 일일이 찾아 다니는 것도 귀찮은 일이다. 그래서 우리는 TV 앞에 모여든다. 생각하지 않아도 알아서 찾아주고 보여주니 편하다. 그런데 우리가 이렇게 편안하게 우리의 관심사를 둘러보기 위해서는 어디선가, 누군가는 일을 해야 한다. 그것이 자발이던, 지시건, 사람이건, 컴퓨터건, 기계건, 누군가는 편안함을 위해 일해야 한다.

재작년에 런던 올림픽 당시 관람객들은 스스로 영광의 순간을 찍은 사진들을 사회 관계망 서비스를 이용하여 순식간에 인터넷으로 전세계에 확산시켰다. 그 중 트위터는 독보적이었다. 이 영광의 순간들은 트위터로 실시간으로 트윗되었고, 신문, 방송 기사들조차 이 순간을 전달에 트윗을 이용했다. 그러나 당시 트위터로 올라오는 트윗과 리트윗들은 올림픽에 관련된 것뿐만 아니라, 일상 관심사에 대한 것들로 뒤섞여 있었고 올림픽 사진뿐만 아니라 다양한 일상의 사진들도 포함되었다. 올림픽에 관심이 많은 사람일지라도 일일이 트위터에서 올림픽의 순간이 포함된 트윗만을 찾아 보는 것은 쉽지 않았다. 결과적으로 사람들은 올림픽의 영광의 순간을 그 순간이 아닌 이후 매체나 글자가 뒤섞인 트윗을 통해야 확인해야 했다. 그런데 이런 지구적인 이벤트에서 영광의 순간을 가장 빨리 편안하게 보려면 어떻게 해야 할지를 고민한 개발자가 있었다.

이 개발자는 올림픽의 순간을 보기 위한 웹 페이지를 개발했다. 그리고 그는 이 웹 페이지 개발에 Apache Camel 통합 프레임워크를 이용했다. 이 웹 페이지는 실시간으로 올라오는 올림픽의 영광의 순간을 담은 트윗들에서 사진들을 별도로 모아 보여주었다. 이 웹 페이지 프로젝트가 통합 커뮤니티에 알려지면서 커뮤니티는 웹 페이지 개발자에게 찬사를 보냈고 통합의 가치에 자부심을 가지게 되었다.

다음은 당시 이 웹 페이지가 보여주었던 화면이다.

이 웹 페이지는 실시간으로 올라오는 수많은 트윗들 중에서 런던 올림픽과 관련된 트윗을 검색하고 찾아낸 결과 트윗들 중 이미지를 포함한 트윗에서 위 화면처럼 웹 페이지에 썸네일 이미지를 보여준다. 그리고 썸네일을 클릭하면 원래 이미지가 나타난다. 올림픽 사진들은 최소 5초마다 계속 페이지에 추가된다. 놀라운 점은 이 웹 페이지가 중복된 사진을 실시간으로 걸러준다는 것이다. 즉 웹 페이지를 감상하는 사람에게 같은 이미지로 도배된 웹 페이지의 보여 주지 않음으로 영광의 순간들을 하나 하나 의미 있게 한다. 마치 기차를 타며 어딘가를 갈 때 창 밖으로 계속 바뀌는 풍경을 감상하는 것처럼 이 웹 페이지는 지나가는 풍경과 같은 감성을 조회자들에게 전달한다. 그 결과 이 사이트에 접속한 사람들은 올림픽의 순간을 마치 TV처럼 사진으로 편안하게 즐길 수 있었었다.

그런데 기술적으로도 놀라운 점은 이런 멋진 웹 페이지의 핵심 로직의 구현에 단지 수십 줄의 코드가 사용됐다는 점이다. 핵심 로직에 단지 23줄이 사용됐다. 이것은 일반 개발자들뿐만 아니라 통합 개발자들에게도 신선한 반향을 일으켰다. 이 핵심 코드는 다음과 같다.

이 코드는 Apache Camel 프레임워크의 라우팅에 대한 정의이다. 이 핵심 로직을 간단히 설명하면 다음과 같다. 검색어를 이용해 트위터로부터 실시간으로 트윗 결과들을 얻은 후, 이미지가 포함된 트윗을 선별하고, 선별된 트윗의 중복을 배제하고, 이미지를 포함한 정제된 트윗은 5초 동안의 여유가 적용된 수 후 웹소켓에 접속한 페이지들에게 브로드캐스트된다.

위와 같은 핵심 로직을 일반적인 웹 개발 방법을 사용하여 구현한다면 어떤 기술과 얼마의 자원과 시간이 필요할까? 아마도 기술을 깊이 이해하고 있는 개발자나 아키텍트라면 단언하기 쉽지 않을 것이다. 그런데 이런 핵심 기능을 23줄이 가능하게 했다. 통합 커뮤니티에서조차도 이 프로젝트가 통합을 창의적으로 적용한 것에 대해 열광했다. 필자가 볼 때도 이 핵심 로직은 프로그램이기보다 시에 가깝다. 이 프로젝트의 개발자는 "Instant Apache Camel Messaging"이란 책을 쓴 실력자이다.

그런데 올림픽은 끝나면서 올림픽의 순간들은 사람들에게 잊혀져 갔고, 아마존의 공짜 마이크로 인스턴스에서 운영되던 이 사이트도 폐쇄되었다. 아마존은 마이크로 인스턴스를 약 한 달인 750시간까지 사용할 수 있게 해준다. 결국 올림픽이 끝나면서 웹 페이지가 동작되던 마이크로 인스턴스의 생애도 끝나버렸다. 필자처럼 이 웹 페이지 개발자도 공짜를 몹시 밝히는 사람인 것 같다. 그의 블로그를 보면 무려 10달러를 사용했다는 것을 글에 남길 정도로 그도 짠돌이다. 그러나 이런 집착은 오픈 소스를 이용한 개발자들의 미학이다.

필자가 이 프로젝트에 다시 관심을 가진 이유는 얼마 전 레드햇의 오픈시프트 퍼블릭 클라우드 사이트에 가입했기 때문이었다. 현재 레드햇의 오픈시프트 퍼블릭 클라우드는 온라인 가입자들에게 놀라운 정책을 제공하고 있다. 이 놀라운 정책이란 무려 3개의 기어라고 불리는 가상 PAAS 서버를 무료로 사용하게 해 준다는 것이다. 각 기어 당 메모리 512M, 디스크 1G까지 네트워크 트래픽에 대한 언급은 없으므로 무제한(?)을 제공해 줄 것으로 생각된다. 필자가 가지고 있는 라즈베리 파이만한 기계를 가상 환경이지만 3개나 공짜인 제공한다는 것이다. 아마존 웹서비스의 마이크로 인스턴스 정책과 비교해 보라. 정말로 파격적인 정책임을 알 수 있다. 아이러니 한 점은 레드햇의 오픈시프트 클라우드가 아마존 웹서비스 클라우드에서 운영된다는 점이다. 그것도 SUSE Linux로…… 필자는 오픈시프트 기술에 대해 검토하면서 "그래 공짜 서버가 생겼는데 놀리면 안되겠다"라는 생각을 했다. 그러면서 위에 설명한 올림픽의 영광의 순간을 담는 웹 페이지를 필자의 방식대로 새로 만들어 이곳에서 운영하면 좋겠다고 생각하고 만들어 보았다.

다음은 필자가 개선한 새로운 실시간 트위터 이미지 웹 페이지다.

들어가는 글은 이 정도로 마치고 이어지는 글에서는 필자가 실시간 트위터 이미지 웹 페이지를 개발하면서 분석하고, 고민하고, 개선하고, 결정했던 이야기들을 하려고 한다.



참고 사이트


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 기반의 서비스 개발 모델은 불필요한 연동 테스트의 낭비를 제거할 수 있다는 것을 설명하려 했다. 메시지 통신이 포함된 애플리케이션의 개발은 시스템 프로그램의 영역과 비즈니스 영역의 프로그램이 모두 필요한 부분이고 개발 및 테스트가 상당히 까다로운 분야이다. 그러나 좋은 실행 예를 참조한다면 메시지 애플리케이션을 개발하는데 합리적인 기술을 선택하고 불필요한 시행착오를 줄여 개발 생산성과 유지보수 비용을 줄일 수 있을 것이다. 그리고 거창한 솔루션을 도입하는 것이 능사가 아님도 알아야 한다. 필요한 곳에 필요한 전문가가 필요한 기술을 적절히 활용하는 것이 기술 종속을 피하는 길이고 필요 이상의 비용도 절약하는 방법일 것이다.

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


참고 사이트