이글루스 로그인


선택이 아닌 필수 AOP

선택이 아닌 필수
AOP(Aspect Oriented Programming)

 

이일민 l tobyilee@gmall.com l Epril 대표 컨설던트

 

AOP의 시대가 도래하고 있다. AOP가 등장한지 10년이 되면서 AOP는 많은 개발자들의 주목을 받고 있다. 하지만 AOP를 본격적으로 적용하는 길은 쉽지만은 않은 탓에 많은 개발자들이 AOP의 문턱을 넘지 못하고 포기하는 경우가 많다. AOP는 잘 쓰면 좋지만 없어도 크게 아쉬울 것 없는 선택 기술일까? 아니면 이제는 반드시 익혀야 하는 필수 기술일까?

 

객체지향프로그래밍(OOP)은 지금까지 출현한 개발 패러다임 중 가장 성공한 것으로 평가되고 있다. 그 때문에, 현재 시스템 개발에 가장 많이 쓰이는 언어의 대부분은 OOP기반으로 설계된 것들이다. 또, 가장 많이 사용되는 언어인 자바와 C#을 비롯해 오랫동안 사용되어져 온 C++, 스몰토크(Smalltalk) 그리고 최근에 각광받고 있는 다이나믹 언어인 파이썬(Python)과 루비(Ruby)에 이르기까지 모두 객체지향언어라는 것을 장점으로 내세우고 있을 정도다.

 

 

AOP의 도입의 필요성

 

 

객체지향 프로그래밍의 가장 큰 특징은 시스템이 다루고자 하는 도메인의 모델을 설계와 구현에 손쉽게 반영할 수 있다는 데 있다. 오브젝트 모델자체가 실제 세계의 개체를 투영하고 있기 때문이다. 고객의 요구사항과 도메인을 가지고 객체지향적인 설계를 하고 나면 그 다음 작업은 매우 수월하다. OOP의 경우 설계(design)와 구현(implementation)은 거의 1:1 관계에 있기 때문이다. 그래서 OO툴을 사용하면 설계 자료에서 자동으로 코드를 생성해 낼 수 있기도 하다. 따라서 많은 경우 설계상의 기능은 각각 하나의 클래스 또는 패키지로 구현될 수 있다.

 

설계와 구현이 정확히 대응되는 이상적인 상황이라면 OOP의 장점이 극대화될 수 있다. 각 패키지별로 독립적인 개발이 가능해 최적화된 형태의 개발 작업이 가능하다. 또 유지보수가 쉬워지고 독립적인 테스트도 수월해진다. 더 나아가 각각의 컴포넌트를 재활용하거나 확장해나가기도 쉽다. OOP나 객체지향분석설계(OOAD)를 설명하고 있는 책에 나오는 모델, 설계다이어그램과 그것을 구현한 코드를 보면 이런 OOP의 매력에 감탄하게 된다. 하지만 현실세계의 OOP 개발자들이 이런 OOP의 장점을 충분히 누리고 있는지 묻는다면 쉽게 그렇다고 대답하기 어렵다.

 

엔터프라이즈 시스템의 복잡도는 점점 증가하고 있다. 이런 복잡한 시스템은 요구사항과 기능을 모두 충족시키면서 객체지향적인 설계를 하는 것이 매우 어려워진다. 결국에는 객체지향적인 원칙을 지키지 못하고 타협할 수밖에 없는 상황에 이르는 것이 현실이다. OOP의 장점을 살리지 못하게 방해하는 이런 타협의 결과는 크게 두 가지로 나타난다. 하나는 기능의 분산이고 다른 하나는 코드의 혼란이다.

 

 

분산(Scattering)

 

 

하나의 기능을 하나의 모듈로 캡슐화하기 불가능하거나 힘든 경우가 있다. 도메인의 핵심기능을 객체지향적인 원칙에 따라 설계했다하더라도 많은 엔터프라이즈 서비스 기능들은 단일 모듈로 독립시킬 수 없고 여러 모듈에 분산해서 존재하게 된다. <그림 1>은 이러한 실제 예를 보여주는 그림이다. 객체지향적으로 설계한 모듈들 안에 보안이나 모니터링 기능이 분산되어 존재하고 있다. 이런 기능들은 단일 모듈로 만드는 것이 불가능하다. 따라서 이런 기능의 코드들은 여러 개의 모듈 안에 분산되어 반복적으로 나타나게 된다. 필연적으로 중복된 코드를 양산하게 되는 것이다.

 

결과적으로 이런 분산된 기능의 코드는 유지보수를 힘들게 한다. 더 나아가서는 객체지향적으로 설계된 모듈임에도 재사용이 어려워 질 수 있다. 분산된 코드에 침범 당한 모듈은 의존성이 매우 강해지기 때문이다.

 

 

혼란(Tangling)

 

 

여러 개의 모듈에 분산되어 중복되어있어 모듈화가 불가능한 코드들은 각 모듈의 코드자체를 혼란에 빠뜨리게 된다. 아무리 모듈의 오브젝트 설계자체는 단일책임원칙에 따라서 잘 설계했다 할지라도 이렇게 침범한(invasive) 코드들로 인해서 원래의 깔끔한 설계와 코드는 직접적인 연관성의 기능이 뒤엉켜 지저분한 코드가 되어 버린다.

 

<리스트 1>은 객체지향설계에 따라 설계된 이상적인 OOP코드이다. 하지만 많은 엔터프라이즈 서비스의 기능들이 독립적으로 존재하지 못하고 코드를 침범하면 <리스트 2>와 같이 다양한 기능이 뒤엉킨 지저분한 코드로 바뀌게 된다.

 

<그림 1> OOP모듈에 분산된 기능

 

<리스트-1> 침범당하지 않은 OOP코드

class MemberService {
MemberRepository repository;

public void upgradeLevel(List<Member> members, int newLevel) {
for(Member member : members) {
member.changeLevel(newLevel);
repository.updateMember(member);
}
}

}

 

<리스트-2> 침범당한 OOP코드

class MemberService {
MemberRepository repository;
Log logger;
TranasctionManager txManager;
SecurityService securityService
NotificationPolicy notificationPolicy;
EMailService emailService;

public void upgradeLevel(List<Member> members, int newLevel) {

// Logging
if (logger.isDebug()) { logger.debug("upgradeLevel() started", members, newLevel)
}

// Security
if (!securityService.checkPerssionForModify(Member.class)) {
if (logger.isWarn()) {
logger.warn("Security violation: Member modification", new Date());
}
throw new SecurityException(Member.class, SecurityType.Modification);
}

// Transaction
Transaction tx;
try {
tx = transactionService.beginTransaction();

for(Member member : members) {
member.changeLevel(newLevel);
repository.updateMember(member);
}
}
catch(DataException e) {
try { tx.rollback(); } catch(Exception e2) {throw e2;}
throw new ExceptionTranslater().translateToSystemException(e);
}

if (notificationPolicy.isEMailNotificationEvent("Member.upgradeLevel")) {
emailService.sendNotificationToMemberAdmin(members, newLevel)
}

}

}

OOP를 학습하기 위해서 작성하는 코드가 아니라면 현실 세계 대부분의 코드는 <리스트 2>와 유사하게 만들어진다. 엔터프라이즈 시스템의 복잡도와 요구사항이 날로 높아지는 요즘 같은 경우는 더더욱 복잡한 기능이 결합되어 있는 코드가 만들어지게 된다. 결과적으로 OOP의 장점인 모듈의 독립성이나 재사용, 이해하기 쉬운 코드, 리팩토링, 단위테스트 편이성 등이 극도로 저하된다. <리스트 2>는 OOP언어를 사용해서 작성한 코드이지만 더 이상 OOP의 장점을 찾아보기 힘든 코드가 되어버렸다.

 

이렇게 독립된 모듈로 분리시킬 수 없는 기능들은 주로 개발하는 시스템의 핵심 로직을 담은 핵심 도메인 로직과는 다른 차원에서 존재하면서 도메인 오브젝트에 분산되어 존재할 수밖에 없는 것들이다. 대표적으로 Tracing, Logging, Error and Exception Handling, Monitoring, Statistics gathering, Transaction, Session Management, Threading, Synchronization, Caching, Remote access 등이다. 이러한 기능은 대부분 엔터프라이즈 서비스나 인프라스트럭처 서비스라고 불리는 것들이고 날이 갈수록 점점 더 많이 요구되어지고 있다.

 

타협할 것인가 대안을 찾을 것인가?

 

이러한 문제에 맞닥뜨린 개발자들은 결국 두 가지 중에 하나를 선택해야 한다. 하나는 OOP의 장점을 포기하고 타협하는 것이다. 코드의 중복을 피할 수 없고 지속적인 유지보수, 재활용의 어려움이 있지만 복사&붙이기와 같은 방법을 통해서 일단은 쉽게 접근할 수 있는 <리스트 2>와 같은 방법을 선택하는 것이다.

 

두 번째 방법은 AOP를 이용해서 이러한 문제들을 풀어나가는 것이다. AOP는 바로 이런 분산, 혼재되어있는 코드의 문제점을 극복하고 <리스트 1>과 같은 OOP의 장점과 특성을 그대로 간직한 코드를 만들 수 있도록 돕기 위해서 존재하는 기술이다.

 

많은 개발자들은 AOP가 OOP를 대신해서 독립적인 프로그래밍 패러다임으로 존재하는 대체기술로 오해하고 있다. 하지만 AOP는 OOP가 가진 한계를 극복하고 OOP의 원칙에 충실한 설계와 개발이 가능하도록 지원해주는 역할을 하고 있다. 사실 AOP로 만들어낼 수 있는 모든 기능은 OOP로 할 수 있는 것들이다. 하지만 OOP만으로는 객체지향원칙에 타협을 해야 할 수밖에 없기 때문에 AOP를 사용하는 것이 훨씬 유리한 선택일 것이다.

 

AOP가 어떻게 OOP의 코드를 유지한 채로 이러한 기능들을 적용해낼 수 있는지는 지난 2005년 11월호 <마소>에 필자가 쓴 ‘관점지향프로그래밍-AOP’이라는 기사를 참조하길 바란다.

 

 

AOP의 도입의 장애물

 

 

그렇다면 이렇게 장점이 많은 AOP가 나온 지 10년이 되도록 폭 넓게 적용되지 못한 이유는 것일까? 그 이유는 AOP가 생각보다 도입하기가 쉽지 않기 때문이다. 많은 개발자들이 AOP에 대해서 관심을 가지고 있음에도 AOP를 실제 업무에 적용하지 못하게 하는 AOP 도입의 장애물은 세 가지 정도로 생각해 볼 수 있다.

 

AOP에 대한 미신과 오해

 

첫째는 AOP에 대한 미신과 오해들로 인해서 AOP도입을 꺼리게 되기 때문이다.

 

AOP를 처음 접하는 개발자들은 아주 간단한 예제로 만들어진 튜토리얼을 접하면서 시작한다. 현실 세계에서 가장 많이 필요로 하고 구현하기 쉽기 때문에 자주 등장하는 예제는 Logging과 Tracing이다. 간단한 크로스컷(crosscut)으로 표현이 가능하고 어드바이스(advice)코드가 단순하기 때문에 AOP 예제에 자주 등장한다. 문제는 이로 인해서 많은 개발자들이 AOP의 활용정도를 단순히 메소드 실행을 추적하거나 로그를 남기는 정도로 한정지어 생각하는 것이다. 그러나 AOP의 활용용도는 매우 다양하다.

 

다음의 요구사항을 생각해보자.

 

● 서비스 레이어의 메소드는 트랜잭션 안에서 동작해야하고 서비스 레이어의 경계를 넘어올 때 commit 되거나 예외가 발생했을 때는 rollback되어야 한다.
● 만약 메소드의 이름이 get으로 시작하면 읽기 전용의 트랜잭션이 생성되어야 한다.
● JDBC 코드로 만든 데이터 레이어에서 발생하는 SQLException은 자동으로 시스템의 표준 Exception hierachy의 하나로 변환되어 다시 던져져야 한다.
● 데이터 레이어의 메소드는 반드시 서비스 레이어의 메소드에서만 호출되어야 한다. 프레젠테이션 레이어나 JSP 등에서는 DAO 메소드를 호출할 수 없고 개발자가 강제로 그러한 코드를 만든다면 에러가 발생해야 한다.
● Optimistic-locking에 의해서 실패한 서비스 메소드는 정해진 횟수만큼 반복해서 재시도 되어야 한다.

 

AOP를 이용하면 이러한 요구사항을 충족하는 기능을 OOP의 핵심코드를 전혀 수정하지 않고 시스템 전체에 걸쳐서 적용하는 것이 가능하다. AOP의 활용용도는 이외에도 무궁무진하다.

 

AOP는 매우 복잡한 기술이고 결과적으로 전체 프로그램도 복잡해지며 이해하기 힘들어진다는 편견도 문제다. AOP의 구현기술이 복잡한 것은 사실이다. AOP를 사용하기 위해서는 OOP 언어를 확장하거나 별도의 컴파일러가 필요하고 바이트 코드조작 등의 고급기법이 사용된다. 하지만 그것은 AOP 툴을 만드는 개발자의 부담일 뿐이고 그것을 사용하는 개발자는 오히려 더 단순하고 심플한 개발을 할 수 있다. 그 이유는 AOP를 이용하면 훨씬 높은 수준의 추상화가 가능하기 때문이다. 물론 추상화가 잘 될수록 프로그램의 흐름을 이해하는 것은 더 어려워진다. 하지만 그것은 OOP도 마찬가지다. 추상화의 장점이 크기 때문에 절차적 프로그래밍보다 OOP를 선호하는 것처럼 AOP의 추상화도 마찬가지이다. 또 AOP로 추상화를 하지 않은 타협한 OOP코드는 사실 이해하기가 훨씬 더 어렵다.

 

그 외에도 AOP의 기능은 잘 설계된 인터페이스나, 디자인패턴 등을 잘 활용하면 대체할 수 있다는 것도 잘못된 이해이다. 인터페이스나 디자인패턴은 OOP 자체의 설계를 건전하고 견고하게 만들어줄 수 있다. 하지만 AOP는 OOP로는 해결할 수 없는 부분을 담당하는 것이기 때문에 단지 인터페이스나 디자인패턴이 AOP를 대체할 수 없다.

 

AOP의 학습에 대한 부담

 

두 번째 문제는 AOP의 학습에 대한 부담이다.

 

새로운 기술을 습득하고 더 나아가 새로운 패러다임을 수용하는 것은 언제나 부담스러운 일이다. AOP를 학습하고 사용하려면 절차적 프로그래밍에서 객체지향 프로그래밍으로 전환할 때만큼의 수고가 필요하다.

 

AOP 학습이 어려운 이유는 OOP와 패러다임 자체의 차이도 있지만, 거기에 다른 새로운 언어적인 요소도 추가됐기 때문이다. 가장 대표적인 AOP 툴인 AspectJ는 자바 언어 자체를 확장해서 만들어진 새로운 언어이다. 따라서 컴파일러도 전용컴파일러를 사용한다.

 

AspectJ에서 가장 중요하면서 제일 어려운 부분은 포인트컷(Pointcut)이다. <리스트 3>의 포인트컷 선언 같은 경우는 사실 쉽게 이해하기 어렵다. AOP에서 포인트컷은 RDB의 SQL과 유사하다고 생각하면 된다. SQL을 충분히 익히지 않으면 RDB 개발에 어려움이 있는 것처럼 포인트컷을 마스터하지 않고는 AOP를 제대로 활용하기 힘들다. 잘못된 포인트컷의 사용은 AOP를 적절히 활용하지 못하고 애플리케이션을 복잡하게 만드는 등의 부작용을 일으킬 수 있다.

 

