주요글: 도커 시작하기
반응형
웹 어플리케이션의 비즈니스 로직 처리와 관련된 도메인 로직 패턴인 트랜잭션 스크립트와 도메인 모델에 대해서 살펴본다.

트랜잭션 스크립트

본 글에서 소개하는 패턴은 '엔터프라이즈 애플리케이션 아키텍처 패턴' (마틴 파울러 저)에 소개된 패턴으로서, 필자는 파울러가 소개한 패턴을 보다 쉽게 이해할 수 있도록 재구성한 것이다.

은행의 계좌 이체 서비스를 생각해보면 '잔고 확인->받는 사람 확인->이체 실행->잔고 감소'의 로직 순서가 하나의 로직으로 처리되어야 하고, 이 과정에서 한번이라도 오류가 발생하면 모든 처리가 취소되고, 모든 과정이 성공해야 비로서 처리가 완료된다. 즉, All-or-Nothing 개념의 트랜잭션 처리가 되는 것이다.

트랜잭션 스크립트(Transaction Script) 패턴은 이렇게 하나의 트랜잭션으로 구성된 로직을 단일 함수 또는 단일 스크립트에서 처리하는 구조를 갖는다. 그래서 패턴의 이름이 트랜잭션 스크립트이다. 트랜잭션 스크립트는 JSP를 처음 공부하는 사람들이 가장 먼저 몸에 습득하는 패턴으로서 모델 1 구조가 가장 간단한 트랜잭션 스크립트 패턴에 해당한다.

트랜잭션 스크립트의 구현 방법

트랜잭션 스크립트의 가장 손쉬운 구현방법은 하나의 트랜잭션 처리를 하나의 JSP 코드에서 처리하는 것이다. 예를 들어, 트랜잭션 스크립트 패턴으로 계좌 이체 처리를 하는 JSP를 작성하면 다음과 같은 형태의 코드를 갖게 될 것이다.

    <%
        ...
        try {
        
            SomeTransaction tx = ...;
            tx.begin();
            
            // 1. 잔고확인
            ...
            // 2. 받는 사람 확인
            ...
            // 3. 이체 실행
            ...
            // 4. 잔고 감소
            ...
            tx.commit();
            
        } catch(..) {
            tx.rollback();
            ...
        } finally {
            ...
            ...
        }
    %>

JSP 페이지에 뷰 코드와 로직 코드를 함께 섞는 것보다는 커맨드 패턴(자바캔 컨텐츠 '커맨드(Command) 패턴과 그 구현' 참고)을 사용해서 JSP로부터 로직 코드를 커맨드 처리 클래스로 이동시키기도 한다. 예를 들어, 다음과 같이 하나의 트랜잭션을 처리하는 메소드를 생성하고 JSP에서 이 메소드를 실행하는 형태를 취하면 된다.

    public class SomeTransactionScript {
        public Result do(...) {
            try {
            
                SomeTransaction tx = ...;
                tx.begin();
                
                // 1. 잔고확인
                ...
                // 2. 받는 사람 확인
                ...
                // 3. 이체 실행
                ...
                // 4. 잔고 감소
                ...
                tx.commit();
                
            } catch(..) {
                tx.rollback();
                ...
            } finally {
                ...
                ...
            }
        }
    }

아마도 위와 같이 트랜잭션 스크립트 코드를 클래스로 옮기게 되면 JSP에서는 다음과 같이 클래스의 인스턴스를 생성해서 트랜잭션을 처리하는 메소드를 호출하는 형태의 코드를 사용하게 될 것이다.

    <%
        SomeTransactionScript ts = new SomeTransactionScript();
        Result result = ts.do(...);
    %>
    ...

스트러츠와 같이 모델 부분을 커맨드 패턴으로 처리해주는 프레임워크를 사용하면 보다 표준화된 방법으로 JSP 페이지로부터 로직 처리 코드를 클래스로 옮길 수 있다. 스트러츠는 모델 2 구조를 기반으로 커맨드 패턴을 사용하는데, 모델 2 구조를 기반으로 커맨드 패턴을 구현하는 방법에 대한 기초적인 내용은 'JSP Model 2 Architecture 2부, 커맨드 패턴의 적용 '에서 설명하고 있으니 참고하기 바란다.

