지난 번 기사에서는 이 애플리케이션에서 자바로 작성한 부분을 테스트하는 것에 관해 살펴보았다. 이번 토니 힐러슨의 Flex로 이용한 엔터프라이즈 애플리케이션 개발 시리즈에서는 서비스 계층 구축을 위해 메이븐(Maven)을 살펴보고, Flex에 들어가고 나오는 관리 데이터(managed data)를 다루는 강력한 LiveCycle Data Service(LCDS) 어셈블러(assembler)를 소개한다.
메이븐(Maven) 설정
다음은 메이븐 설정을 나타낸 것이다:
4.0.0bookieProjectlcds.examples.bookie1.0-SNAPSHOTlcds.examples.bookiebookie-ui1.0-SNAPSHOTwarBookie Example WAR Projectbookiemaven-war-plugin
${basedir}/src/main/bookie.war
...
lcds.examples.bookiebookie-data1.0-SNAPSHOTprovided
...
특별히 새로울 게 없다. 먼저 우리는 메이븐에 이 프로젝트의 부모 프로젝트(parent project)가 있음을 알려준 다음, 그 프로젝트를 확인해야 한다. 다음으로는 war 파일을 지정하여 생성할 아티팩트의 타입을 메이븐에 설정한다.
한 가지 더 밟아야 할 단계는 WAR 소스 디렉터리의 이름을 기본 webapp가 아닌 bookie.war로 재설정하는 것이다. 내 생각에는 그렇게 하는 것이 더 의미가 있기 때문이다.
이 프로젝트의 의존성에는 bookie-data 프로젝트의 의존성과 우리가 직접 저장소에 집어넣은 몇몇 파일이 포함된다.
서비스
이 프로젝트를 소개할 때 논의했던 유스 케이스를 다시 살펴보자. 먼저 우리는 사용자 유스 케이스를 살펴볼 것이다. 여기에는 lcds.examples.bookie.service.BookSearchService라는 간단한 클래스에 API(다른 클라이언트에 대한 단순 서비스로서 노출할 일련의 메서드를 의미하며, 이 경우 클라이언트는 Flex가 된다)를 정의할 것이다. 여러분도 볼 수 있듯이, 그 클래스는 우리가 이전 연재 기사에서 정의했던 세션 빈을 단순히 호출하는 일련의 메서드로 구성되어 있다. 이러한 메서드 호출을 하나의 클래스로 모으면 다음과 같은 이점을 누릴 수 있다:
이렇게 하면 좋은 서비스 지향 아키텍처(SOA; Service-Oriented Architecture)의 실천 지침을 따른다고 할 수 있는데, 이는 서비스가 특정한 범주의 업무 요건을 지원하는 클래스들을 하나의 서비스로 묶기 때문이다.
모든 빈의 중요 메서드를 찾기 위한 접근 지점이 한 군데로 모이며, 이를 통해 역할 기반 보안(role-based security)과 같은 관심사를 관리하기가 보다 용이해진다.
Flex가 접근하기 위한 하나의 RemoteObject를 서비스로서 정의할 수 있다.
단일 접근 지점(single point of access)이 중요한 것은 이곳에서 우리가 모든 원격 호출에 대해 해야 할 일을 할 수 있기 때문이다. 예제에서 별달리 해야 할 것은 없지만, 그렇게 하는 것이 좋은 실천지침을 따르는 것이다.
이 서비스 API를 이용하면 사용자에 대한 대부분의 유스 케이스를 처리할 수 있으며, 이를 통해 Flex에서 POJO(Plain Old Java Object)에 어떻게 접근하는지를 살펴볼 수 있다. 또한 “어셈블러(assembler)”라고 하는 LCDS 기능을 사용하여 관리 데이터 기능을 이용해 보고, 그것들이 어떻게 개발을 더 편하게 해주는지 살펴볼 것이다.
한 가지 알아둘 점은 이런 식으로 유스 케이스의 구현을 나눈 것은 Flex와 자바간의 통신을 가능하게 하는 다른 방법들을 부각시키기 위함이라는 것이다. 여러분의 애플리케이션에서 필요로 하는 기능에 따라 여러분은 RemoteObject를 고수하고 싶을 수도 있고, 아니면 철저히 어셈블러를 이용하여 DataService를 거쳐 데이터에 접근할 수도 있다.
[표 7]은 사용자 유스 케이스와 그것이 구현된 곳을 나열한 것이다.
유스 케이스
서비스 메서드나 어셈블러
로그인
BookSearchService.findPersonByCardNumber
주제로 검색
BookSearchService.findBooksBySubject
제목으로 검색
BookSearchService.findBooksByTitle
저자로 검색
BookSearchService.findBooksByAuthor
저자 검색
PersonAssembler
주제 검색
SubjectAssembler
제목 찾아보기
BookAssembler
저자 찾아보기
PersonAssembler
주제 찾아보기
SubjectAssembler
[표 7] 사용자 유스 케이스와 해당 유스 케이스가 구현된 위치
어셈블러에서 관리하는 모든 관리자 유스 케이스는 [표 8]에 나열되어 있다.
관리자 유스 케이스
서비스 메서드나 어셈블러
주제 생성, 갱신, 삭제하기
SubjectAssembler
서적 생성, 갱신, 삭제하기
BookAssembler
저자 생성, 갱신, 삭제하기
PersonAssembler
사용자 생성, 갱신, 삭제하기
PersonAssembler
책 대출
BookAssembler
[표 8] 관리자 유스 케이스와 해당 유스 케이스가 구현된 위치
[그림 15]는 UI 프로젝트의 모든 자바 서비스 클래스의 위치를 보여준다.
[그림 15] UI 프로젝트의 자바 서비스 클래스 위치
LCDS를 이용한 데이터 관리
데이터 관리는 LCDS의 가장 멋진 기능 가운데 하나다. Flex의 데이터 서비스 기능을 사용하면 백엔드 서비스를 명시적으로 호출하는 코드를 작성하지 않아도 된다. Flex에서 임의의 중첩된 객체가 될 수 있는 데이터 컬렉션을 관리할 경우, 해당 데이터 컬렉션에 가해지는 어떠한 변경사항도 자동으로 서비스로 전송되기 때문이다. 이것은 관리 데이터 컬렉션을 사용하는 모든 뷰는 오로지 컬렉션에 연결(hook up)하기만 하면 된다는 뜻이다. 일단 데이터 집합이 관리되면, 해야 할 일은 그걸로 끝이다. 우리는 명시적으로 전송할 DTO로 들어가는 프로퍼티를 읽거나, 어느 프로퍼티가 변경될지, 또는 변경사항이 언제 저장될지 지켜볼 필요가 없다. 이렇게 하면 데이터 중심의 애플리케이션 개발에 있어 상당한 시간을 절약할 수 있다.
하지만 잠깐 기다려라. 알아야 할 게 아직 남아있다! 한 클라이언트의 변경사항도 자동으로 서비스로 전송될 수 있지만 서비스로 전송된 다른 클라이언트의 변경사항도 동일한 관리 데이터 소스를 사용하고 있는 다른 클라이언트에게도 자동적으로 전송된다. 한 클라이언트가 데이터 컬렉션에 대한 변경사항을 저장할 때 그와 같은 변경사항은 또 다른 클라이언트에도 적용된다. 이 경우 두 번째 클라이언트의 저장 내역으로 해당 변경사항들을 덮어쓰면 stale 데이터 문제가 일어나는데, 애플리케이션에서 이러한 stale 데이터 문제가 일어나지 않게 하는 것이 점점 더 중요해지고 있으므로 이러한 기능 또한 매우 중요해지고 있다.
물론 여기에도 마법은 없다. LCDS는 첫 번째 클라이언트의 변경사항을 두 번째 클라이언트가 저장할 덮어써서 그 결과로 나타나는 데이터가 올바른 변경사항을 나타내는 건지 자동적으로 알지는 못한다. 그러나 LCDS가 할 수 있는 것은 충돌 관리 프레임워크를 제공하는 것이다. 데이터 동기화 연산의 일부로 Flex는 데이터에 대해 특정한 변경 메시지 충돌이 이미 관리 컬렉션에 병합되었음을 통지받을 수 있다. 이렇게 하면 Flex 개발자들은 사용자에게 충돌이 일어났음을 보여줘서 사용자가 변경작업을 계속하거나, 또는 다른 어떤 행동을 할 것인지를 애플리케이션에서 자동으로 수행할 수 있게 할 수 있다. 이를 통해 애플리케이션은 다수의 사용자가 동일한 데이터를 편집하더라도 잘 동작하며, 오프라인 상태에 있는 애플리케이션도 Adobe AIR와 같은 프레임워크를 사용하여 단절된 세션의 데이터와 동기화할 수 있다.
사용자는 이전에 다운로드한 데이터를 사용한 다음 온라인 상태가 되었을 때 해당 데이터를 서비스와 동기화시킬 수 있다. LCDS를 사용하는 서비스는 동기화 과정을 거쳐 클라이언트를 처리하여 사용자는 자신이 마지막으로 온라인 상태에 있었던 이후에 변경된 데이터가 어떤 것인지를 알 수 있다.
이러한 유형의 애플리케이션을 개발하는 서비스 개발자로서 여러분은 데이터 관리 프로세스의 서로 다른 부분에 연결하여 데이터가 저장되고, 갱신되며, 질의되고, 삭제될 때 어떤 일이 일어나는지를 정의한 구현을 제공할 것이다. 이러한 애플리케이션 생명주기에 해당되는 클래스들을 어셈블러라 한다.
어셈블러(Assembler)
LCDS는 자바에서 사용할 수 있는 데이터 관리 시스템에 대한 API를 제공한다. Flex가 관리하길 원하는 데이터를 획득하는 한 방법은 사용자 정의 어셈블러를 사용하는 것이다. 어셈블러는 새로이 추가된 객체나, 갱신된 객체, 초기 선택사항 등과 같은 관리 데이터를 찾을 지도 모르는 상황에 부합하게 작성할 수 있는 클래스들이다.
어셈블러는 데이터 질의(fill)나 데이터 동기화와 같은 두 가지 데이터 관리 생명주기의 특정 부분에 어울리는 메서드를 포함하는 단순한 자바 클래스가 될 수도 있다. 이 경우 POJO는 어셈블러에 대한 구현을 제공하며 개발자들은 이러한 메서드에 연결(hook up)하는 설정을 통해 LCDS를 제공하게 된다.
우리는 LCDS에서 제공하는 어셈블러 인터페이스를 구현하는 좀 더 정형적인 접근법을 채택하여 DAO의 데이터를 데이터 관리 프레임워크로 제공할 예정이다. [표 9]에는 Assembler 인터페이스를 구현하고 있는, 우리가 확장하려고 하는 클래스인 AbstractAssembler의 메서드가 나열되어 있다.
메서드 서명
사용처
Object getItem(Map identity);
이 메서드는 특정 타입의 객체 하나를 가져올 때 사용한다. 맵 타입의 인자에는 대상 객체를 고유하게 식별하기 위한 키/값 쌍이 포함되어 있어야 한다.
List getItems(List identityList);
이 메서드는 getItem 메서드와 동일하나 객체의 리스트를 반환한다. 인자는 getItem 메서드에서 설명했던 것과 동일한 맵의 리스트여야 한다.
void createItem(Object newItem);
이 메서드는 새로운 항목이 관리 컬렉션에 추가될 때 호출된다. 객체는 LCDS에 의해 타입이 정해질 수 있거나(차후에 알아볼 것이다), 또는 어셈블러 타입의 객체를 생성하는데 사용되는 키와 값의 맵일 수 있다.
void updateItem(Object newVersion, Object previousVersion, List changes);
이 메서드는 클라이언트상의 항목이 변경될 때 호출된다. 객체나 맵이 첫번째 인자이며, 이전 상태의 객체가 두 번째 인자, 변경된 프로퍼티의 리스트가 세번째 인자이다. 세 번째 인자는 알수 없는 변경에 대해서는 null일 수 있다. 영속 객체와 충돌하는 변경사항이 있다고 판단될 경우 어셈블러는 flex.data.DataSynchException을 던질 책임이 있다.
void deleteItem(Object item);
이 메서드는 한 항목이 삭제될 때 호출된다.
int count(List countParameters);
이 메서드는 fill 메서드와 동일한 방식으로 동작하나 매개변수와 일치하는 항목의 수를 반환한다.
void addItemToFill(List fillParameters, int position, Map identity);
이 메서드는 클라이언트에서 관리 컬렉션의 특정 위치에 항목을 삽입할 때 호출된다.컬렉션이 자동으로 갱신되도록 설정되어 있는 경우에는 대개 이 메서드를 사용하지 않도록 한다.
void removeItemFromFill(List fillParameters, int position, Map identity);
이 메서드는 refreshFill과 동일하지만, 항목을 삭제하는데 사용된다.
boolean autoRefreshFill(List fillParameters);
이 메서드는 매개변수에 따라 특정 관리 컬렉션이 자동으로 갱신되어야 할지를 결정할 때 사용할 수 있다
[표 9] ABSTRACTASSEMBLER 클래스의 메서드
[표 9]에 나열된 메서드에 추가하여, 설정을 통해 사용자 정의 fill 메서드를 만들 수도 있으며, 그것들과 함께 객체가 추가되거나, 변경되거나, 삭제될 때마다 사용자 정의 메서드를 호출하여 해당 변경 내역이 사용자 정의 fill 메서드에 추가되어야 할지 결정할 수 있다.
우리가 만든 어셈블러 가운데 가장 일반적인 형태의 SubjectAssembler를 살펴보자. 여기서는 메서드별로 알아보도록 하겠다:
package lcds.examples.bookie.assemblers;
...
public class SubjectAssembler extends AbstractAssembler {
@Override
public Object getItem(Map identity) {
Integer id = (Integer)identity.get("id");
try {
SubjectDAO dao = getSubjectDAO();
return dao.findById(id);
} catch (Exception e) {
throw new RuntimeException("Unable to get Subject", e);
}
}
...
이미 언급했듯이 SubjectAssembler는 Assembler를 구현하는 AbstractAssembler를 확장한다. AbstractAssembler는 앞서 언급했던 한 두개의 메서드에 대한 기본 구현을 제공한다. 다른 메서드는 아무것도 구현되어 있지 않으며, UnsupportedOperationException을 발생시켜(throw) 해당 메서드가 반드시 재정의되어야함을 나타낸다. 한 가지 주의할 점은 AbstractAssembler는 실제로 abstract로 지정되어 있지 않으므로, 별 차이는 없겠지만 AbstractAssembler를 구현 클래스로도 사용할 수 있다는 것이다.
여기서는 특정 객체를 고유하게 식별하는 프로퍼티 맵을 받아들이는 getItem 메서드를 재정의할 것이다. 주제를 고유하게 식별하기 위해서는 ID만 있으면 된다. 여기서는 그와 같은 ID를 DAO로 전달하여 그 결과를 반환하기만 한다. 만약 예외가 발생하면 RuntimeException을 던질 것이다.
...
public Collection getAllSubjects() {
try {
SubjectDAO dao = getSubjectDAO();
return dao.getAll();
} catch (Exception e) {
throw new RuntimeException("Unable to get all Subjects", e);
}
}
public Collection findByName(String name) {
try {
SubjectDAO dao = getSubjectDAO();
return dao.findByName(name);
} catch (Exception e) {
throw new RuntimeException("Unable to get Subjects by name", e);
}
}
...
위의 두 메서드는 재정의하지 않았는데, 그것들은 서로 다른 fill 메서드로 설정될 것이기 때문이다. 잠깐 살펴보면, getAllSubjects 메서드는 이름에서 그 역할을 유추할 수 있으며, 특이사항은 없다. findByName 메서드는 문자열 하나를 인자로 받아들여 DAO의 findByName 메서드로 전달한다.
public List syncSubjects(List changes) {
try {
SubjectDAO dao = getSubjectDAO();
Subject subject;
for (ChangeObject change : changes) {
if (change.isCreate() || change.isUpdate()) {
subject = (Subject)change.getNewVersion();
change.setNewVersion(dao.merge(subject));
} else if (change.isDelete()) {
subject = (Subject)change.getPreviousVersion();
if (subject.getId() > 0)
dao.removeDetached(subject);
}
}
return changes;
...
syncSubjects 메서드는 동기화 메서드이며, 이는 Flex에서 클라이언트측의 관리 컬렉션에 변화가 있을 때 이 메서드가 호출됨을 의미한다. 만약 어셈블러가 이러한 메서드를 사용하고 있다면 createItem, updateItem, deleteItem 메서드 대신 그 메서드들이 호출된다. 삭제시 한 가지 알아두어야 할 점은 클라이언트에서 아직 개체 관리자(entity manager)에 들어있지 않은 객체를 대상으로 메서드를 호출함으로써 발생하는 문제를 syncMethod 메서드가 DAO의 removeDetached 메서드를 호출하여 방지한다는 것이다.
메서드의 인자는 관리 데이터 컬렉션의 특정 구성요소에 대한 변경사항을 나타내는 ChangeObject의 리스트이다. 변경사항이 생성이나 갱신과 관련된 변경사항이라면 해당 객체를 개체 관리자로 병합한 다음 변경된 객체에 대한 새로운 버전의 객체가 클라이언트 측으로 전달된다. 변경사항이 삭제와 관련된 것이면, 해당 객체는 previousVersion 프로퍼티에 저장되어 있으므로, 그 프로퍼티에 접근하여 그 객체가 실제로 저장되어 있는지 확인한 다음(그 객체가 ID를 갖고 있는지 확인하여), removeDetached 메서드를 사용하여 해당 객체를 삭제한다.
...
public boolean shouldAddSubjectToAllCollection(Object[] params,
Object item) {
return true;
}
public boolean shouldAddSubjectToNameCollection(Object[] params,
Object item) {
String name = (String) params[0];
Subject subject = (Subject) item;
if (subject.getName().contains(name)) return true;
return false;
}
...
다음 두 메서드는 구현은 간단하지만 약간 더 복잡하다. 이 메서드들은 특정 관리 데이터 컬렉션에 대한 동기화 메서드이다. 각 메서드는 fill 메서드에 대응되며, 우리가 getAllSubjects와 findByName 메서드를 살펴본 것을 기억할 것이다. 클라이언트가 이 fill 이나 관리 컬렉션을 요청하면 LCDS는 어떤 클라이언트가 어느 fill을 사용하고 있는지를 관리한다. 이 어셈블러가 관리하는 특정 타입의 객체가 새로이 생성될 때마다 이 메서드들이 실행되어 어셈블러가 새 항목이 이러한 fill 로 들어가야 할지를 판단한다.
첫 번째 메서드인 shouldAddSubjectToAllCollection은 이름 끝이 물음표가 붙었어야 했다(자바는 루비처럼 이렇게 하는 것을 허용하지 않아 아쉬울 따름이다). 기본적으로 이 메서드는 "새 주제가 모든 주제에 대한 컬렉션에 추가되어야 합니까?"라고 묻는 것과 같으며, 물론 대답은 "그렇다"이다. 왜냐하면 새 주제 역시 주제이고, 모든 주제의 컬렉션에 속하기 때문이다.
다음 메서드는 다음과 같이 묻는다. "새 주제가 첫 번째 매개변수로 전달된 이름과 일치하는 주제의 컬렉션에 추가되어야 합니까?". 이 경우 전달된 주제의 이름이 컬렉션에 포함되어 있는지 확인할 필요가 있는데, 만약 컬렉션에 해당 이름이 포함되어 있다면 true를 반환한다.
LCDS는 findByName 메서드를 통해 검색한 각각의 구분되는 이름에 해당되는 주제 컬렉션을 관리하며, 만약 새로운 주제가 생성되면 이 메서드를 호출하여 그와 같은 컬렉션에 추가되어야 할지 확인한다.
어셈블러 설정
어셈블러가 어떻게 설정되어 있는지 확인하기 위해 LCDS 서비스의 설정을 살펴보기로 하자. [그림 16]은 LCDS가 설치된 내역을 보여주며, 우측은 중요 설정 파일을 설명한 것이다.
[그림 16] 중요 설정 파일에 대한 설명이 곁들여진 LCDS 설치내역
LCDS installation directory : LCDS 설치 디렉터리
data service definition : 데이터 서비스 정의
compiler configuration : 컴파일러 설정
automatic compilation config, logging : 자동 컴파일 환경설정, 로깅
message service definition : 메시지 서비스 정의
Flex Libraries included in compilation : 컴파일 과정에 포함된 Flex 라이브러리
remote object definition : 원격 객체 정의
master service config, includes others : 전역 서비스 설정(기타 설정내역 포함)
어셈블러 정의는 data-management-config.xml 파일에 들어있다. 다음은 data-management-config.xml 파일에 포함된 SubjectAssembler 정의를 보여준다:
application
...
이 부분은 Flex 서비스 코드에 대한 데이터 서비스 목적지로서 주제 어셈블러를 어떻게 제공할지 LCDS에 알려준다. 목적지 ID는 subjectService인데, 이는 어댑터가 java-dao 어댑터이기 때문이며, 기본적으로 우리가 자바 어셈블러를 사용하고 있다는 것을 의미한다. 채널은 services-config.xml에 정의되어 있으며, 어떤 클래스가 어셈블러 코드를 담고 있는지를 LCDS에 알려준다. 유효범위(scope)는 어셈블러가 얼마나 오랫동안 활성 상태에 있을지를 말하며, 요청이나 세션에 대해, 또는 전체 애플리케이션(우리가 원하는 유효범위다)에 대해 하나의 어셈블러만 존재할 수 있도록 설정할 수 있다.
우리는 이러한 설정을 통해 어셈블러의 매우 유용한 기능들을 구성할 수 있다. 여러분은 테이블 형태로 정렬된 상당한 양의 데이터를 HTML 페이지로 전송해 본 적이 있는가? 데이터 집합이 커질 수록 결국 여러분이 한 번에 한 페이지를 통해 볼 수 있는 데이터 양을 제한하기 위해 특정한 페이징 접근법을 구현하기로 마음 먹기 전까지는 페이지에 포함되는 데이터의 양이 많아질 것이다. 어셈블러는 LCDS에 관리 데이터 컬렉션을 어떻게 채울지 알려주며, LCDS 또한 컬렉션이 지능적으로 채워지도록 처리한다. 이는 사용자가 어셈블러에서 스크롤을 이용하여 데이터 그리드를 움직이면, LCDS가 미리 사용자가 보게 될 한 "페이지" 분량의 데이터를 컬렉션에 들어가게 한다는 뜻이다. 다음과 같은 설정을 통해 여러분은 그와 같은 행위를 제어할 수 있다.
태그는 어셈블러의 데이터 접근 메서드에 대한 정보가 들어 있는 곳이다. 여기에서는 관리 컬렉션에 들어있을 모든 데이터를 획득하는 방법을 정의한다. Flex에 의해 전송되는 인자 집합은 어떤 fill 메서드를 사용할지 LCDS에게 알려주므로 인자를 받지 않는 fill 메서드를 포함하여 인자 집합에는 단 하나의 fill 메서드만이 포함될 수 있다.
getAllSubjects fill 메서드는 모든 주제를 가져오므로 인자가 필요하지 않다. 이 메서드를 이용하여 가져온 주제는 정렬된 상태여야 한다. 한 가지 더, addSubjectToAllCollection 메서드를 기억하는가? 이 메서드를 통해 우리는 LCDS가 특정 fill 메서드에 대해 이 메서드를 이용하여 새로운 주제가 fill에 추가되어야 할지 여부를 확인하게 한다.
문자열 매개변수가 하나 있다면 findByName fill 메서드가 사용된다. 이 메서드도 정렬된 상태를 유지해야 하며, fill에 새로운 주제를 추가해야 할 경우 선택해야 할 메서드는addSubjectToNameCollection이다.
동기화 메서드인 syncSubjects 메서드는 주제에 대한 관리 컬렉션에 어떠한 변화가 일어나면 호출되는 메서드이다.
진척상황을 확인하기 위해 다른 어셈블러와 설정 파일을 살펴보겠지만, 한 가지 알아둘 점은 주제 컬렉션에 대한 설정과 어셈블러는 다른 것에서도 비슷하게 되어 있다는 것이다.
JMS를 이용하여 RTMP상으로 데이터 보내기
하나 더 남은 것이 있다. 우리는 새로운 예약사항이 발생할 때마다 실시간으로 관리자에게 이러한 사실을 알려주고자 한다.
이런 일을 하는 방법에는 몇 가지가 있다. 가장 분명한 방법은 몇 초 간격으로 실행되는 타이머를 통해 서비스 메서드를 호출하여 새로운 갱신내역이 있는지 확인하는 것이다. 나는 이러한 방식을 “구걸(poor man’s push)” 메서드라 한다. 아직 LCDS가 준비되어 있지 않다면, 이러한 방법을 적용하는데 어려움이 있을 것이다.
운좋게도 LCDS가 있으면 더 나은 방법이 있다. 알다시피 웹을 돌아가게 하는 HTTP(HyperText Transfer Protocol) 프로토콜은 클라이언트와의 통신을 위한 채널을 생성하며, HTTP 1.1 버전에서는 그와 같은 채널이 “alive” 상태로 남아있거나 재접속에 대비할 수 있지만, 클라이언트 상태에 대한 정보는 전달해 주지 않으며 요청에 대한 응답으로만 정보를 전송한다.
Adobe에는 Real Time Media Protocol (RTMP)라는 Flash 미디어 서버를 강력하게 만들어 주는 프로토콜이 있다. RTMP 연결은 양방향 통신과 미디어와 데이터에 대한 스트리밍을 허용하도록 열린 상태를 유지한다. LCDS는 RTMP를 활용하여 데이터와 메시지가 클라이언트에 대한 확인작업을 거치지 않고도 서버에서 클라이언트로 전송되게 할 수 있다.
Java Messaging Service (JMS)는 메시지 중심의 소프트웨어 개발을 위한 API이다. JMS는 종단간(point-to-point) 메시징(또는 대기열 메시징(queue messaging))과 발행/구독(publish/subscribe) 메시징으로 두 가지 메시징 모델을 지원한다.
대기열 메시징은 여기서 생산자(producer)를 호출하는 서버가 클라이언트나 소비자(consumer)에 메시지를 전송할 수 있다는 사실만 제외하면 브라우저와 웹 서버가 따르는 클라이언트/서버 모델과 유사하게 동작한다.
발행/구독 메시징은 생산자와 소비자간에 일대일 관계를 갖지 않는다. 수 많은 구독자가 하나의 발행자를 구독하여 모든 구독자가 발행자가 보내는 메시지를 받을 수 있는 모델이다.
우리는 발행/구독 모델을 사용할 예정인데, 서버가 어떤 책이 예약되었음을 나타내는 메시지를 전송하면 관리자 클라이언트가 구독하여 해당 메시지를 획득하는 식으로 발행/구독 모델이 우리가 지원하려고 하는 것과 잘 맞기 때문이다. JBoss에서는 어떤 메시지가 전송될지에 관한 토픽(topic)을 초기화하면 어느 곳에서도 해당 토픽에 대한 메시지를 발행할 수 있다.
다음은 bookie-data/service-config/jboss/bookie-message-service.xml의 토픽을 초기화하는 방법을 보여주며, 여러분은 이 파일을 JBoss 배포 디렉터리에 복사해야 한다.
jboss.mq:service=DestinationManager
이 애플리케이션이 실제 운영 환경에 배포되어 있다면, 이러한 설정 파일이 이미 배포 디렉터리에 들어있겠지만, 개발 과정에서는 루트 프로젝트의 build.xml 파일에서 이 파일을 dev-deploy 태스크에서 복사하게 해두었다.
ReservationDAOBean에서는 예약이 저장되었을 경우 해당 예약 사항과 관련된 메시지를 토픽으로 발행할 것이다. 차후에 우리는 Flex에서 관리자 클라이언트를 구독하여 이러한 메시지들을 수신하여 관리자에게 알려줄 수 있는지에 대해 살펴볼 것이다. 다음은 메시지 발행과
관련된 코드를 보여준다:
package lcds.examples.bookie.dao.beans;
...
@Stateless
@Local(value={ReservationDAO.class})
public class ReservationDAOBean implements ReservationDAO {
@PersistenceContext
public EntityManager entityManager;
@Resource(mappedName="TopicConnectionFactory")
private ConnectionFactory connectionFactory;
@Resource(mappedName="topic/outstandingReservationTopic")
private Topic outstandingReservationTopic;
먼저 @PersistenceContext 아래에서는 @Resources가 설정되어 있는데, 이것들은 TopicConnectionFactory를 가리키며, 이를 통해 우리는 해당 토픽에 메시지를 전송하기 위한 연결을 획득한 다음 토픽 자체에 대한 참조를 획득하게 된다.
이제 새로운 예약이 발생하거나 변경되면(ReservationDAO의 persist나 merge 메서드에서), 알림 메시지를 전송해야 한다. 이러한 통지 로직은 sendReservationMessage 메서드에 포함되어 있다. 통지를 위해서는 팩터리에서 연결을 획득하여 세션을 생성한 다음 outstandingReservationTopic으로 주입된(injected) 토픽을 이용하여 MessageProducer를 획득한다.
그런 다음 “객체 메시지(object message)”를 생성하는데, 이것은 우리가 예약이라는 메시지를 가진 타입의 객체를 전송할 것임을 뜻한다. 그런 다음 예약을 객체로 초기화하고 메시지를 보낸 다음 연결을 닫는다. 이런 과정이 꽤 직관적이긴 하지만, 꼭 그렇게 단순한 것만은 아니다. 다음으로 알아야 할 것은 관리자 UI에서 알림 메시지를 보여주어야 한다는 것이다.
다음 번 연재 기사에서는 Flex의 서비스 계층을 살펴보고 Flex를 위한 마이크로 아키텍처(micro-architecture)인 Cairngorm을 소개할 것이다. 언제든지 여러분은 여기에서 전체 연재 기사를 확인해 볼 수 있다.