<리스트-3> 복잡한 포인트컷 선언

(execution(!private * com.other..*.set*(*))
|| execution(!private * com.other..*.get*(*)))
&& cflow(execution(public * com.mycompany..*.*(*, *, ..)))

 

따라서 AOP를 학습할 때에는 포인트컷에 대한 투자를 충분히 해야 한다. 포인트컷의 문법을 숙지하고 다양하게 작성된 포인트컷을 분석해보는 작업이 필요하다. 포인트컷 안에 사용되는 조인포인트(Join Point)의 정의와 의미도 정확히 이해해야한다.

 

AspectJ 5에서는 Java5의 어노테이션을 이용한 포인트컷이 도입되었다. 와일드카드를 이용한 기존의 포인트컷보다 좀 더 편리한 타깃 설정이 가능하다는 장점이 있지만 반면에 포인트컷 자체를 더 이해하기 어렵게 만들기도 한다. <리스트 4>는 언뜻 보기엔 유사하지만 의미가 다른 어노테이션을 이용한 세 가지 포인트컷의 예이다. 첫 번째 포인트컷은 @Transactional 어노테이션을 가진 모든 종류의 메소드의 실행(execution)을 가리키는 포인트컷이다. 두 번째는@Transactional 어노테이션을 가진 오브젝트를 리턴하는 모든 메소드를 실행하는 것을 정의한 포인트컷이다. 세 번째는@Transactional 어노테이션을 가진 타입(type)에 정의된 모든 메소드의 실행에 대한 포인트컷이다. 매우 짧고 간단한 포인트컷들이지만 이를 명확히 구분하려면 정확한 포인트컷 언어에 대한 정의를 알고 있고, 익숙하게 사용할 수 있도록 학습하는 것이 절실히 필요하다.

 

<리스트-4> 어노테이션을 이용한 다양한 포인트컷 정의

execution(@Transactional * *.*(..))
execution((@Transactional *) *.*(..))
execution(* (@Transactional *).*(..))

또 아직 이렇다할 표준이 없다는 점도 AOP 학습을 어렵게 하는 이유 중 하나이다. 따라서 AOP 툴에 따라 사용하는 용어가 조금씩 다르고 포인트컷이나 애스펙트를 정의하는 것이 제각각이다. 그로인해 용어의 혼동을 가져오기도 하고 하나의 AOP 툴을 충분히 이해했다하더라도 다른 AOP 솔루션을 사용하면, 또다시 새로운 학습이 필요하게 되는 어려움이 있다. AOP의 표준을 만들거나 스펙의 연합체를 만들려는 노력은 계속 있어왔다. 대표적으로 SpringAOP가 참여한 AOP Alliance는 Java/JEE의 AOP 표준을 만들기 위한 시도 중의 하나였다. 하지만 여전히 명확히 정의되고 모든 AOP 솔루션이 따르고 있는 표준은 존재하지 않는다. 그나마 다행스러운 것은 AOP 툴 사이에 여러 가지 호환을 위한 노력이 진행되고 있다는 점이다. AspectJ의 포인트컷 언어를 사용하는 Spring2.0 AOP의 예가 그런 긍정적인 노력의 하나라고 할 수 있다.

 

AOP의 학습이 쉽지 않은 것은 사실이지만 그 필요성을 절실히 인식하고 기술습득에 대한 충분한 투자를 할 만한 가치를 인정하는 것이 필요하다.

 

AOP도입전략의 부재

 

세 번째는 적절한 AOP 도입전략을 가지고 있지 못하기 때문이다.

 

AOP를 도입하려는 개발자나 기업의 가장 잘못된 접근방법은 단번에 AOP를 도입하려는 욕심을 내는 데 있다. AOP를 전부 아니면 전무(All-or-nothing) 개념으로 접근하는 것은 바람직하지 않다. AOP는 패러다임의 전환이고 상당한 노력과 시간이 필요하다. 또 그 적용은 팀 내 모든 개발자에게 영향을 미친다. 섣부른 AOP의 도움은 오히려 AOP에 대한 실망만 안겨주고 그다지 좋은 결과를 얻지 못할 수 있다.

 

AOP 도입의 가장 이상적인 방법은 단계적인 접근이다. 처음에는 가볍고 쉽게 시작할 수 있는 것부터 출발해서 지속적인 학습과 검증을 병행해 가면서 난이도가 높은 단계로 발전해나가는 것이다. AOP는 OOP 전체가 아닌 일부를 대체하기 때문에 그 정도를 조절하는 것이 가능하다.

 

<그림 2> AOP의 단계적인 도입 전략

 

<그림 2>는 AOP 전문가들이 추천하는 AOP 도입전략이다. 크게 4단계로 구분해서 1단계부터 단계적으로 적용하고 성공적인 적용이 끝나면 다음 단계로 나아가는 식이다.

 

첫 번째 단계는 프레임워크 등을 통해서 이미 구현되어있는 애스펙트를 사용하는 것이다. 가장 대표적인 것으로 SpringAOP를 이용한 서비스 레이어에 대한 트랜잭션 애스팩트의 적용이 있다. 스프링프레임워크를 사용하는 개발자는 AOP에 대한 지식 없이 간단한 설정만으로 AOP의 혜택을 누릴 수 있다. 사실 대부분의 스프링 사용자는 SpringAOP를 직접 사용하지 않는다. 하지만 프레임워크에서 제공하는 AOP 기능을 충분히 누릴 수 있다. 이처럼 손쉽게 쓸 수 있는 애스펙트를 적용하면서 AOP로 인해서 단순해진 핵심로직의 편리함을 누리는 것이 AOP의 매력을 느끼고 다음단계로 나아갈 수 있는 출발이 될 수 있다.

 

두 번째 단계는 정책의 검사와 강제(exploration and enforcement)에 AOP를 적용하는 것이다. 시스템 전반에 걸친 정책을 일괄적으로 적용하는 것은 쉬운 일이 아니다. 모든 개발자들이 정책을 명확히 인지하고 있어야 하고 그것을 위반하지 않고 개발에 적용해야 하기 때문이다. 대형프로젝트에는 수십 페이지의 정책문서가 작성되고 개발자들에게 공개된다. 개발자들은 그 규정에 따라 코드를 작성해야 한다. 그렇게 만들어진 코드에 정확히 정책이 적용되었는지를 검증하는 것 또한 큰일이다. 품질팀의 사람들이 여러 단계에 걸쳐서 그것을 검토하지만 끊임없이 수정되고 발전하는 코드를 지속적으로 체크하는 것은 어려운 일이다. AOP는 이런 전체적인 정책을 검사하고 필요에 따라 강제할 수 있는 효과적인 수단을 제공한다. 이 단계에서는 아직 모든 개발자가 AOP에 대한 많은 지식을 가질 필요도 없다. 정책을 결정하고 부여하는 역할을 담당하는 소수의 사람에 의해서 적용이 된다. 더 나아가서 필요하면 정책의 변경도 손쉽게 할 수 있다.

 

레이어기반의 아키텍처(Layered Architecture)에서 레이어 간의 의존성과 메소드의 호출 정책은 중요한 요소이다. 얼마 전에 필자는 대형프로젝트의 아키텍트로부터 개발자들이 이 정책을 지키지 않고 편의에 따라서 뷰(JSP)에서 데이터 레이어(DAO)의 인스턴스를 만들어서 사용하고 있는 것 때문에 고민이라는 얘기를 들었다. 시스템 레벨에서 일괄적으로 이것을 막을 수 있는 방법에 대한 조언을 구했기에 간단히 AOP를 적용하도록 조언해줬다.
AOP를 이용하지 않고 이런 적용을 하려면 매우 복잡할 것이다. 모든 DAO 메소드에 현재 쓰레드의 호출스택(Call Stack)을 조사해서 지정된 레이어가 아닌 곳에서 호출이 있다면 이를 막는 코드를 다 삽입해야 할 것이다. 큰 프로젝트라면 수백, 수천 개의 메소드에 이 코드를 모두 삽입해야 한다. 하지만 AOP를 적용하면 간단히 이런 정책을 강제할 수 있다. <리스트 5>의 AOP 코드만으로 어떤 뷰에서도 DAO를 사용할 수 없도록 제한 할 수 있다. 더 나아가서 정책이 변경된다면 언제든지 이 애스펙트를 수정하는 것만으로 전체 시스템에 정책의 변경을 적용할 수 있다.

 

<리스트 5> 레이어간 호출 정책 애스펙트

aspect LayerCallingPolicy {
pointcut inView() : within(view..*);
pointcut daoCall(): call(* dao..*(..)) &&!inDao();

declare error : daoCall() && inView(): "View에서 DAO를 직접 호출할 수 없습니다";

}

try/catch를 사용한 모든 코드에서 catch 후 아무 것도 하지 않는 dummy catch를 찾아내는 것을 생각해보자. 코드리뷰를 통해서 하려면 엄청난 작업이다. 반면에 AOP를 이용하면 세 줄 정도의 코드만 추가하면 충분하다.

 

세 번째 단계는 인프라스트럭처 애스펙트의 적용이다. 인프라스트럭처 서비스를 사용하는 것은 개발자들이 항상 해야 하는 일이기 때문에 이 단계에서 AOP를 적용하는 것은 팀 내 모든 개발자들이 분명하게 인지하고 있어야 한다. 대부분의 인프라스트럭처 서비스나 엔터프라이즈 서비스를 이 단계에서 AOP를 통해서 모듈화 할 수 있고 개발자들은 결과적으로 OOP에 충실하게 핵심로직을 개발하는 것이 가능해진다. 로깅, 에러핸들링, 모니터링, 트랜잭션, 세션관리, 캐슁 등의 많은 서비스들을 AOP에 적용할 수 있다.

 

세 번째 단계가 되면 시스템 전체적으로 AOP가 중요한 역할을 담당하게 된다. 이쯤 되면 본격적으로 AOP를 사용하고 있다고 생각할 수가 있다. 어느 정도 사용패턴이 정해져 있기 때문에 전략을 잘 세우고 소수의 AOP 개발자들만 수고하면 나머지 개발자들은 매우 편리하게 OOP에 집중해서 핵심로직을 개발할 수 있을 것이다.

 

마지막 네 번째 단계는 각 도메인에 특화된 AOP를 적용하는 단계이다. 이때부터는 개발자들이 매우 깊게 AOP를 사용하게 된다. 도메인이나 시스템에 특화된 로직의 많은 부분에 애스펙트를 적용할 수 있기 때문이다. 이때는Introduction(Inter-type declaration)이나 Mixin같은 고급기법을 이용할 수도 있다. 여기서 AOP와 OOP가 교차하는 부분이 생긴다. OOP의 추상화를 이용한 방법이 가능하지만 그것이 시스템의 설계를 복잡하게 한다면 AOP를 이용하는 것도 고려해볼만 하다. 예를 들어 OO디자인 패턴의 대표적인 옵저버패턴(Observer Pattern)은 객체지향적인 패턴을 활용해서 OOP로 구현할 수 있지만 경우에 따라서는 AOP를 사용하면 매우 간단하게 구현할 수도 있다.

 

단계적인 AOP 적용과 학습을 통해서 전체 팀이 AOP에 익숙해진 시점에서 이 네 번째 단계를 적용하기 시작하면 된다. AOP의 성공적인 적용에는 반드시 단계가 있다. 충분한 이해와 경험이 없이 처음부터 모든 영역에 AOP를 사용하려고 하는 것은 반드시 문제를 일으키게 마련이다.

 

 

AOP의 미래

 

 

필자가 AOP에 대한 기사를 처음 썼던 때에 비하면 지금은 AOP가 훨씬 많이 사용되고 있다. 특히 스프링프레임워크는 AOP를 널리 알리고 손쉽게 실무에 적용하게 해준 일등공신이다. 또 AspectWertz 프로젝트와 통합되고 Java5의 많은 특성을 적용한 AspectJ 5의 발전은 AOP 도입의 견인차 역할을 톡톡히 하고 있다. 최근엔 AspectJ와 SpringAOP도 긴밀하게 연동이 되어서 AOP 툴 간의 비호환 문제가 많이 해소되었다.

 

AOP를 이용하기 시작한 개발자들의 공통적인 반응은 “AOP가 없을 때는 어떻게 개발을 했는지 모르겠다”고 할 정도로 매우 뜨겁다. 최근에 등장한 많은 기술들의 공통점은 개발자들이 기술적인 것보다는 도메인의 비즈니스로직에 더 집중할 수 있도록 하는 것과 객체지향의 기본정신에 충실할 수 있도록 돕는 것이다. POJO 기반의 개발전략과 기술이 바로 그런 것들이다. 동시에 POJO 중심의 개발에서 AOP는 빠질 수 없는 필수 도구이다.

 

점점 복잡해지는 시스템의 요구사항을 기술적인 타협을 통해서 적당히 해결하고 후에 많은 문제를 떠안고 갈 것인가 아니면 처음엔 조금 부담이 되지만 AOP라는 멋진 대안을 선택해서 개발의 즐거움을 누릴 것인가 하는 것은 이제 개발자들의 선택에 달려있다.

 

참고자료

1) Aspect In Action, Ramnivas Laddad, Manning(2003)
2) One-on-one J2EE Development without EJB, Rod Johnson, Wrox(2004)
3) The AspectJ Project, http://www.eclipse.org/aspectj/
4) Spring 2.0 reference manual, http://static.springframework.org/spring/docs/2.0.x/reference/index.html
5) AOP@Work: AOP myth and realities, http://www-128.ibm.com/developerworks/java/library/j-aopwork15/
6) Introduction to Practical AspectJ Programming, SpringOne 2006
7) AOP in the enterprise, SpringOne 2006
8) Avoing AOP pitfalls, The Spring Experience 2006
9) AsoectJ for Spring Developer, The Spring Experience 2006

출처명 : 한국마이크로소프트 [2007년 3월호]

by 루크 | 2007/04/11 09:07 | S/W 공학 | 트랙백 | 덧글(0)

[Eclipse] DB 플러그인 SQL Explorer 소개

[Eclipse] DB 플러그인 SQL Explorer 소개
SQL Explorer : Eclipse를 위한 RDMS 쿼리 도구

모 사이트 지원나갔는데, 간만에 RDB에 접속할 일이 생겼다. 한 1년만인가..
select * from table; 빼놓고는 생각나는게 없네.. ^^

옆에 있는 사람이 DB접속해서 쿼리 날리고 결과 보여주는 프로그램이 없는 나를 보고 원시인 보듯 하는 것같다.. ^^
멋진 이클립스 플러그인으로 본떼를 보여주리라~~

◆ 어디서 받나?

Eclipse Plugins 싸이트에 갔더니 DB 관련 플러그인 중에 가장 높은 순위에 있는 것이 SQL Explorer였다. 그래서 선택했다.

설치는 다 아시다시피 이클립스 디렉토리에 압축을 풀면 된다.

Ecllipse 3.1을 지원한다.

◆ 사용
SQL Explorer 는 단독 퍼스펙티브로 작동한다.

이클립스 차림표에서 Windows->Open Perspective->Other->SQLExplorer 항목을 선택한다.

여기부터는 My SQL을 기준으로 설명한다.
화면 좌측 Drivers 뷰는 DB 접속 템플릿과 같은 것이다. 드라이버 클래스등을 설정해 준다.

◆ JDBC 드라이버 설정
MMMySQL Driver에서 오른쪽 클릭 -> Change the Selected Driver -> Extra Classpath 에서 "Add" 단추를 눌러 My SQL JDBC 드라이버 JAR 파일을 지정한다.