트랜잭션 스크립트의 장/단점

트랜잭션 스크립트 방식의 최대 장점은 구현이 매우 쉽다는 것이다. ASP.NET이나 JSP 등이 프로그래밍 언어에 기반하고 있는 스크립트 언어임에도 불구하고 사람들은 ASP나 JSP가 프로그래밍 언어보다 쉽다고 느끼는데, 그 이유는 트랜잭션 스크립트 방식의 구현 방법의 단순함 때문이다. 스트러츠와 같이 MVC 패턴과 커맨드 패턴을 함께 사용하더라도 이러한 단순함은 그대로 유지되기 때문에, 스트러츠 코드에 조금만 익숙해지면 손쉽게 스트러츠로도 트랜잭션 스크립트 패턴을 구현할 수 있게 된다.

하지만, 트랜잭션 스크립트로 구성된 어플리케이션은 비즈니스 로직이 복잡해질수록 난잡한 코드를 만들게 된다. 특히, 트랜잭션 스크립트 코드는 애초에 도메인에 대한 분석/설계 개념이 약하기 때문에 코드의 중복 발생을 막기 어려워진다. 또한, 트랜잭션 스크립트를 사용하게 되면 쉬운 개발에 익숙해지기 때문에 공통된 코드를 공통 모듈로 분리하지 않고 복사&붙이기 방식으로 중복된 코드를 만드는 유혹에 빠지기 쉽다.

요즘은 트랜잭션 스크립트 방식의 단점만 많이 부각되고 장점은 부각되지 않는 경우가 많은데, 이는 잘못된 현상이라 생각한다. 트랜잭션 스크립트 방식의 최대 장점이라면, 트랜잭션 스크립트 패턴이 이미 많은 개발자들의 몸에 익은 (최상은 아니지만) 최적의 개발 방법중의 하나라는 것이다. 또한, 얼마나 모듈화를 잘 하느냐에 따라서 트랜잭션 스크립트 만으로도 높은 효율을 낼 수 있기 때문에 트랜잭션 스크립트 패턴은 결코 무시할 수 없는 패턴이다.

도메인 모델

도메인 모델(Domain Model)은 흔히 말하는 객체 지향 분석 설계에 기반해서 구현하고자 하는 도메인(비즈니스 영역)의 모델을 생성하는 패턴이다. 도메인 모델은 비즈니스 영역에서 사용되는 객체를 판별하고, 객체가 제공해야 할 목록을 추출하며, 각 객체간의 관계를 정립하는 과정을 거친다. 명사와 동사를 구분해서 명사로부터 객체를 추출해내고, 동사로부터 객체의 기능 및 객체 사이의 관계를 유추해낸다.

객체를 기반으로 하는 도메인 모델의 주요 특징은 데이터와 프로세스가 혼합되어 있다는 것이며, 객체 간의 복잡한 연관 관계를 갖고 있고, 상속 등을 통해서 객체의 기능과 역할을 확장할 수 있다.

현업에서는 보통 두 가지 형태의 도메인 모델이 사용된다. 첫번째는 단순한 형태로서 데이터베이스 테이블 하나당 하나의 도메인 객체를 가지는 구조를 갖는 방식이다. 이런 모델에서는 하나의 테이블에 해당하는 하나의 자바빈 객체가 존재하는 것이 보통이며, 자바빈 객체에 프로세스가 함께 포함되기도 하고 프로세스를 처리하는 객체를 따로 생성하기도 한다. EJB의 엔티티 빈은 데이터베이스트의 테이블과 1대 1로 매핑되며 세션빈은 프로세스를 처리하기 때문에 단순한 형태의 모델에 가장 알맞은 구현 방법이라고 볼 수 있다.

현업에서 사용되는 도메인 모델의 두번째 형태는 상속, 다양한 패턴, 그리고 각각의 객체가 서로 연결된 복잡한 그물망을 갖는 구조이다. 단순 도메인 모델은 데이터베이스 모델링과 비슷한 구조의 객체가 사용되지만, 풍부한 도메인 모델은 '객체지향분석설계' 관련 서적에서 나오는 방식에 의해 객체를 판별하고 객체 사이의 관계를 유추하는 등의 작업을 통해서 객체를 판별하기 때문에 데이터베이스와의 매핑이 쉽지많은 않다. 따라서 풍부한 도메인 모델을 사용하는 경우에는 도메인 모델과 데이터베이스 사이의 매핑을 처리할 수 있는 보조 유틸리티를 사용하는 것이 좋다.