JDBC드라이버의 JAR는 알아서 구한다.

Fedora Core 2 에서는 yum install mysql-jdbc 해주면 /usr/share/java 디렉토리에 My SQL의 JDBC 드라이버 JAR가 깔린다.

◆ 접속 정보 설정
Aliases 탭을 누른다. 여기서 진짜 접속에 쓰이는 정보를 설정한다.
Aliases 뷰의 빈 공간에서 오른쪽 클릭 -> Create New Alias를 누르고 접속에 사용될 별명(아무 이름)과 Driver(MMMySQL Driver 선택), 접속 URL, 사용자명, 비밀번호 등을 기록한다.



◆ 접속과 쿼리 날리기
이제 Aliases 뷰에 새로 만들 접속 정보가 표시된다.
접속정보에서 오른쪽 단추를 누르고 Open...을 하면 접속된다.

화면 가장 오른쪽 Database Structure View 에서 말 그대로 데이타베이스의 구조, 테이블, 테이블 구조 등을 볼 수 있다.

SQL문장을 입력하고 실행하려면 화면 오른쪽 하단 Connections 에서 접속 정보의 이름을 오른쪽 클릭하고 New SQL Editor를 하면 화면 가운데에 SQL 문장을 입력하는 편집기가 나온다.
거기에서 SQL문을 입력하고 사람이 달리기 하는 모양의 아이콘을 누르면 화면 가운데 하단에 결과가 나온다.
단축키로 Ctrl-F9 가 지정되어 있다고 나오지만 작동하지 않았다.

주: MS-Windows에서는 Ctrl-F9 작동했음



◆ 장/단점
사실 나는 이 프로그램의 장단점을 논할 수 없다. 딴 걸 써본게 거의 없으니.
내 생각에 좋은 점이라면 일단 매우 많은 데이타베이스에 대한 접속 템플릿이 미리 마련되어 있어 초보자들에게 거부감이 덜하다는 점이다.

허나 도움말이 없다... --;

참조 : http://www.eclipsenews.com/en_archive/etips_08042004.html

◆ 기타
쿼리문 중간에 SQL 주석(--)을 넣으면 쿼리가 동작하지 않는다.
MySQL이나 SQL*Plus 등에서 작동해도 SQL Explorer에서는 안된다. 쿼리문 중간의 주석은 모두 제거할 것.

by 루크 | 2007/02/28 10:17 | 트랙백 | 덧글(0)

J2EE Connector Architecture 활용하기

IBM WebSphere Developer Technical Journal: J2EE Connector Architecture 활용하기 (한글)

Phil Wakelin, CICS Transaction Gateway Technical Planner, IBM
Peter Masters, CICS Development, IBM

CICS 애플리케이션과 J2EE 컴포넌트의 통합은 많은 기업들이 직면하고 있는 문제입니다. 이 글에서는, IBMWebSphere Application Server에 전개된 CICS 애플리케이션과 J2EE 컴포넌트의 트랜잭션 통합에 J2EE Connector Architecture (JCA)와 CICS Transaction Gateway가 어떻게 사용되는지를 설명합니다. WebSphere Application Server V6.x에 맞춰 업데이트 되었습니다.

머리말

CICS 애플리케이션은 서비스의 트랜잭션 자질과 동의어이다. 이러한 애플리케이션들을 현대적인 J2EE 컴포넌트와 통합한다는 것은 많은 기업들에게 도전이 된다. J2EE Connector Architecture (JCA)와 CICS Transaction Gateway는 WebSphere Application Server에 전개된 CICS 애플리케이션과 J2EE 컴포넌트의 트랜잭션 통합에 사용될 수 있다.

근본적인 트랜잭션 개념을 설명하는 것으로 시작해서, IBM WebSphere Application Server, CICS Transaction Server for z/OS™ (CICS TS), CICS Transaction Gateway (CICS TG) 내의 트랜잭션 환경을 자세히 검토할 것이다. CICS TG V6.1 for z/OS의 two-phase commit 지원인 새로운 XA도 설명하겠다. WebSphere Application Server내의 서블릿과 EJB 컴포넌트에 사용할 수 있는 트랜잭션 컨트롤을 상세히 분석하고, 이러한 컨트롤을 사용하여 WebSphere Application Server와 CICS에 전개된 애플리케이션들 간 다양한 트랜잭션 통합 레벨을 설명하도록 하겠다.

이 글은 JCA와 CICS를 사용하는 트랜잭션 의미를 이해하고자 하는 애플리케이션 디자이너와 아키텍트를 위한 글이다. CICS와 JCA에 대한 기본적인 지식이 있는 것으로 간주한다.

분할의 역사

분할이라는 개념은 SQL Server에서는 새로운 것이 아닙니다. 사실 SQL Server 제품의 모든 릴리스에서 여러 가지 형태의 분할이 가능했습니다. 그러나 분할 스키마를 만들고 유지 관리하기 위해 특수하게 디자인된 기능이 없었기 때문에 분할은 번거로웠으며 활용도가 낮았습니다. 또한 사용자 및 개발자는 보다 복잡한 데이터베이스 디자인으로 인해 스키마를 잘못 이해했기 때문에 이점이 줄어들었습니다. 그러나 이 개념은 기본적으로 상당한 성능상의 이점을 제공하므로 SQL Server 7.0에서는 분할된 뷰를 통해 분할의 형태를 사용 가능하도록 함으로써 이 기능을 향상시켰습니다. 그리고 이제 SQL Server 2005는 분할된 테이블을 통해 대형 데이터 집합을 분할하기 위한 매우 뛰어난 고급 기능을 제공합니다.

트랜잭션: 이것은 무엇인가?

J2EE 세계에서, 트랜잭션은 하나의 작업 단위로서, 이 안에서 복구 가능한 리소스들에 대해 여러 업데이트가 자동으로 이루어진다. CICS 세계에서, 트랜잭션은 특정 트랜잭션 식별자에 의해 호출된 CICS 프로그램(또는 프로그램의 시퀀스)에 의해 수행된 작업을 의미하며, 특정 CICS 태스크 범위에서 실행된다. 이 CICS 태스크는 동기화 포인트(syncpoints)에 의해 구분된 복구 가능한 작업 단위(또는 논리적 작업 단위)들로 구성될 수 있다. 이러한 작업- 단위 는 복구 가능한 가장 작은 원자 단위이고, 이 같은 것은 J2EE 세계의 트랜잭션과 비슷하다.

필수 컴포넌트

트랜잭션 환경 내에서, 모든 참여자들은 리소스 매니저나 트랜잭션 매니저로 구분된다. 리소스 매니저는 파일이나 큐 같은 복구 가능한 데이터를 관리한다. 트랜잭션 매니저는 트랜잭션에 반응하고 여러 리소스 매니저들간 트랜잭션 결과를 조정한다. 이들 사이에서, 트랜잭션 매니저와 리소스 매니저들은 복구 가능한 리소스들의 업데이트를 신뢰성 있게 조정한다. 원자성, 일관성, 고립, 내구성 같은 트랜잭션 규칙들이 관리된다. 이를 위해서, 각 참여자는 공통의 아키텍처 표준을 구현해야 한다. 다음 섹션에서는 다음의 표준과 프로토콜을 살펴보도록 하겠다.

ㆍJ2EE Connector Architecture (JCA)
ㆍTwo-phase commit.

JCA는 J2EE 표준의 일부이고 리소스 어댑터에 의해 구현되는 시스템 콘트랙트를 정의한다. 이러한 시스템 콘트랙트는 리소스 어댑터가 트랜잭션 관리, 연결 관리, 보안에 제공하는 서비스 품질을 정의한다. (그림 1).


그림 1. JCA 시스템 콘트랙트

J2EE 아키텍처에서, 분산 트랜잭션은 글로벌 트랜잭션이라고 불리고, 복구 가능한 리소스를 관리하는 시스템은 리소스 매니저로 알려져 있다. (예, CICS, IMS™, DB2??). 이러한 리소스 매니저들은 트랜잭션에 대한 지원에 기반하여 분류된다. two-phase coordination (XAResource 인터페이스 제공)을 지원하거나, (LocalTransaction 인터페이스를 통해서) one-phase coordination만 지원하거나, 또는 비 트랜잭션(non-transactional)일 수 있다.

트랜잭션 관리의 경우, 리소스 어댑터의 전개 디스크립터(ra.xml 파일)리소스 어댑터는 다음과 같은 콘트랙트 중 하나를 구현해야 한다.

ㆍXA Transaction -- two-phase commit 프로세스에 완전히 참여할 수 있는 리소스 어댑터이고, 글로벌 트랜잭션의 결과에 영향을 줄 수 있다.

ㆍLocalTransaction -- 리소스 매니저에 국한된(이 경우, CICS region) 트랜잭션에 참여할 수 있는 리소스 어댑터이지만, two-phase commit 트랜잭션 기능은 없다. 정갈한 문서를 유지하기 위해서, 리소스 매니저 로컬 트랜잭션(RMLT)를 싱글 리소스 매니저에 국한된 트랜잭션을 의미하는 용어로 사용한다.

ㆍNoTransaction -- 어떤 트랜잭션 속성도 없는 리소스 어댑터. 트랜잭션 콘텍스트에 참여할 수는 있지만 트랜잭션의 결과에 (영향을 주지도 않고) 영향을 받지도 않는다.

WebSphere Application Server 트랜잭션 지원은 한 트랜잭션 내에서 Two-phase 기능을 하는(XA Transaction) 리소스 매니저를 조정한다. 또한, 싱글 one-phase 기능의 (Local transaction) 리소스 매니저가 다른 리소스 매니저의 부재 시 트랜잭션 내에서 사용될 수 있도록 한다.

Two-phase commit

분산 트랜잭션 프로세싱의 필수적인 부분은 two-phase commit 프로세스이다. 이는 트랜잭션에 있는 모든 리소스 매니저들이 어떤 오류에도 관계 없이 신뢰성 있게 조정될 수 있도록 보장하는 흐름 구조이다. two-phase commit는 모든 트랜잭션 프로토콜에 의해 구현되고, 근본 개념은 기본적으로 같다. 다음 디스크립션은 XA 스팩에 따라 흐름을 요약한 것이다. CICS나 LU6.2 같은 기타 프로토콜들은 흐름에 대해 다른 용어와 변수를 사용한다. (CICS syncpoint 흐름은 CICS Intercommunication Guide, SC34-6243, Chapter 2: Recovery and restart in interconnected systems를 참조하라.)

two-phase commit 프로세스 전에, 트랜잭션 동안 수행된 모든 작업은 실행 중인 것으로 간주되고, 이 기간 동안 실패한 것은 롤백으로 이어진다. 업데이트는 트랜잭션 매니저가 two-phase commit 프로세스를 초기화 할 때에만 가능하다.


그림 2. Two-phase commit

two-phase commit 프로세스의 첫 번째 단계(Stage 1)는 다음과 같다.:

a.트랜잭션 매니저는 모든 리소스 매니2저들에게 복구 가능한 리소스들을 위임을 준비할 것을 요청한다.

b.각 리소스 매니저는 (준비될 경우) 긍정적으로 응답하거나 부정적으로 응답한다.(롤백) 리소스 매니저가 긍정적으로 응답한다면 필요한 정보를 안정적으로 기록하고, 응답을 준비하고, 다음 스테이지에서 결정된 것 처럼 트랜잭션의 최종 결과를 따른다.

c.리소스 매니저는 불확실한 것으로 기술된다. 트랜잭션 매니저에 대한 트랜잭션의 최종 결과를 삭제했고, 이제는 트랜잭션의 실제 결과에 대해서도 의심하기 때문이다.

두 번째 단계에서(Stage 2), 모든 리소스 매니저들이 긍정적으로 응답된 것으로 간주한다.:

a.트랜잭션 매니저는 각 리소스 매니저에게 커미트 흐름과 함께 응답한다. 하지만, 리소스 매니저가 응답에 실패하면, 트랜잭션 매니저는 트랜잭션이 중지된 것으로 간주하기 전에 준비 흐름을 다시 보낸다.

b.커미트 흐름을 받을 때, 리소스 매니저는 복구 가능한 리소스에 대한 업데이트를 마감하고, 리소스에 대한 모든 잠금을 해제하거나 파일을 연다.

c.리소스 매니저는 최종 커미트 된 흐름으로 응답하고, 이는 더 이상 의심 상태가 아닌 트랜잭션 매니저를 가리킨다.

d.최종 커미트 흐름을 트랜잭션 매니저에서 받지 못하면, 트랜잭션 매니저는 이 커미트가 리소스 매니저에 아직 도착하지 않은 것으로 간주하고, 긍정적인 응답을 받을 때까지 커미트를 다시 보내야 한다.


커미트 프로세싱 동안 트랜잭션 매니저가 실패하면, 트랜잭션은 리소스 매니저에서 의심 상태로 남겨진다. 재시작 시, 트랜잭션 매니저는 리소스 매니저와 다시 연결되어 트랜잭션 상태를 발견하고, 커미트 또는 트랜잭션 취소 등으로 커미트 프로세싱을 계속 진행한다.

Last participant 지원

J2EE 트랜잭션 환경에서, WebSphere Application Server의 Last participant 지원 기능은 글로벌 트랜잭션 모델을 확장하여 하나의 one-phase commit 리소스를 two-phase commit 가능 리소스를 가진 글로벌 트랜잭션에 참여시킨다. 트랜잭션 커미트에서, 애플리케이션 서버는 two-phase commit 리소스 매니저를 준비하고, 이것이 성공하면 one-phase commit 리소스는 실행을 요청 받는다. two-phase commit 리소스는 one-phase commit 리소스의 응답에 따라서 실행 되거나 롤백된다. 이 프로세스는 트랜잭션 조정을 효과적으로 one-phase commit 리소스에 성공적으로 위임한다.


그림 3. Last participant 지원

Last participant 지원은 WebSphere Application Server Enterprise V5의 일부로 제공되고, WebSphere Application Server V6과 이후 버전의 기본 기능으로 사용할 수 있다.

two-phase commit 프로세스와는 다르게, one-phase commit 리소스와 관련된 통신 오류로부터 복구된 것은 없다. 따라서, one-phase commit 리소스의 실행 동안 통신 오류는 혼합된 결과의 위험성을 트랜잭션에 초래한다. (heuristic hazard) two-phase commit 리소스는 롤백 되지만, one-phase commit 리소스의 결과는 알려지지 않는다. 실행되거나 롤백된다. 따라서 애플리케이션들은 그와 같은 예정된 결과의 위험성까지 포용하도록 설정되어야 한다. 상세한 내용은 이 글 후반에 설명하겠다.



CICS 작업 단위: 트랜잭션, 태스크, syncpoint

 

앞서 언급했던 것처럼, “트랜잭션”이라는 용어는 많은 뜻을 내포하고 있다. 이 글에서는, CICS 트랜잭션을 CICS 영역에서 초기화 된 작업을 의미하고, 네 개의 문자로 된 트랜잭션 ID(tranid)하에서 실행된다. 이러한 tranid는 로딩 될 초기 프로그램의 이름을 짓는 CICS 리소스 정의와, 관련 태스크들이 실행 될 CICS 트랜잭션의 속성이다. 역사적으로, 이들은 Program Control Table (PCT)를 구현했던 매크로에 의해서 정의되었지만 요즘은 resource definition online (RDO) 데이터베이스 내에서 TRANSACTION 정의에 의해 정의된다. 트랜잭션 초기화 때, CICS는 새로운 태스크를 시작한다. 이는 수행 될 트랜잭션 작업의 초기 바운더리이다. 복구 가능한 리소스로의 모든 업데이트나 다른 트랜잭션 시스템에 대한 요청들은, 동기화 포인트(syncpoint)가 CICS 프로그램 내에 도달할 때까지, 이 작업 단위의 새로운 일부가 된다. syncpoint는 EXEC CICS SYNCPOINT 명령어를 사용하여 CICS 애플리케이션 내에서 분명하게 코딩 되거나, 태스크가 종료할 때 도달하게 된다. 이 syncpoint에서, 트랜잭션 매니저로서 CICS는 모든 관련 리소스 매니저들을 준비하고 성공적인 커미트로서 결과를 조정하거나, 이것이 불가능 하면, 모든 복구 가능한 업데이트가 작업 단위의 시작 이전의 상태로 롤백된다. (그림 4).


그림 4. CICS 트랜잭션

시스템 간 분산 프로그램 링크(DPL) 요청이 사용되는 특정 환경에서, 연결된 CICS 트랜잭션은 원격 CICS 시스템에 의해서 조정될 수 있다. CICS TG는 외부 호출 인터페이스를 사용하여 이러한 상황을 확장시켜서 CICS 작업 단위를 초기화 하고 조정한다. CICS TG 용어에서, 이는 확장된 논리적 작업 단위라고 일컬어 진다. (그림 5) DPL 요청이 CICS 프로그램을 호출할 때 사용되면, CICS 프로그램은 syncpoint 명령어를 실행하는 것이 더 이상 허용되지 않는다. 트랜잭션은 원격 시스템에 의해 조정되기 때문이다.


그림 5. 확장된 작업 단위

또한, 이 반대의 상황도 가능하다. 호출된 CICS 트랜잭션이 애플리케이션을 호출하는 개별 트랜잭션 콘텍스트에서 실행되는 상황이다. 이를 sync-on-return이라고 하고, 이는 CICS의 미러(mirror) 트랜잭션을 제어하면 호출 애플리케이션에 컨트롤을 리턴할 때 syncpoint를 실행한다는 것을 의미한다. (그림 6) sync-on-return 유형 링크를 사용하면 호출된 CICS 프로그램이 EXEC CICS SYNCPOINT 명령어를 실행할 수 있도록 한다. 또 다른 트랜잭션 매니저에 종속되지 않기 때문이다.


그림 6. sync-on-return 링크



 

CICS Transaction Gateway의 트랜잭션 지원

 

CICS Transaction Gateway를 사용하여 J2EE 애플리케이션에서 CICS로 트랜잭션 통합을 할 때, 기반 연결은 ECI가 제공한다. 이것은 호출 애플리케이션(J2EE 서블릿이나 EJB 컴포넌트)이 호출된 CICS 트랜잭션을 조정하도록 한다. 하지만, 제공된 기능을 이해하기 위해서는, two-phase commit 네트워크 연결의 필수 트랜잭션 컴포넌트와 복구 가능한 로깅과 관련하여 CICS TG에서 제공하는 트랜잭션 지원을 이해해야 한다.

ECI 프로토콜을 사용할 때, 근본적으로 두 개의 다른 네트워크 연결이 생긴다. 주로 J2EE 컴포넌트에서 CICS Transaction Gateway로의 연결이고, CICS Transaction Gateway에서 CICS로의 네트워크 연결이다. 다양한 네트워크 프로토콜들은 다음과 같은 연결을 위해 지원된다.

CICS Transaction Gateway에 대한 리소스 어댑터: TCP/IP, SSL 또는 로컬 바인딩
CICS Transaction Gateway-CICS: SNA, TCP62, TCP/IP, EXCI.
CICS Transaction Gateway에서 CICS로 연결할 수 있는 옵션들 중에서, ECI 흐름에 two-phase commit 트랜잭션 코디네이션을 제공하는 유일한 프로토콜은 External CICS Interface (EXCI)이다. 이는 CICS TS for z/OS for batch MVS™ 애플리케이션에서 제공하는 인터페이스이고, MVS Resource Recovery Services (RRS)와 관련하여 트랜잭션을 지원한다. 이 지원을 Transactional EXCI라고 하고, MVS 배치(batch) 애플리케이션(이 경우, CICS Transaction Gateway)과 목표 CICS 영역들이 같은 MVS 이미지에 있어야 한다.

CICS TG V6.1의 XA 지원은 트랜잭션 EXCI 지원을 기반으로 구현된다. 새로운 CICS ECI XA 리소스 어댑터를 통해 CICS로의 요청에 글로벌 트랜잭션을 지원한다. 이는 WebSphere Application Server(z/OS)에서 실행되는 로컬 J2EE 애플리케이션이나 AIX 같은 분산 플랫폼에서 WebSphere Application Server에서 실행되는 원격 J2EE 애플리케이션에 대해 two-phase commit 글로벌 트랜잭션을 지원한다.

하지만, CICS TG가 분산 플랫폼에서 실행될 때, one-phase commit 연결이 여전히 사용되어야 하고, 따라서 CICS TG는 로컬 트랜잭션 리소스 매니저로서 간주되어야 한다.

CICS 리소스 어댑터

CICS TG는 J2EE 애플리케이션 서버에서 CICS로 아웃바운드 통신에 사용될 수 있는 세 개의 다른 JCA 리소스 어댑터를 제공한다.:

ㆍCICS ECI 리소스 어댑터 -- LocalTransaction 인터페이스를 구현하고, 리소스 매니저 로컬 트랜잭션을 지원한다. (트랜잭션은 CICS 영역처럼, 하나의 리소스 매니저에 국한되어 있다.) JCA 1.0과 1.5 버전 모두 각각 J2EE V1.3과 J2EE V1.4 애플리케이션 서버에서 사용할 수 있다. 이 리소스 어댑터는 z/OS와 멀티 플랫폼의 CICS Transaction Gateway에서 제공되고, 어떤 플랫폼에서도 어떤 CICS 버전에도 사용될 수 있다.

ㆍCICS ECI XA 리소스 어댑터 -- XA 트랜잭션 지원을 구현하고, 글로벌 two-phase commit 트랜잭션을 완전히 지원한다. JCA 1.5 버전에서만 사용할 수 있고, CICS TG for z/OS와 CICS TS for z/OS와 연결하여 WebSphere Application Server V6 내에서 사용할 수 있도록 CICS Transaction Gateway v6.1 for z/OS에서만 제공된다.

ㆍCICS EPI 리소스 어댑터 -- 3270 터미널 기반 프로그램으로의 액세스에 사용될 수 있다. CICS 3270 인터페이스의 특성 상 어떤 글로벌 트랜잭션 지원도 되지 않으며, CICS 애플리케이션의 트랜잭션 통합에는 사용될 수 없다. JCA 1.0과 1.5 버전이 제공되지만, 멀티 플랫폼 상에서 CICS Transaction Gateway와 함께 사용될 수 있다.

WebSphere Application Server on z/OS의 RRS 트랜잭션 지원

WebSphere Application Server for z/OS를 사용할 때, CICS ECI 리소스 어댑터는 추가 RRS 트랜잭션 모드를 사용하여 글로벌 트랜잭션을 지원한다. 이 RRS 트랜잭션 모드는 논리적 게이트웨이가 사용될 때 자동적으로 사용된다. 로컬 게이트웨이의 사용은 CICS ECI 연결 팩토리 ConnectionURL 매개변수에 “local”을 설정하는 것으로 표시되고, 리소스 어댑터는 WebSphere Application Server JVM 내의 CICS Transaction Gateway EXCI 인터페이스를 직접 호출하여 독립 게이트웨이의 필요성을 없애야 함을 나타낸다. 이러한 공존 방식은 줄어든 경로 길이(pathlength)와 MVS Resource Recovery Services (RRS)로의 two-phase commit 프로세싱 이라는 두 가지 퍼포먼스 혜택을 제공한다.

CICS ECI 리소스 어댑터나 CICS ECI XA 리소스 어댑터를 사용할 때 RRS 트랜잭션 지원이 가능하고, WebSphere Application Server V5, V5.1, V6에서 실행되는 JCA 1.0과 JCA 1.5 리소스 어댑터로도 지원된다.


WebSphere Application Server의 트랜잭션 지원

 

WebSphere Application Server는 다양한 유형의 J2EE 컴포넌트에 다른 품질의 서비스를 제공한다. 이는 컨테이너라고 하는 고립된 런타임 환경을 사용하여 이루어진다. 네 가지 컨테이너가 있는데, Web 컨테이너, EJB 컨테이너, 클라이언트 컨테이너, 애플릿 컨테이너가 있다. WebSphere Application Server V5와 V6에서, JCA 지원은 웹과 EJB 컨테이너 내에서 제공되고, 두 개 모두 JCA 커넥션 풀링 메커니즘과 J2EE 컴포넌트에서 트랜잭션 콘텍스트의 제공을 지원한다.

 

 

Web 컨테이너

 

웹 컨테이너의 기본 기능은 서블릿과 JSP 컴포넌트를 위한 것이지만, 글로벌 트랜잭션 지원도 한다. 하지만, 애플리케이션이 프로그램 방식으로 트랜잭션 범위를 제어하더라도, 웹 컨테이너에서 제공되는 컨테이너 관리 트랜잭션 서비스가 없다. 리소스 매니저 로컬 트랜잭션은 ConnectionFactory에서 획득한 Connection 객체에 대해 getLocalTransaction() 메소드를 호출함으로써 제어될 수 있다. 이는 JCA 커넥션 팩토리(CICS 영역)의 싱글 인스턴스에 국한된 one-phase commit 트랜잭션 콘텍스트를 제공한다. two-phase commit 트랜잭션 콘텍스트는 javax.transaction.UserTransaction 인터페이스를 사용하여 만들어져서, 트랜잭션을 시작 및 종료한다. 이 같은 애플리케이션은 HTTP 요청 수명 내에서 트랜잭션을 실행해야 한다. 하나의 서블릿으로 여러 HTTP 요청을 통해 트랜잭션의 라이프사이클을 확장하는 것은 불가능 하고, 서블릿 service() 메소드의 끝에서 종료되지 않은 글로벌 트랜잭션은 WebSphere Application Server에 의해 롤백된다.

 

 

EJB 컨테이너

 

EJB 컨테이너는 컨테이너 관리 트랜잭션(CMT)와 빈 관리 트랜잭션(BMT)를 포함하여, 글로벌 트랜잭션에 대한 전체 트랜잭션 지원을 제공한다. 세션 빈과 메시지 중심 빈은 두 유형 중 한 유형을 채택한다. 엔터티 빈은 CMT로만 제한된다. BMT를 사용하는 빈들은 트랜잭션 한계 설정에 책임이 있고 UserTransaction 인터페이스를 사용하여 트랜잭션을 시작 및 종료한다. CMT는 트랜잭션 제어를 애플리케이션 서버에 위임하여, 애플리케이션 개발자가 비즈니스 로직을 개발하는데 집중하면서, 애플리케이션의 트랜잭션 속성들이 전개 시 결정될 수 있도록 하기 때문에 선호된다. CMT를 이용한 트랜잭션 제어의 핵심은 EJB 트랜잭션 애트리뷰트이다. 다음에 설명하도록 하겠다.

 

 

트랜잭션 애트리뷰트

 

트랜잭션 애트리뷰트는 EJB 전개 디스크립터(ejb-jar.xml 파일)에서 설정되고 빈 메소드가 호출될 때 글로벌 트랜잭션이 시작되는 환경 하에서 제어하는 애트리뷰트이다. 이 트랜잭션 애트리뷰트는 <container-transaction> 섹션에 나타나고, <trans-attribute> 태그로 설정된다. 예를 들어, 다음 XML은 CTGTesterCCI 빈에 대한 원격 execute() 메소드가 "Required"라는 트랜잭션 애트리뷰트를 갖고 있다는 것을 정의하고 있다.:

<container-transaction>
<method>
<ejb-name>CTGTesterCCI</ejb-name>
<method-intf>Remote</method-intf>
<method-name>execute</method-name>
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>


IBM Rational?? Application Developer의 EJB Deployment Descriptor 에디터를 사용하여 이 설정을 정의하는 모습은 그림 7에 나타나 있다.


그림 7. Rational Application Developer의 트랜잭션 애트리뷰트

 

 

트랜잭션 애트리뷰트의 값과 설명은 표 1에 정리했다.:

표 1. EJB 트랜잭션 애트리뷰트

 

 

로컬 트랜잭션 제한

 

EJB 2.0 스팩은 글로벌 트랜잭션 없이 메소드가 실행되는 경우에, 컨테이너의 작동을 지정하지 않는다. 이는 서블릿, 빈 관리 트랜잭션을 사용하는 세션 빈, 기타 시나리오에도 마찬가지다. 이 경우, 애플리케이션은 “지정되지 않은 트랜잭션” 콘텍스트에서 실행되도록 명령을 받는다. 일관성과 이식성을 이룩하기 위해, WebSphere Application Server는 로컬 트랜잭션 제한(local transaction containment LTC) 정책을 사용하여 “지정되지 않은 트랜잭션” 콘텍스트를 구현한다. 이 LTC 정책은 웹 컨테이너와 EJB 컨테이너에 의해 사용되는 효과적인 범위 설정 장치로서 글로벌 트랜잭션 밖에서 파견된 작업의 시작과 끝을 정한다. 이 같은 LTC 내에서 리소스 매니저(CICS)로의 액세스는 LTC의 끝에서 결정되어야 하는 리소스 매니저 로컬 트랜잭션(RMLT)을 통해서 이루어진다. LTC 범위와 프로그래밍 방식의 인터랙션 방식은 없다. 대신, LTC 범위 지정이 J2EE 애플리케이션에 영향을 주는 방식은 전개 시 J2EE 컴포넌트에 설정될 수 있는 세 개의 확장 전개 디스크립터(XDD)에 의해서 제어된다.:

ㆍBoundary -- BeanMethod (default) 또는 ActivitySession값을 가질 수 있다. ActivitySession은 WebSphere Application Server Enterprise V5에서만 사용할 수 있는 EJB 컨테이너의 확장이다. 로컬 트랜잭션 기반 리소스 매니저의 메소드 한계를 넘어 확장된 작업 단위를 제공한다. (Transactional services in WebSphere Application Server Enterprise V5, REDP3759 참조.)

ㆍResolver -- ContainerAtBoundary 또는 Application (default) 값을 갖는다. 글로벌 트랜잭션 콘텍스트(Never의 트랜잭션 애트리뷰트) 밖에 있는 EJB 컨테이너에서 ECI 요청이 발생되면, 리졸버 애트리뷰트가 Application으로 설정될 경우, ECI 호출 유형은 비확장이 될 것이다. 반대로, 리졸버 애트리뷰트가 ContainerAtBoundary로 설정되면, 리소스 매니저 로컬 트랜잭션이 시작되고, ECI 호출 유형은 확장형이 되고 EJB 메소드 범위에서 컨테이너에 의해 해결된다.

ㆍUnresolvedAction -- Commit 또는 Rollback (default) 값을 가질 수 있다. EJB 컨테이너나 웹 컨테이너를 위해 설정될 수 있고, 컨테이너가 LTC 바운더리에서 두드러지는 RMLT와의 연결을 제거하는 방법을 알려준다. 이는 웹 컴포넌트(서블릿)에 유일하게 설정 가능한 LTC 설정이고, LocalTransaction.begin() 메소드를 사용하여 빈 관리 트랜잭션에 적용한다. 모든 컨테이너 관리 트랜잭션은 트랜잭션이 완료한 후에 자동 실행되기 때문에, 이 애트리뷰트는 웹 컨테이너의 컨테이너 관리 트랜잭션에 의해 사용되지 않는다.