모데인 모델의 구현 방법

도메인 모델을 사용할 때 최대 관건은 도메인 모델과 데이터베이스 테이블 사이의 매핑을 어떻게 처리할 것인가 하는 것이다. 웹 어플리케이션의 (대부분의) 비즈니스 로직은 데이터 삽입, 데이터 조회, 데이터 변경 그리고 데이터 삭제와 관련되어 있기 때문에, 비즈니스 로직을 자바 객체로 표현한 도메인 모델에 알맞은 데이터베이스 매핑 패턴을 사용하는 것이 좋다. (데이터 매퍼를 사용함으로써 데이터베이스로부터 도메인 모델을 독립적으로 유지할 수 있으며, 유지보수도 간단하게 만들 수 있다.)

도메인 모델을 구현하기 위해서는 먼저 객체 지향 개념에 기반한 클래스 설계 기법을 몸에 익혀야 한다. 요즘 나오는 책들은 UML을 기반으로 설명하고 있지만, 꼭 UML 기반이 아니어도 상관없다. 객체 지향 개념을 익히고 싶다면 다음의 책들을 참고하기 바란다.

객체 지향 설계와 더불어 익혀야 할 것은 도메인 모델과 테이블 사이의 매핑을 처리해주는 기법이다. 이 매핑 처리 기법은 '단일객체-단일테이블'의 1-1 매핑에서 '다수객체-다수테이블'의 n-n 매핑까지 다양하게 존재하며, 또한 데이터베이스 매핑을 지원해주는 API도 상업용에서 오픈소스까지 다양하게 존재한다. 오브젝트와 데이터베이스 사이의 매핑에 대한 정보는 아래의 사이트에서 구할 수 있을 것이다.

아직까지 널리 사용되는 도메인 모델은 EJB의 엔티티빈과 세션빈과 같은 단순 도메인 모델이다. 즉, 테이블과 1대 1로 매핑되는 엔티티가 존재하고 엔티티를 사용해서 로직을 수행하는 세션이 존재하는 단순 도메인 모델이 대세를 이루고 있다. 특히 EJB의 경우 CMP와 EJB QL(Query Language)을 통해서 데이터베이스 매핑을 처리할 수 있기 때문에 도메인 모델의 비즈니스 로직에 집중할 수 있도록 도와주는 장점이 있다.

도메인 모델의 장/단점

도메인 모델의 장점은 역시 객체 지향에 기반한 재사용성, 확장성, 그리고 유지 보수의 편리함에 있다. 일단 도메인 모델을 구축하고나면 (필요에 따라 약간의 수정이 필요하겠지만) 언제든지 재사용할 수 있다. 예를 들어, 한번 커뮤니티(클럽, 게시판 등) 도메인 모델을 구축하고 나면 비슷한 커뮤니티 시스템에 대해서 도메인 모델을 재사용할 수 있게 된다. 또한, 상속/인터페이스, 더 나아가 컴포넌트 개념을 바탕으로 도메인 모델을 개발하게 되면 무한한 확장성을 갖게 된다.

도메인 모델의 단점은 하나의 도메인 모델을 구축하는 데 많은 노력이 필요하다는 것이다. 객체를 판별해내야 하고 객체들 간의 관계를 정립해야 하며, 더 나아가 객체와 데이터베이스 사이의 매핑에 대해서 고민해야 하기 때문이다. 이는 이론과 경험을 함께 겸비하지 않으면 쉽게 풀수 없는 문제이기 때문에, 도메인 모델에 능숙한 개발자가 팀에 없을 경우 도메인 모델을 구축하는 것 자체가 힘들어질 수도 있다.