다음은 LTC 정책을 사용할 때 유의할 점이다.:

ㆍLTC 범위는 각 J2EE 컴포넌트에 국한된다. 따라서, EJB 컴포넌트 A는 LTC A 하에서 실행되고 EJB 컴포넌트 B를 호출한다면, 컴포넌트 B는 개별 LTC 하에서 실행되어야 한다.
ㆍ애플리케이션이 글로벌 트랜잭션 밖에서 실행된다면, 웹 컴포넌트나 BMT 엔터프라이즈 빈이 J2EE 1.3 이전 레벨이 아닌 이상, 컨테이너는 언제나 LTC 범위를 지정해야 한다.


트랜잭션 전개 시나리오

 

이 섹션에서는, 서블릿과 EJB 컴포넌트를 WebSphere Application Server 환경에 전개하는 방법과, 트랜잭션 제어를 효과적으로 사용하는 방법을 설명하겠다. 웹 컨테이너와 EJB 컨테이너 환경과 관련하여 자주 묻는 질문들을 선별했고, 이에 대한 실질적인 솔루션을 설명한다.

1. 같은 트랜잭션 범위 내에서, 서블릿에서 여러 ECI 요청들을 어떻게 만드는가?
2. SYNCPOINT 명령어를 실행하는 CICS 프로그램에 연결하는 방법은?
3. 같은 트랜잭션 범위 내에서, EJB 컴포넌트에서 다중 ECI 요청이 가능한가?
4. 글로벌 트랜잭션의 CICS 부분에 ECI 요청을 하는 방법은?
5. z/OS에서 WebSphere Application Server를 사용한다면, 트랜잭션 지원이 달라지는가?
6. z/OS에 CICS TG를 전개하면 어떤 점이 좋은가?
7. two-phase commit 프로세싱 중에 네트워크 연결이 실패한다면 어떤 일이 발생하는가?
8. one-phase commit 프로토콜이 two-phase commit 프로토콜 보다 더 이로운 상황이 있는가?
9. 로컬 트랜잭션 리소스 어댑터(CICS ECI 리소스 어댑터)가 글로벌 트랜잭션에 추가된다면 어떤 문제가 발생하는가?
10. ECIRequest 클래스 또는 Common Connector Framework (CCF) 클래스를 WebSphere Application Server에서 사용한다면 어떤 지원을 받을 수 있는가?
11. CICS 영역이나 트랜잭션이 갑자기 실패한다면?

 

웹 컨테이너에 전개하기

 

웹 컨테이너에 전개된 서블릿 컴포넌트에는 EJB 컴포넌트의 트랜잭션 애트리뷰트 대부분이 부족하다. 하지만, 웹 컨테이너는 RMLT와 LTC 제한 정책 모두를 지원하고, 이 두 가지 모두 ECI 리소스 어댑터에 의해 만들어진 JCA 요청들에 대해 어느 정도의 영향력을 갖고 사용될 수 있다.

 

1. 같은 트랜잭션 범위 내에서, 서블릿에서 여러 ECI 요청들을 어떻게 만드는가?

execute() 메소드가 한 트랜잭션에서 두 번 호출되면, CICS에 이에 상응하는 두 개의 ECI를 호출한다. 두 개 모두 CICS SYNCONRETURN 옵션을 사용하는 것으로 연결된다. 아래 코드 샘플에 설명되어 있다.:

Context ic = new InitialContext();
cxfn = (ConnectionFactory) ic.lookup("java:comp/env/eis/ECICICS");
Connection cxn= cxnf.getConnection();
Interaction ixn= cxn.createInteraction();
ECIInteractionSpec ixnSpec= new ECIInteractionSpec(SYNC_SEND_RECEIVE,"CICSPROG");
JavaStringRecord jsr = new JavaStringRecord()

jsr.setText("DATA1");
ixn.execute(ixnSpec, jsr, jsr);
...
jsr.setText("DATA2");
ixn.execute(ixnSpec, jsr, jsr);
...
ixn.close();
cxn.close();


하지만, 같은 트랜잭션 콘텍스트 속에서 실행되는 CICS로 두 개의 요청 보다는, 이들이 CICS에서 개별 작업 단위로서 실행되고, CICS 미러 트랜잭션의 개별 인스턴스를 사용한다는 것을 알게 될 것이다. 웹 컨테이너 내에서, 각 인터랙션은 후속 요청이 만들어지기 전에 자동 실행되기 때문이다.

같은 트랜잭션 범위에서 실행되는 CICS로 두 개의 요청을 해야 한다면, 두 가지 솔루션이 있다. 첫 번째는, 권장 방법으로서, EJB 컨테이너의 트랜잭션 제어를 사용하는 것이다. (질문 3 참조) 두 번째는 빈 관리 트랜잭션(BMT)로 트랜잭션 범위를 프로그램 방식으로 만들어서 제어하는 것이다. 이는 커넥션 팩토리의 로컬 트랜잭션 지원을 사용하여, 어떤 버전의 CICS Transaction Gateway로도 가능하다. 코드 샘플:

Context ic = new InitialContext();
cxfn = (ConnectionFactory) ic.lookup("java:comp/env/eis/ECICICS");
Connection cxn= cxnf.getConnection();
Interaction ixn= cxn.createInteraction();
ECIInteractionSpec ixnSpec= new ECIInteractionSpec(SYNC_SEND_RECEIVE,"CICSPROG");
JavaStringRecord jsr = new JavaStringRecord()

LocalTransaction tran = cxn.getLocalTransaction();
tran.begin();

jsr.setText("DATA1");
ixn.execute(ixnSpec, jsr, jsr);
...
jsr.setText("DATA2");
ixn.execute(ixnSpec, jsr, jsr);
...
tran.commit();
ixn.close();
cxn.close();


인터랙션을 제어할 때, 이 트랜잭션 콘텍스트는 Connection 객체, ConnectionFactory와 이것이 참조하는 CICS 영역에만 국한된다. 같은 CICS에서 모두 초기화 되고 같은 CICS Transaction Gateway를 통해 액세스 된다면, CICS로 여러 요청들을 만들 수 있다.

두 개의 다른 CICS 시스템 같은, 다중의 리소스 매니저에 업데이트를 해야 한다면, 글로벌 트랜잭션 콘텍스트가 필요하다. 이는 CICS ECI XA 리소스 어댑터를 사용하고 CICS Transaction Gateway V6.1 for z/OS를 사용할 때 필요하다. BMT는 Java Transaction API와 UserTransaction 인터페이스를 사용하여 제어되어야 한다. 이는 여러 연결들을 통해 필요한 XA 트랜잭션 지원을 제공한다.

try {

Context ic = new InitialContext();
utx = (UserTransaction) ic.lookup("java:comp/UserTransaction");
cxfn = (ConnectionFactory) ic.lookup("java:comp/env/eis/ECICICS");

utx.begin();

Connection cxn= cxnf.getConnection();
Interaction ixn= cxn.createInteraction();
ECIInteractionSpec ixnSpec= new ECIInteractionSpec(SYNC_SEND_RECEIVE,"CICSPROG");
JavaStringRecord jsr = new JavaStringRecord()

jsr.setText("DATA1");
ixn.execute(ixnSpec, jsr, jsr);
...
jsr.setText("DATA2");
ixn.execute(ixnSpec, jsr, jsr);

utx.commit();
...
ixn.close();
cxn.close();
} catch (ResourceException re) {
try {
userTransaction.rollback();
}


2. SYNCPOINT 명령어를 실행하는 CICS 프로그램에 연결하는 방법은?

 

SYNCPOINT 명령어(롤백 옵션을 사용하거나 사용하지 않음)를 실행하는 CICS 프로그램은 또 다른 트랜잭션 매니저에 종속될 수 없고, 확장 트랜잭션 또는 글로벌 트랜잭션의 일부가 될 수 없다. 이 트랜잭션 매니저는 DPL 요청을 실행하는 또 다른 CICS 시스템이 될 수 있고, 또는 우리 같은 경우, CICS ECI 리소스 어댑터에 CCI 요청을 만드는 WebSphere Application Server 트랜잭션 매니저가 될 수 있다. 이렇게 제한하는 이유는 프로그램으로 연결된 것이 글로벌 트랜잭션에서는 효력을 발휘하는 리소스 매니저가 되고, 트랜잭션 매니저만(원래의 호출자)는 트랜잭션 조정을 제어할 수 있기 때문이다.

따라서, SYNCPOINT 명령어를 실행하는 CICS 프로그램으로 연결하려면, SYNCONRETURN이 LINK 명령어에 지정되어야 한다. 이는 CICS 서버 프로그램이 클라이언트와는 독립적으로 자신의 작업 단위에서 실행하고 있다는 것을 나타낸다. CCI 호출에 SYNCONRETURN 작동의 사용은 WebSphere Application Server와 연결된 CICS ECI 리소스 어댑터에 의해 동적으로 제어된다. 웹 컨테이너에서 서블릿에서의 호출의 경우, LINK와 SYNCONRETURN이 요청에 필요한 기본 작동이 된다. (질문 1 참조) 반면, EJB 컨테이너에서는, 표 2에 나타난 것처럼, EJB 트랜잭션 애트리뷰트에 대한 비 트랜잭션 속성(Never)을 정의함으로써 이 컨테이너를 활용할 수 있다.

원격 트랜잭션 매니저(이 경우, WebSphere Application Server)에 의해 트랜잭션 제어로부터 CICS 프로그램을 해방시키고, 트랜잭션 결과를 CICS에 위임하기 때문에, SYNCONRETURN 옵션은 유용하다. 이것은 애플리케이션 디자인에 필수적이고, CICS가 모든 복구 가능한 업데이트(CICS/DB2 또는 CICS/VSAM을 통한 업데이트)를 관리한다면 CICS 내에서의 트랜잭션 무결성 역시 관리된다.

 

 

EJB 컨테이너에 전개하기

 

 

WebSphere Application Server의 EJB 컨테이너는 트랜잭션 컴포넌트의 전개에 이상적으로 맞고, 컨테이너와 빈 관리 트랜잭션 모두를 지원한다. 컨테이너 관리 트랜잭션은 모든 트랜잭션 조정이 J2EE 서버에서 수행된다는 이점이 있고, 애플리케이션 개발자들은 트랜잭션 로직 보다는 비즈니스 로직 개발에 집중할 수 있다. 트랜잭션 컴포넌트의 작동을 제어하기 위해, EJB 컨테이너는 컨테이너 관리 컴포넌트의 트랜잭션 작동을 제어하는데 사용될 수 있는 애트리뷰트를 제공한다. 이 섹션에서는, 세 가지 애트리뷰트를 설명하고, 이들이 CICS ECI 리소스 어댑터와 연결되어 사용되는 방법을 설명하겠다.

 

3. 같은 트랜잭션 범위 내에서, EJB 컴포넌트에서 다중 ECI 요청이 가능한가?

트랜잭션 애트리뷰트와 트랜잭션 콘텍스트의 초기 상황은 ECI 호출 유형과 CICS로의 호출에 대한 결과 트랜잭션 범위에 영향을 미친다. 표 2는 결과 ECI 호출 유형과 CICS 미러 태스크의 트랜잭션 범위를 설명한 것이다. Long running 특성을 가진 CICS 미러 태스크는 CICS 내에서의 확장된 작업 단위를 나타내고, synconreturn 옵션을 가진 미러 태스크는 CICS 트랜잭션이 EJB 컴포넌트의 콘텍스트와는 다른 트랜잭션 콘텍스트에서 실행되고, 미러 태스크가 각 ECI 호출이 끝난 후에 종료된다는 것을 나타낸다.


4. 글로벌 트랜잭션의 CICS 부분에 ECI 요청을 하는 방법은?

 

애플리케이션 서버 환경에 따라서, 글로벌 트랜잭션에 참여하는 다른 XA 가능 리소스 매니저가 있다면 네 가지 방법이 있다.

1. CICS TG for z/OS V6.1의 XA 지원은 CICS 영역과 다른 XA 순응 리소스들 간 글로벌 트랜잭션 지원을 제공하기 위해 사용된다. CICS ECI XA 리소스 어댑터는 WebSphere Application Server V6 설정 내에서 사용될 수 있다.

2. WebSphere Application Server for z/OS내의 RRS 글로벌 트랜잭션 지원은 CICS 영역과 다른 XA나 RRS 순응 리소스 매니저들 간 글로벌 트랜잭션 지원을 제공하기 위해 사용된다. 이 기능은 RRS에 기반하고 있고, CICS, CICS TG, WebSphere Application Server가 같은 쥐오스 시스템에서 사용되어야 하고, WebSphere Application Server for z/OS 와 CICS TG for z/OS의 모든 지원 버전들에 사용할 수 있어야 한다.

3. 이 트랜잭션 내에 열거된 XA 리소스 매니저(JDBC 데이터 소스)가 없다면, CICS ECI 리소스 어댑터의 로컬 트랜잭션 지원이 글로벌 트랜잭션 내에서 사용될 수 있다. 글로벌 트랜잭션은 two-phase commit 프로토콜에서 one-phase 최적화를 제공하기 때문에 이것이 가능하고, one-phase commit 흐름은 이 트랜잭션에 개입된 단 하나의 리소스 매니저 브랜치(싱글 리소스)만 있다면 트랜잭션 매니저에 의해 사용된다. 이를 유일한 에이전트 최적화라고 한다. 이는 주로 퍼포먼스 최적화를 위한 것이며, 준비될 필요가 없는 글로벌 트랜잭션의 싱글 one-phase commit 리소스 매니저(CICS ECI 리소스 어댑터를 사용한 연결)를 지원하는 것도 가능하다.

4. WebSphere Application Server V6 (그리고 WebSphere Application Server Enterprise V5)에서 지원하는 Last participant 지원은 싱글 one-phase commit 리소스 매니저(CICS ECI 리소스 어댑터에서의 연결)가 two-phase commit 리소스 매니저를 사용하여 글로벌 트랜잭션에 참여할 수 있도록 한다.

LPS 기능은 확장 전개 디스크립터(XDD)를 통해 EJB 컴포넌트에 대해 실행된다. WebSphere Application Server의 엔터프라이즈 애플리케이션 설정은 Last participant 지원 속성 페이지를 제공하고, 여기에는 Accept 위험 체크박스가 있다. (그림 8)


그림 8. WebSphere Application Server: Last participant 지원


WebSphere Application Server V6 트랜잭션 서비스는 one-phase commit 리소스를 실행하기 전에 추가 로그 엔트리를 작성하여 복구 동안 적절한 보고를 실행하도록 설정될 수 있다. 관리 콘솔에서 Application Servers => Server => Server properties => Transaction Service로 가서, Enable logging for heuristic reporting 체크 박스를 선택한다.

5. z/OS에서 WebSphere Application Server를 사용한다면, 트랜잭션 지원이 달라지는가?

CICS Transaction Gateway가 WebSphere Application Server for z/OS의 로컬 모드에서 사용되면, 글로벌 트랜잭션은 내부 RRS 기능을 사용하여 CICS ECI 리소스에 의해서 지원된다. 이것은 z/OS 환경에 최적화 되어있고 원격 게이트웨이를 사용할 때 two-phase commit에 필요한 XA 트랜잭션 흐름의 오버헤드가 없다.

게다가 WebSphere Application Server for z/OS는 같은 트랜잭션에서 싱글 one-phase commit 리소스와 RRS 리소스를 사용하도록 허용한다. 분산 플랫폼의 WebSphere Application Server와는 달리, LPS XDD 속성들을 지정할 필요가 없다.

WebSphere Application Server for z/OS에 CICS Transaction Gateway에서 제공되는 RRS 글로벌 트랜잭션 지원은 빈 관리 로컬 트랜잭션은 지원하지 않는 다는 점에 주목하라. 질문 1에서 설명한 것처럼, CICS ECI 커넥션 팩토리의 LocalTransaction 인터페이스를 사용하는 것은 지원되지 않는다.

6. z/OS에 CICS TG를 전개하면 어떤 점이 좋은가?

z/OS 상의 CICS TG는 EXCI를 사용하여 CICS에 고속 크로스 메모리 액세스를 제공한다. 이 메커니즘은 MRO 기반 통신 메커니즘이기 때문에, 다른 플랫폼에서는 사용할 수 없다. EXCI 프로토콜을 사용하면 MVS Resource Recovery Services (RRS)를 통해서 two-phase commit 트랜잭션 지원이 가능하다. 이는 CICS TG V6.1에서 XA 지원을 통해 사용할 수 있다.

CICS TG V6.1 for z/OS는 복제된 CICS Transaction Gateway 데몬을 통해 TCP/IP 로드 밸런싱을 지원한다. TCP/IP 포트를 공유하여 높은 쓰루풋과 고가용성을 제공한다.

7. two-phase commit 프로세싱 중에 네트워크 연결이 실패한다면 어떤 일이 발생하는가?

트랜잭션 실행 중에(커미트 프로세싱이 시작되기 전), CICS Transaction Gateway 데몬으로 TCP/IP 네트워크 연결이 실패하면, 이에 대한 공지를 받은 CICS Transaction Gateway 데몬에 의해 이 트랜잭션은 RRS에서 롤백으로 표시된다. 하지만, 커미트 프로세싱 중에 연결이 실패하면, 트랜잭션은 in-doubt 단계에서 중지되고, 데몬은 커미트를 대기하거나, 연결이 다시 이루어지면 트랜잭션 매니저(WebSphere Application Server)에서 백아웃(back-out) 응답을 기다린다.

8. one-phase commit 프로토콜이 two-phase commit 프로토콜 보다 더 이로운 상황이 있는가?

two-phase commit 프로세시가 분산 트랜잭션 지원의 조건이기는 하지만, one-phase commit 프로세스로도 충분한, 또는 더 선호되는 상황도 있다.:

CICS로 단 하나의 호출이 이루어지고, 이 트랜잭션 내에서 복구 가능한 리소스에 대한 업데이트가 없다면, 글로벌 트랜잭션을 사용할 필요가 없다. 이 경우, SYNCONRETURN 옵션을 가진 비 트랜잭션 요청이 사용되어 트랜잭션 영역이 CICS에 대한 엔트리에서 시작되고 리턴 시 종료된다.

글로벌 트랜잭션 내 모든 요청들이 싱글 CICS 시스템을 통해 이루어지면, CICS ECI 리소스 어댑터가 제공하는 one-phase commit 로컬 트랜잭션 지원으로도 충분하다. two-phase commit 작동이 필요 없다. 게다가, WebSphere Application Server의 RMLT를 사용할 때 감소한 네트워크 흐름으로 인해, XA 트랜잭션에 비교해볼 때, 로컬 트랜잭션을 사용하는 요청 퍼포먼스가 더 알맞다. 하지만, XA 프로토콜은 커미트 프로세싱 중 실패에 대한 재동기화 및 복구 로직을 제공하기 때문에 one-phase commit 시나리오에 부가적인 무결성도 제공한다.

9. 로컬 트랜잭션 리소스 어댑터(CICS ECI 리소스 어댑터)가 글로벌 트랜잭션에 추가된다면 어떤 문제가 발생하는가?

로컬 트랜잭션 리소스 어댑터가 XA 리소스 매니저와 함께 글로벌 트랜잭션에 추가된다면, EJB 컨테이너 내에서 예외가 발생한다. two-phase commit 프로세스는 one-phase commit 리소스 매니저를 사용하여 준비 단계를 완료할 수 없게 된다. EJB 컨테이너는 기존 two-phase commit 리소스에 one-phase commit 리소스를 추가하려는 잘못된 시도가 발생했다(An illegal attempt to enlist a one-phase capable resource with existing two-phase capable resources has occurred)는 메시지를 보고한다.

10. ECIRequest 클래스 또는 Common Connector Framework (CCF) 클래스를 WebSphere Application Server에서 사용한다면 어떤 지원을 받을 수 있는가?

WebSphere Application Server V5에서, ECIRequest 클래스와 CCF 클래스는 웹 컨테이너 내에서만 지원되고, 비 확장 논리적 작업 단위로 사용할 때에만 지원된다. 게다가, WebSphere Application Server에서 제공하는 JCA 관리 환경에 참여할 수 없기 때문에, RMLT나 글로벌 트랜잭션에도 참여할 수 없다. 따라서, 이러한 클래스를 사용하는 트랜잭션 요청 애플리케이션들은 신중하게 설계되어(적절한 보상 로직도 활용할 수 있도록 설계되어) 일관성 있는 결과를 낼 수 있어야 한다.

11. CICS 영역이나 트랜잭션이 갑자기 실패한다면?

XA 트랜잭션 프로토콜은 예상된 중지 프로토콜이다. 따라서, 트랜잭션이 진행 중에(커미트 프로세싱이 초기화 되기 전에) 오류가 발생하면, 모든 업데이트가 롤백된다. CICS 하위 시스템 또는 네트워크 중단도 이에 포함된다. 하지만, CICS의 ABEND는 약간 다르게 처리된다. JCA 아키텍처에서, (ABEND를 포함한) 모든 오류는 ResourceException로서 J2EE 컴포넌트로 간다. 이 예외가 잡히지 않으면, 기본적으로 CICS에 의해 수행되는 모든 작업들을 포함하여 트랜잭션이 실행되어 ABEND에 이르게 된다. CICS 트랜잭션 ABEND가 자동 롤백을 실행하도록 하고 싶다면 두 가지 방법이 있다.:

a. CICS TS V2와 이후 버전에서, EXCI 옵션 테이블 DFHXCOPT에는 새로운 옵션인 ABENDBKOUT={NO|YES}가 추가되었다. 이 옵션은 CICS 트랜잭션 ABEND가 RRS Unit of Recovery의 자동 롤백을 실행할 것인지의 여부를 설정하고 CICS 작업 단위 내에서 이루어진 모든 업데이트의 백아웃을 실행한다. 이 옵션은 CICS TS V3.1에는 APAR PK17426, CICS TS V2.2와 V2.3에서는 APAR PK17427로 도입되었다. (APAR는 EXCI에만 적용되기 때문에 CICS TG on z/OS에서만 사용된다.)

b. J2EE 애플리케이션이 ResourceException을 받으면, 이 예외가 CICS 트랜잭션 ABEND를 나타내는지, 심지어 특별한 CICS ABEND 코드가 무엇인지도 조사할 수 있다. 일단 탐지 되면, EJB를 위한 디폴트 액션이 EJB 세션 콘텍스트에서 setRollbackOnly로 마킹되어 트랜잭션 매니저가 트랜잭션을 자동으로 롤백 할 수 있도록 한다. 다음 코드 샘플을 참조하라.:

try {
eciInt.execute(eSpec, jsr, jsr);
} catch (ResourceException re) {

if (re instanceof com.ibm.connector2.cics.CICSTxnAbendException )
{
System.err.println("CICS ABEND detected."+ " Connection Factory="+targetCF.toString());
try {
mySessionCtx.setRollbackOnly();
} catch(IllegalStateException ise) {
ise.printStackTrace();
}


맺음말

 

WebSphere Application Server와 CICS에서 제공하는 트랜잭션 지원에 대해 알아보았다. CICS ECI 리소스 어댑터가 두 개의 환경들 간에 트랜잭션 조정에 어떻게 사용되는지도 배웠다. 통합의 핵심은 리소스 어댑터와 J2EE 애플리케이션 서버 간 JCA 트랜잭션 관리 콘트랙트이다. 이 콘트랙트는 다양한 요소들의 영향을 받는다. 웹 컨테이너를 사용하는지 아니면 EJB 컨테이너를 사용하는지 여부, EJB 트랜잭션 애트리뷰트, LTC 설정, WebSphere Application Server에서 XA나 RRS를 사용하는지 여부 등이 영향을 미친다.

 

기사의 원문보기

IBM WebSphere Developer Technical Journal: Exploiting the J2EE Connector Architecture

 

참고자료


WebSphere Application Server Information Centers

CICS Transaction Gateway Library

CICS Transaction Server for z/OS Library

CICS Transaction Gateway V6.1 enhancements

Integrating WebSphere Application Server and CICS using the JCA

Transactional services in WebSphere Application Server Enterprise V5

Configuring and using XA distributed transactions in WebSphere Studio

Redbook: CICS Transaction Gateway for z/OS Version 6.1



제공 : DB포탈사이트 DBguide.net

출처명 : 한국IBM

by 루크 | 2007/01/03 17:02 | S/W 공학 | 트랙백 | 덧글(0)

내 속에 빈 공간

종종 상사와 부모들이 하는 착각 중 하나가, 내가 좋은 말을 해주면 부하 직원이나 자녀가 그 말을 잘 알아듣고
변화할 것으로 가정하는 것이다. 그래서 계속 훈계를 하고 지시를 한다. 이미 차 있는 잔에 차를 계속 붓는 것이다.

직원과 혹은 자녀와의 갭을 줄여서 진정한 소통을 하려면 내 말만 할 것이 아니라, 상대방의 얘기를 들어야 하고, 그러려면
내 안에 스페이스가 있어야 한다. 내 판단, 내가 할 말을 비우고 풍부한 느낌으로 돌아오면 어떤가? 욕심을 줄이고 새롭게
상대를 받아들일 스페이스를 넉넉히 담아 돌아오는 여행이라면 또 얼마나 멋진가?
  

고현숙의 'CEO들의 특별한 여름이야기' 중에서 (이코노믹리뷰, 2006.7.12)

내 속에 '빈 공간'이 있으면 좋겠습니다. 소중한 사람이 들어오고, 지인들의 생각이 들어오고, 새로운 지식과 지혜가
들어올 수 있는 그런 공간이 있었으면 좋겠습니다.

우리는 무엇이든 허겁지겁 담아 넣기 바쁩니다. 자꾸 채워 넣어야 뒤처지지 않을 것 같고 초조함이 덜해질 것 같아섭니다.
하지만 내 마음속이, 내 머릿속이 가득 차있고 번잡하기만 해서는 오히려 더 큰 무언가를 잃어버리기 쉽습니다.

중요한 결정을 내리는 판단력, 문제의 핵심을 꿰뚫어보는 혜안, 멀리 보는 지혜는 이렇게 내 마음에 빈 공간이 있을 때
가능해집니다. 아이나 후배에게 해주는 선의의 훈계도 내 속에 빈 공간이 있어 그들의 말을 들어줄 수 있을 때 비로소 바라는
효과를 얻을 수 있습니다.

필자는 한 선승의 이야기를 전해줍니다. "도(道)가 무엇입니까"라고 물은 사람에게 그 선승은 찻잔에 차를 계속 따르라고만
시킵니다. 말로 된 설명이 아니라 넘치는 찻잔을 보며, 그 사람은 깨달았습니다. 차를 따르려면 먼저 찻잔을 비워야 한다는
것을 보며, 그는 도의 이치를 알아차린 것이지요.

"내 속엔 내가 너무도 많아... 당신의 쉴 곳 없네..." 제가 대학을 다녔던 80년대 언저리에 '시인과 촌장'의 하덕규가 만든 노래
'가시나무의 가사입니다.

다른 이들이 들어와 쉴 수 있고, 소중한 이들이 나와 진실된 커뮤니케이션을 할 수 있고, 진정한 지식과 지혜가 들어올 수
있는 내 속의 빈 공간, 그 공간을 만들기 위해 항상 마음 속의 일부를 비워두면 좋겠습니다.

by 루크 | 2006/10/10 00:06 | [우행시] | 트랙백 | 덧글(0)

[객체지향 SW 설계의 원칙] ② 사례연구, 단일 책임 원칙

[객체지향 SW 설계의 원칙] ② 사례연구, 단일 책임 원칙

최상훈 (핸디소프트), 송치형 (서울대)   2005/04/25
오래 전 질레트가 여러 분야로 사업을 다각화한 적이 있다. 자신의 경쟁력 있는 분야인 면도날, 면도기 사업을 벗어나 샴푸 등 일용품 사업에까지 뛰어든 것이다. 면도용품도 일용품이니 사업분야 간 시너지 효과가 있을 것이라는 판단이었던 듯하다.
하지만 실적은 나지 않았고 주가는 곤두박질쳤으며 결국은 많은 손실을 입은 채로 다각화한 사업분야를 정리해야만 했다. 현재는 면도용품 사업에 집중해서 세계 최고의 면도용품 회사라는 명성과 실적을 되찾게 되었다.
이처럼 많은 기업들이 위험을 분산시키고 실적을 향상시키기 위해서 다각화(diversification)를 한다. 하지만 많은 경우 다각화는 오히려 위험을 증가시키고 실적을 악화시킨다. 이를 들어 월 스트리트의 전설적인 투자가로 불리는 피터 린치(Peter Lynch)는 “다각화는 대부분 다악화(diworsfication)로 끝난다”고 충고한다. 실제 그는 자신의 포트폴리오에 다각화를 시도하는 기업은 가능한 배제하며, 경험상 이러한 결정을 후회한 적은 거의 없다고 자신 있게 말한다.
스탠포드의 짐 콜린스 교수는 피터 린치의 경험을 트럭 수십 대에 해당하는 자료를 분석한 결과로 지지해 준다. 그의 연구에 따르면 위대한 기업으로 도약한 기업들은 모두가 우직하게 한 우물을 팠다고 한다. 다음은 그가 "좋은 기업에서 위대한 기업으로"란 책에서 ‘예상치 못한 발견’이라 놀라며 서술한 내용이다.

“좋은 회사에서 위대한 회사로 도약한 기업들은 고슴도치 - ‘한 가지’만 알고 그것에 집중하는 단순하고 촌스러운 동물-에 가깝다. 비교 기업들은 여우 - 많은 것을 알지만 일관성이 결여된 꾀 많고 교활한 동물-에 가깝다.”

어떤 단일 조직이 여러 분야로 다각화하게 되면 조직 내에서 다른 목표를 추구하는 사람들 간의 이질감이 발생하고 또한 한 가지에 집중하지 못하고 힘이 흩어지기 때문에 다각화는 쉽사리 다악화로 변질된다. 소프트웨어 세계에서도 이와 비슷하게 다각화를 경계하라는 원리가 있다. 바로 하나의 클래스는 하나의 책임만을 가져야 한다는 ‘단일 책임 원칙(이하 SRP)’이다.
이번 글에서는 프로그램에서 객체가 가지는 책임이란 것이 무엇인지, 그리고 왜 객체가 단일 책임만 가지는 것이 좋은지를 살펴볼 것이다. 또한 현재 객체가 이미 여러 책임을 지니고 있는 여우 객체인 경우 나타날 수 있는 문제점(악취)과 이를 고슴도치 객체로 바꾸는 방법에 대해서도 논의해 본다.

단일 책임 원칙의 개요
다음은 국제 거래 은행에서 사용하는 ‘잔고’라는 클래스이다. 잔고 클래스는 다음과 같은 인터페이스를 갖는다. 이 클래스의 인터페이스는 직관적으로 단순하고 기능적으로 완결된 클래스일 수 있다. 하지만 만약 두 개의 서로 다른 애플리케이션이 잔고 클래스를 사용한다고 했을 때 이 두 사용자 클래스는 서로 다른 메쏘드를 이용하게 된다. 환율 조정 애플리케이션은 ‘환율 계산’ 메쏘드를 이용할 것이고 이율 관리 애플리케이션은 금액에 관한 인터페이스를 이용할 것이다.
하지만 이 각 애플리케이션이 각각 배포됐을 때 잔고 클래스는 정체성의 혼란이 생긴다. 왜냐하면 이율관리 애플리케이션에 배포될 때는 ‘환율 계산’ 메쏘드가 무용하게 되고 환율 조정 애플리케이션과 배포될 때는 금액에 관한 인터페이스가 소외되기 때문이다. 문제는 여기서 그치지 않는다. 만약 환율 계산 메쏘드의 시그니처가 변경될 경우 환율 계산을 요청하는 잔고 클래스의 다른 메쏘드들이 같이 변경되어야 한다. 간결하게 설계한 잔고 클래스는 확실히 문제를 내장하고 있었다.

<그림 1> 잔고 클래스

문제의 원흉은 무엇일까? 잔고 클래스는 사실은 두 가지 책임을 가지고 있다. 수리적 이율 연산을 담당하는 ‘환율 계산’의 책임과 금액에 관련한 처리를 담당하는 메쏘드들이 또 다른 책임이다. 또한 하나의 클래스 안에 필요에 의해 두 가지 책임이 공존할 때 서로의 의존관계는 심각하게 강결합되기 때문에 변경에 대한 충격이 전달될 수밖에 없다.

확실히 두 가지 책임을 담당해야 하는 한 클래스는 불편한 점이 많다. 지난 글에 소개한 OCP는 ‘확장’이 설계적 관전 포인트라면 단일 책임 원칙(Single Responsibility Principle : SRP)은 ‘변경’이 관전 포인트가 될 것이다. 이 ‘변경’의 거북함을 조장하는 요소는 서로 다른 ‘책임’이 혼재해 있다는데 있다.

SRP의 키워드는 책임으로 요약되는데, 그렇다면 책임이란 무엇일까? 책임이란 ‘변경을 위한 이유’이다. 만약 하나의 클래스에 변경을 위한 두 가지 이상의 이유가 있다면 그 클래스는 한 가지 이상의 책임을 갖고 있는 것이다. <그림 1>의 잔고 클래스는 변경의 내용이 두 가지로 요약된다. 금액과 환율이다. 즉, 잔고 클래스는 금액과 환율의 책임을 갖고 있다.

SRP는 하나의 클래스에 한 가지 책임을 가르치는 원칙이다. 우리는 설계 관점에서 우리가 인식하지 못하는 SRP 위반을 자주 하게 된다. 이 위반을 경계하기 위해 깊은 통찰력이 필요하지도 않다. 단지 머리에 ‘책임’이란 단어를 상기하는 습관이면 된다.

위반 사항에는 대가가 따른다. SRP를 위반할 경우 따르는 재앙은 첫 번째로 ‘왕따’가 발생한다는 것이다. 잔고 클래스가 이율 관리 애플리케이션과 배포됐을 때 확실히 ‘환율 계산’ 메쏘드는 소외된다. 즉 만약 A라는 책임과 B라는 책임을 갖고 있는 클래스가 있을 경우 A만 필요로 하는 애플리케이션은 항상 B를 들고 다녀야 한다.
문제는 여기서 그치지 않는다. 두 번째 재앙은 무관한 메쏘드에 변경이 발생할 경우 불필요한 변경 임팩트가 전달된다. 만약 ‘환율 계산’ 메쏘드가 변경됐을 경우 이율 관리 애플리케이션은 사용하지도 않는 ‘환율 계산’ 메쏘드 때문에 다시 컴파일해야 하고 리테스트해야 하며 재배포해야 한다. 이율 관리와 전혀 무관한데도 불구하고... 사실은 이 임팩트의 영향은 더 심각한데 다음의 케이스 스터디에서 살펴보겠다.

이미 구현된 소프트웨어에서 이 재앙들은 다시 (『리팩토링』에서 소개하는) 악취(bad smell)로 분류될 수 있다. 즉, 무관한 변경에 피해를 당한다든가 불필요한 요소가 따라다닐 경우 SRP를 적용해야 하는 빨간불(bad smell)로 생각해도 무방하다. 그렇다면 SRP는 어떤 구조를 제안하고 있을까?

마틴 파울러의 엔터프라이즈 패턴을 이용한 케이스 스터디
지금은 DB에 관한 여러 우수한 툴들이 제공되고 DB와 관련한 좋은 설계방식이 많이 제안되고 있다. 그래서 과거에 복잡하고 장황했던 DB 관련 코드들이 현재는 이런 툴과 기법을 통해 많이 단순화, 은닉되고 있지만 일반적으로 개발자가 간단하게 DB 관련 클래스를 설계할 때는 <그림 2>과 같은 방식을 사용한다.

<그림 2> 액티브 오브젝트 패턴

이 Person이란 클래스는 세 가지 필드를 가지고 있고 이 클래스의 행위를 처리하는 메쏘드(비즈니스 로직 메쏘드)와 DB를 접근하기 위한 CRUD(Create, Read, Update, Delete) 메쏘드를 가지고 있다. Person이란 객체가 비즈니스 로직 메쏘드에 의해 상태변화가 일어났을 경우 (필드가 CRUD 됐을 경우) 적절한 시점에서 DB에 그 변화된 값을 반영해야 하며 이때 DB 접근 메쏘드를 사용하게 된다.

이렇게 비즈니스 로직 메쏘드와 DB 처리 메쏘드를 분리하는 이유는 하나의 메쏘드에 비즈니스 로직 루틴과 DB 처리 루틴이 혼재하게 됐을 때 메쏘드의 처리 루틴의 복잡도가 2배 이상 증폭되기 때문이다. 만약 분리시키지 않았다면 자바의 경우 Connection, PreparedStatement, ResultSet 같은 JDBC 클래스들이 비즈니스 로직과 무관하게 전체 루틴 사이에 등장하게 된다. 또한 이런 루틴은 필연적이게도 루핑을 하며 ResultSet에서 레코드를 읽어 와서 어떤 처리를 한다든가 변수에 대입하는 루틴이 작성되게 된다.

하지만 이런 메쏘드는 DB 처리 루틴이 변할 때 비즈니스 코드를 변경해야 하며 그 역의 경우도 발생하게 된다. 즉 하나의 책임이 변할 때 역시 필연적이게도 다른 책임도 같이 변하게 된다. SRP 위반의 두 번째 재앙의 대표적인 사례가 이런 경우이다.
이런 경우를 이번 호의 언어로 풀이하자면 하나의 메쏘드에 두 가지의 책임(‘비즈니스 로직’과 ‘DB 로직’)이 같이 있었기 때문이며 이를 분리하여 관리하는 설계가 <그림 2>의 액티브 오브젝트 패턴이다. 액티브 오브젝트 패턴은 이렇게 DB관련 처리를 따로 메쏘드로 캡슐화하며 비즈니스 로직과 DB 메쏘드를 분리시킴으로써 깔끔한 설계와 효과적인 관리를 보장하는 장점을 갖는다.
액티브 오브젝트 패턴은 하나의 메쏘드에서 두 가지 책임을 분리시켰을 뿐이지 하나의 클래스에서 두 가지 책임을 분리시키지 못했다. 즉 <그림 2>의 Person 클래스에는 여전히 Person 객체에 대한 DB에 접근 책임과 Person의 비즈니스 로직 책임이 혼재되어 있다. 데이터 맵퍼 패턴은 이 두 책임을 분리시키는 구조를 제안하고 있다(<그림 3> 참조).

<그림 3> 데이터 멥퍼 패턴

사실 데이터 맵퍼 패턴은 우리가 흔히 DAO(Data Access Object)로 알고 있는 인터페이스로 실현화되어 익히 사용하고 있는 패턴이다. 흔히 『Core J2EE Patters』에서 소개된 ‘DAO 패턴’으로 DAO를 알게 된 자바 개발자는 DAO 개념이 J2EE에서 제안된 것으로 알고 있다. 하지만 DAO는 마이크로소프트에서 4GL 언어 아키텍처 작업 당시 객체단위 DB 접근 인터페이스로 제안한 DB 접근 객체 인터페이스다.
데이터 맵퍼는 클래스가 비즈니스 로직에 집중할 수 있도록 DB 접근 루틴을 데이터 맵퍼 클래스로 분리시킴으로써 액티브 오브젝트 패턴에서 필자가 제기했던 ‘한 지붕 두 책임’의 문제를 해결한다. 데이터 맵퍼 패턴을 따르면 객체와 데이터베이스, 맵퍼 간의 독립성이 유지되며, 객체와 DB 간에 데이터를 이동시키는 맵퍼 레이어를 제공받게 된다. 따라서 DB 테이블이 변하거나 DB 접근 루틴이 변한다 해도 Person 클래스는 변경의 충격에서 안전하다.
또한 데이터 맵퍼를 사용하면 Person 객체의 이용방식도 자연스럽게 DB 관련 부분과 Person 사용으로 분리된다. 즉 최초 DB에 저장된 Person 객체를 생성할 경우 PersonDAO에게 load를 요청해 DB에서 Person을 얻어오고, 사용자는 자연스럽게 Person 객체의 비즈니스 로직 부분만 집중하게 된다. 이 Person 객체를 변경하거나 삭제를 원할 경우 각각 PersonMapper를 통해 insert, update를 요청하여 DB 작업을 위임한다.

액티브 오브젝트 패턴의 경우 이 두 가지 책임에 관한 사용자의 작업이 명백히 분리되지 못했는데 데이터 맵퍼 패턴을 사용하므로 사용자의 Person 객체에 관한 책임을 사용하는 목적과 방법이 명확해진다(Person 객체의 인터페이스인지, DB 관련 작업인지).

이로써 액티브 오브젝트와 데이터 맵퍼 패턴으로 최초의 Person 클래스의 책임은 명확하게 분리될 수 있었다. 하지만 만약 Person 객체의 상태변화가 압도적으로 많아서 DB 접근이 빈번하게 이뤄지고 이 빈번한 DB 접근 비용으로 인해 성능장애가 올 경우를 상상해 보자. 우리는 이와 같은 경우 DB 접근 비용을 감소시키기 위해 일반적으로 맵퍼 레이어 뒤에 캐싱 레이어를 둔다. 즉 Person 클래스에게 또 하나의 ‘캐싱’이라는 책임이 더해진다.
가령 앞서 제기한 문제처럼 ‘boby’라는 Person 객체를 n번 load한다고 했을 때 기존 방식으로는 n번 DB에 SELECT해야 한다. 하지만 SELECT는 한번만으로도 족하다. 한번 load된 객체를 재사용한다면 DB 접근은 n-1번 생략할 수 있다.
문제는 이 캐시란 책임을 어디에 두느냐일 것이다. 액티브 오브젝트처럼 (같은 클래스의) 메쏘드 단위로 클래스 내에 분리시킬 것인가, 데이터 맵퍼처럼 클래스 단위로 서로 분리시킬 것인가, 아니면 더 큰 컴포넌트나 패키지로 분리할 것인가의 분리 단위 결정이 갈등요소가 된다.

여기서 설계자는 분리의 ‘크기(granularity)’를 고민한다. 작은 단위로 섬세하게 사용할 수 있는 미세단위(fine-grained)로 구분할 것인가, 아니면 단순하지만 입도가 큰(COARSE-GRAINED) 단위로 구분할 것인가에 대해서 말이다. 이 문제의 경우 애플리케이션에서 문제 영역이 각각 서로 다른 케이스 바이 케이스로 이루어지기 때문에 명백히 일관적으로 적용할 가이드라인을 제공하기 힘들다.

하지만 그 기준은 대상에 대한 복잡도, 크기, 용도가 된다. 복잡도가 높고 부피가 큰데 반해 그 용법이 단순하다면 COARSE-GRAINED가 적합하다. 역으로 복잡도가 낮고 부피가 작으며 용법이 다양하다면 fine-grained가 적합하다.

<그림 4> 식별자 맵 패턴

여하튼 이 경우 보통 식별자 맵 패턴이 사용되는데 식별자 맵은 DB를 통해 얻어온 객체를 캐시하는 맵이다(<그림 4>에서 식별자 맵은 클래스 크기로 분리하고 있다). 한번 load된 객체는 식별자 맵에 등록되고 두 번째 load 요청부터 DB에 SELECT할 필요 없이 식별자 맵에서 가져오면 된다.

높은 응집도, 낮은 결합도 
‘높은 응집도, 낮은 결합도(High Cohesion, Loose Coupling)’의 원리는 1970년대 Larry Constantine과 Edward Yourdon이 정의했던 아주 고전적인 원리이다. 이것은 현재 모든 소프트웨어 시스템 고유의 유지보수성과 적응성을 측정하는 가장 좋은 방법으로 사용되고 있다. 소프트웨어 디자인뿐만 아니라 아키텍처 평가에도 이 원리가 기준이 되는데, 그 이유는 이 원리의 적용 효과가 아주 명백하기 때문이다.

이 원리의 예외는 거의 찾아보기 힘들만큼 보편성을 가지고 있어서 마치 물리학의 엔트로피 법칙처럼 절대적인 기반원리를 제시한다. 낮은 응집도를 갖는 구조는 변경이나, 확장 단계에서 많은 비용을 지불해야 하며 높은 결합도의 경우도 마찬가지이다.

응집도는 ‘하나의 클래스가 하나의 기능(책임)을 온전히 순도 높게 담당하고 있는 정도’를 의미하며 이들은 서로 조화될수록 그 구조는 단순해진다. 응집도가 높은 동네에서 내부 개체가 변했을 때 다른 개체에 충격을 주는 것은 오히려 당연한 징후이다. 이들은 하나의 책임아래 서로 유기적인 관계를 갖고 있기 때문에 내부 개체가 변했을 때 다른 개체의 변경 확률이 높아진다. 마치 예쁜 부츠를 사면 부츠에 어울리는 치마를 입어야 하듯이… 응집도의 종류는 다양한데 다음은 권장할 만한 순기능적 응집 관계들이다.

* 기능적 응집(Functional Cohesion)
일관된 기능들이 집합된 경우를 말하며 <그림 3>의 데이터 맵퍼는 DB 처리라는 기능 항목의 높은 응집성을 갖는다.
* 순차적 응집(Sequential Cohesion)
한 클래스 내에 등장하는 하나의 소작업(메쏘드)의 결과가 다음 소작업(메쏘드)의 입력으로 사용되는 관계(파이프라인 방식의 처리 체인 관계).
* 교환적 응집(Communicational Cohesion)
동일한 입력과 출력 자료를 제공하는 메쏘드들의 집합을 말하며, 팩토리 클래스는 전형적인 교환적 응집도가 높은 인터페이스를 갖는다.
* 절차적 응집(Procedural Cohesion)
순서적으로 처리되어야 하는 소작업(메쏘드)들이 그 순서에 의해 정렬되는 응집관계
* 시간적 응집(Temporal Cohesion)
시간의 흐름에 따라 작업 순서가 정렬되는 응집관계
* 논리적 응집(Logical Cohesion)
유사한 성격의 개체들이 모여 있을 경우를 말하며 java.io 클래스들의 경우가 대표적인 예이다.

이와 반해 결합도는 ‘클래스간의 서로 다른 책임들이 얽혀 있어서 상호의존도가 높은 정도’를 의미하며 이들이 조합될수록 코드를 보기가 괴로워진다. 이유는 서로 다른 책임이 산만하고 복잡하게 얽혀있기 때문에 가독성이 떨어지고 유지보수가 곤란해지기 때문이다. 이유는 필요 없는 의존성에 있다. 마치 키보드의 자판 하나가 고장나도 키보드 전체를 바꿔야 하는 것처럼. 하나의 변경이 엄청난 민폐를 야기하는 관계이다. 다음은 수용할 수 있는 수준의 결합 관계들이다.

* 자료 결합(Data Coupling)
두 개 이상의 클래스가 매개변수에 의해서 결합 관계를 가지므로 낮은 수준의 결합도로 연관되는 경우
* 스탬프 결합(Stamp Coupling)
자료 결합의 경우에서 매개변수 일부만을 사용하는 경우
* 제어 결합 (Control Coupling)
두 클래스간의 제어 이동이 매개변수를 이용하여 사용되는 경우로 커맨드 패턴이 대표적인 사례이다(지난 연재 기사 OCP 참조).

즉 순서는 ‘1. 우선 key에 해당하는 객체를 식별자 맵에서 찾는다( 1.1. 없다면 DB에서 얻어온다. 1.2. 식별자 맵에 등록한다). 2. 리턴한다’'의 절차를 밟는다. 마치 싱글톤 패턴처럼 한 번의 접근을 보장함으로써 DB 접근 비용을 감소시킨다.
이와 더불어 실제 엔터프라이즈 애플리케이션 설계 시 데이터 접근의 동기화, 동시성 처리, 풀링, O/R 맵핑 등의 ‘책임’들이 등장하게 되는데 이 각 책임들을 담당하는 클래스를 분리시키고 이들 간의 관계를 잘 정의할수록 복잡도는 감소하고 아키텍처는 깨끗해진다.
현재 제공되고 있는 DB 관련 프레임워크는 이 책임들을 맡는 기능을 구현한 녀석들이다. SRP의 효과가 바로 여기에 있다. SRP를 적용하면 무엇보다도 책임 영역이 확실해지기 때문에 한 책임의 변경에서 다른 책임의 변경으로의 연쇄작용에서 자유로울 수 있다.
하지만 무조건 책임을 분리한다고 SRP가 적용되는 건 아니다. 가령 데이터 맵퍼 클래스의 메쏘드들이 각각의 insert, delete, update, load 클래스로 분리됐을 경우를 생각해 보자. 마치 절차적 언어에서와 같은 함수 단위의 클래스가 될 것이다. 각 메쏘드 역할에 따른 책임들이 분리되었지만 설계는 장황해지고 관계는 복잡해진다. 하지만 이 문장은 틀린 문장이다. 동일한 책임을 갖는 여러 메쏘드들이 분리된 것이다. 즉 분리의 기준은 책임이며 분리의 목적은 복잡도 감소에 있다.

반면 각각 분리된 insert, delete, update, load 클래스들이 있다고 했을 때 이들은 한 책임아래 병합되어야 할 것이다. 왜냐하면 이들은 병합될수록 관계는 단순해지고 설계가 일목요연해지기 때문이다. 그렇다면 이 서로 상반된 관계, 즉 어떤 경우는 분리를 할수록 설계가 깨끗해지고, 반대로 어떤 경우는 병합을 할수록 설계가 깨끗해지는데 이 차이는 무엇일까?

각 개체 간의 응집력이 있다면 병합이 순작용의 수단이 되고 결합력이 있다면 분리가 순작용의 수단이 된다. 응집력이 있다는 것은 여러 개체가 같은 책임아래 있다는 것을 의미하며 결합력이 있다는 것은 한 개체가 여러 책임을 갖고 있다는 의미가 된다. 따라서 응집도는 높을수록, 결합도는 낮을수록 좋은 설계 품질을 보장받는다.

SRP 위반의 악취들
SRP는 하나의 객체가 하나의 책임, 즉 하나의 ‘변경의 이유’만 지니게 함으로써 설계를 단순하게 하고 변화에 기민하게 만들어 준다. 필자는 서두에 SRP 위반했을 때 재앙을 악취로도 소개했는데 이번에 소개할 악취들은 『리팩토링』에서 구체화된 악취들이다. SRP 위반의 악취는 ‘여러 원인에 의한 변경(divergent change)’와 ‘산탄총 수술(shotgun surgery)’을 들 수 있다.

여러 원인에 의한 변경
여러 원인에 의한 변경은 한 클래스를 여러 가지 다른 이유로 고칠 필요가 있을 때 발생한다. 즉, 하나의 클래스에 여러 책임이 혼재하고 있어서 하나의 책임의 변화가 다른 책임에게 영향을 준다. 그리고 이 책임이 두 개보다 훨씬 많은 여러 개로 혼재된다면 이 클래스는 심각한 고문관이 된다. 더욱이 이 구조는 더 괴로운 경우로 심화될 수 있다.

<그림 1>의 ‘잔고 클래스’에서 ‘환율 계산’이란 책임의 변화로 다른 금액 관련 책임들의 인터페이스까지 칼을 대야 할 상황에 이른다면 작은 ‘환율 계산’의 변경이 전혀 무관한 금액 관련 인터페이스를 이용하는 모든 사용자 클래스까지 변경해줘야 하는 것이다. 이런 위험 상황을 소개한 것이 ‘여러 원인에 의한 변경’이라는 나쁜 냄새이다.

‘여러 원인에 의한 변경’을 해결하는 방법은 경우에 따라 다르다. 다음은 여러 원인에 의한 변경을 해결하는 리팩토링 기법이다.

◆ Extract Class는 혼재된 각 책임을 각각의 개별 클래스로 분할하여 클래스 당 하나의 책임만을 맡도록 하는 것이다. 액티브 오브젝트 패턴에서 데이터 맵퍼 패턴으로의 진화가 대표적인 사례가 된다. 여기서 관건은 책임만 분리하는 것이 아니라 분리된 두 클래스간의 관계의 복잡도를 줄이도록 설계하는 것이다.

◆ 만약 Extract Class된 각각의 클래스들이 유사하고 비슷한 책임을 중복해서 갖고 있다면 Extract Superclass를 사용할 수 있다. Extract Class된 각각의 클래스들의 공유되는 요소를 부모 클래스로 정의하여 부모 클래스에 위임하는 기법이다. 따라서 각각의 Extract Class들의 유사한 책임들은 부모에게 명백히 위임하고 다른 책임들은 각자에게 정의할 수 있다.


여러 원인에 의한 변경 
윤성준, 조재박 님이 번역하고 대청미디어에서 출간된 마틴 파울러의『리팩토링』 한국어판을 보면 여러 원인에 의한 변경(divergent change)이 ‘확산적 변경’이라 되어 있다. 하지만 확산적 변경이라 했을 때는 변경이 다른 클래스로 확산된다는 느낌이 강하고, 클래스가 여러 변경 원인에 의해 변경된다는 느낌이 언뜻 오지 않는다.

그래서 이번 글에서는 divergent를 수렴(convergent)에 대응하는 의미가 아닌 ‘다른’의 뜻으로 세기고 divergent change를 ‘여러 원인에 의한 변경’이라 하겠다. They hold divergent opinions on controversial issues like abortion(그들은 낙태와 같은 논란이 되는 이슈에 대해 서로 다른 의견을 주장했다)에서의 divergent 용례를 참고하면 될 듯하다. 흔히들 ‘확산적 변경’과 뒤에 소개할 ‘산탄총 수술’을 잘 구분하지 못하는데 ‘확산적 변경’을 ‘여러 원인에 의한 변경’으로 바꾸어 이해하면 이 둘의 구분이 조금 더 직관적으로 다가올 것이다.

산탄총 수술
산탄총을 발사하면 하나의 탄환이 부서지면서 여러 개의 탄환으로 확산되어 발사된다. 따라서 (상상하기도 싫지만) 산탄총을 맞은 대상의 총상은 온몸 전체에 퍼지게 된다. 만약 이런 환자를 수술하는 의사는 마치 수십 발의 총을 맞은 환자를 수술하는 것처럼 힘들 것이다.

‘산탄총 수술(shotgun surgery)’은 ‘여러 원인에 의한 변경’과 비슷한 듯 하면서도 정 반대의 내용을 갖는다. ‘여러 원인에 의한 변경’이 하나의 클래스가 여러 변경 원인(책임)을 지니는 반면, 산탄총 수술은 어떤 변경이 있을 때 여러 클래스를 수정해야 하는 증상이다. 즉 어떤 변경의 대상이 여러 곳에 분포되어 마치 산탄총 총상 환자를 수술해야 하는 것 같은 많은 노동비용이 따른다.

‘산탄총 수술’이 괴로운 이유는 단지 수술 부위가 많다는 것만이 아니다. 이 수술을 했음에도 불구하고 혹시 치료하지 못한 상처가 존재할 수 있다는 가능성이 ‘산탄총 수술’의 더 큰 위험성이다. 가령 하나의 테이블을 조작하는 DB 처리문이 애플리케이션 전역에 퍼져 있는 상황에서 DB 테이블의 구조가 바뀌게 됐을 경우에 발생하는 재앙과 같다. 수술도 고되지만 모든 환부를 찾아야 하는 집중력과 긴장감이 개발자를 더욱 힘들게 한다.

<그림 5> 산탄총 수술

산탄총 수술이란 악취는 하나의 책임이 여러 클래스에 분산되어 있기 때문에 발생한다. 한 클래스가 너무 많은 책임을 맡고 있어도 곤란하지만, 책임을 식별하지 못해 이를 담당할 클래스를 만들지 않고 여러 클래스에 흩뿌려 놓는 것 또한 문제가 있다. 이는 보통 프로그램의 전체 책임을 올바로 분담하지 못해서 발생하게 된다.
이 악취는 Move Field와 Move Method를 통해 책임을 기존의 어떤 클래스로 모으거나, 이럴만한 클래스가 없다면 새로운 클래스를 만들어 해결할 수 있다. 즉 산발적으로 여러 곳에 분포된 책임들을 한 곳에 모으면서 설계를 깨끗하게 한다. 즉 응집성을 높이는 작업이 필요하다.
산탄총 수술의 냄새는 특히 설정 정보(configuration information), 로깅(logging), DB 처리에서 발생하기 쉬운데 이들을 다룰 때는 항상 산탄총 수술의 악취를 경계해야 한다. 예를 들어 한 곳에서 관리할 필요가 있는 설정 정보를 여러 클래스에서 나누어 처리하고 있다면 이는 산탄총 수술을 할 수 있는 좋은 본보기가 된다.
이를테면 쓰레드, 커넥션, 오브젝트 풀의 크기 값이나 DB, 서버의 주소 정보들을 각각의 클래스에 자체적으로 관리하고 있다면 이들을 설정 파일이나 설정 관리자에게 Move Field하는 것이 바람직하다. 더 나아가 플러그인을 도입해 설정 정보를 통해 동적으로 행위 변화를 통제(Enable Configurable Behavior with Plugin)하는 것도 생각해 볼만하다. 또한 XML 처리나 프로토콜 해석을 담당하는 메쏘드가 여러 곳에 분포되었다면 각각의 유틸성 클래스로 Move Method하는 것이 바람직하다.

‘여러 원인에 의한 변경’과 ‘산탄총 수술’이란 악취를 SRP를 어긴 신호로 여기고 제거한다면 변경이 여러 곳으로 확산되지 않을 뿐 아니라 책임을 적절히 분배함으로 인해 코드의 가독성 향상, 유지보수 용이라는 이점까지 누릴 수 있다. 또한 적절한 책임 분배는 객체지향 원리들의 대전제 격인 OCP뿐 아니라 다른 원리들을 적용하는 기초가 되어준다.

핵심 리팩토링 기법과 책임 분배 
리팩토링에서 소개하는 대부분의 냄새들(코멘트, 비대한 클래스에서 시작해 산탄총 수술까지)은 객체간의 책임 분배와 직간접적으로 관련이 있는데, 항상 코드를 최상으로 유지한다는 리팩토링의 근본 정신은 곧 항상 객체들의 책임을 최상의 상태로 분배한다는 것이기 때문이다. 그리고 다음의 리팩토링 기법들은 ‘객체들의 책임을 최상의 상태로 분배’하는 가장 기본이 된다.

Extract Class(6)

Move Method(6)

Extract Method(4)

Move Field(4)

이들은 가장 많은 냄새를 해결하는 리팩토링 기법들을 정리한 것으로 괄호 안의 숫자는 냄새를 해결하는 데 사용된 빈도수이다. 고급스럽고 우아한 고급 리팩토링 기법이나 빅 리팩토링 기법은 때때로 유용하지만, 이들은 항상 유용하다.

필자는 앞의 4개의 리스트에 ‘Rename *’를 추가하여 5개의 리팩토링 기법을 가장 단순하면서도 중요한 핵심 리팩토링 기법으로 여긴다. 파울러의 책에는 Rename Method만이 수록되어 있지만, Rename Class, Rename Field, Rename Parameter 역시 굉장히 자주 쓰이며 실제 이클립스의 리팩토링 기능을 보면 이 모두가 하나로 Rename이라 되어 있다. 그리고 경험에 비추어 보면 어느 리팩토링 기능보다도 Rename을 자주 사용하게 된다. 올바른 클래스 이름은 해당 클래스의 책임을 나타낼 수 있는 가장 좋은 방법이다.

코딩을 하면서 키보드 앞에 A4 용지를 놓고 수시로 CRC 카드, 클래스 다이어그램, 시퀀스 다이어그램을 간략히 그려보며 객체들 간의 책임을 점검해 보고, 리팩토링을 수행하는 것은 분명 좋은 SRP를 어기는 코드를 사전에 어느 정도 방지할 수 있는 좋은 대비책이 되어줄 것이다. 할 수만 있다면, ‘사후 대처’보다는 ‘사전 예방’이 더 좋은 해결책이지 않은가?

위대함보다 단순함은 없다
눈동자의 초점이 분산되면 난시가 되고 마음이나 정신의 초점이 정상을 상실하고 분산하는 상태가 지속되면 정신착란 혹은 정신 분열이 온다. 마찬가지로 클래스가 하나의 책임에 집중하지 못한 채 이것저것 두리번거리는 여우와 같고, 이러한 클래스들이 누적된다면 프로그램은 점점 유지보수, 변경에 대응하기 어려워진다. 어쩌면 ‘툭’하고 잘못 건드리면 정신착란이 올지도 모를 일이다.
소프트웨어는 항상 변경을 전제한다. 따라서 변경에 민감하게 반응하지 못하는 설계는 프로젝트를 힘들게 한다. “소프트웨어 설계에는 두 가지 방법이 있다. 한 가지는 분명히 결함이 없도록 단순하게 설계하는 것이고 다른 한 가지는 분명한 결함이 없도록 가능하면 복잡하게 설계하는 것이다"라는 명언이 있다.

SRP를 적용하면 클래스의 숫자가 늘 수는 있다. 하지만 클래스 숫자의 증가가 프로그램의 복잡도 증가와 비례하는 것은 아니다. 오히려 SRP를 잘 따르는 프로그램은 적절한 책임 분배로 인해 클래스 숫자와 프로그램의 복잡도가 반비례하는 경향이 있다고도 할 수 있게 된다.
위대함보다 단순함은 없다. 실제로 단순한 것이 위대한 것이다. 그리고 이 단순함의 중심에는 단일 책임의 원칙이 있다.

* 이 기사는 ZDNet Korea의 제휴매체인 마이크로소프트웨어에 게재된 내용입니다.

by 루크 | 2006/10/06 20:41 | S/W 공학 | 트랙백 | 덧글(0)


◀ 이전 페이지          다음 페이지 ▶