따라서 사용 기간이 1-6개월에 해당하는 어플리케이션이나 단순한 비즈니스 로직만 필요한 경우에는 도메인 모델을 사용하는 것이 오히려 개발 일정에 부담을 줄 수 있으므로, 프로젝트의 규모, 비즈니스 로직의 복잡함, 개발할 어플리케이션의 사용 기간 등을 고려해서 도메인 모델의 사용여부를 결정하는 것이 좋다.

도메인 모델의 예

트랜잭션 스크립트 방식과 도메인 모델의 방식을 비교하기 위해 게시판 글 쓰기 예를 사용해보도록 하겠다. 요즘 인터넷 게시판은 글을 쓰거나 내 글에 답글이 달렸을 경우 이를 이메일을 통해서 통지하는 기능을 제공하고 있다. 트랜잭션 스크립트 방식을 사용하게 되면 다음과 같은 형태로 JSP 코드를 작성할 것이다.

    <%
        ...
        try {
        
            SomeTransaction tx = ...;
            tx.begin();
            ...
            DB에 데이터 기록
            ...
            tx.commit();
            
            // 성공하면 이메일 발송
            ...
            
        } catch(..) {
            tx.rollback();
            ...
        } finally {
            ...
            ...
        }
    %>

만약 글을 쓸 때마다 게시판 목록을 미리 캐싱해두는 작업을 수행해야 한다면 다음과 같이 위 코드에 캐싱 처리 코드가 추가될 것이다.

    <%
        ...
        try {
        
            SomeTransaction tx = ...;
            tx.begin();
            ...
            DB에 데이터 기록
            ...
            tx.commit();
            
            // 성공하면 이메일 발송
            ...
            // 캐시 생성 코드
            ...
        } catch(..) {
            tx.rollback();
            ...
        } finally {
            ...
            ...
        }
    %>

도메인 모델을 사용할 경우에는 트랜잭션 스크립트 방식과 달리 아래 그림과 같은 도메인 관련 객체를 분석하고 관계를 맺어주게 된다.

[도메인 모델 예]

위 클래스 다이어그램은 게시판에 글을 쓸 경우 이벤트가 발생하여 등록된 이벤트 리스너가 실행되는 모델을 보여주고 있다. 이 도메인 모델을 사용하게 되면 먼저 다음과 같이 이벤트를 처리할 리스너를 등록하게 된다.

    Board someBoard = ...;
    someBoard.registerBoardListener(new EmailNoticer());
    someBoard.registerBoardListener(new BoardCacheMaker());

이후 게시판 글쓰기를 처리하는 코드에서는 다음과 같이 글쓰기만 처리하면 된다.

    <%
        Board someBoard = ...;
        BoardRow row = ...;
        someBoard.write(row);
    %>

글쓰기 과정에서 새로운 처리가 추가되는 경우 BoardListener 인터페이스를 구현한 이벤트 리스너 클래스를 작성한 뒤 registerBoardListener() 메소드를 사용해서 이벤트 리스너로 등록시켜주면 된다. 트랜잭션 스크립트 방식이었다면 계속해서 글쓰기 부분에 코드가 추가되었을 것이다. 만약 글삭제/글수정할 때에도 똑같은 형태로 메일을 발송하고 캐시를 생성해야 하는 경우, 트랜잭션 스크립트 방식에서는 소스 코드를 그대로 복사하는 방법을 주로 택하지만(그래서 로직 처 코드마다 방식이 달라질 수도 있지만), 도메인 모델에서는 Board 클래스만 변경해주면 되므로 표준화된 처리 방식을 그대로 유지할 수 있게 된다.

이런 유지보수의 장점 뿐만 아니라 도메인 모델을 조금만 변경하면 다른 게시판 시스템에서 사용할 수 있기 때문에, 재사용을 통한 빠른 개발도 이끌어낼 수 있다.

도메인 모델에서 엔티티를 표현하는 객체는 고유성을 갖는다!

도메인 모델에는 하나의 개체(Entity)를 하나의 객체로 표현한다. 예를 들어, EJB에서 하나의 엔티티 빈 객체는 테이블 상의 하나의 행을 나타낸다. 또한, 서로 다른 엔티티 빈 객체는 테이블의 서로 다른 행에 해당한다. 즉, 각각의 엔티티 빈은 서로 다른 개체를 나타내는 고유 객체인 것이다.

관련링크:

+ Recent posts