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

테이블 모듈

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

테이블 모듈(Table Module)
은 형태는 앞서 '도메인 로직 패턴 1'에서 살펴봤던 도메인 모델 패턴과 비슷하지만 결정적인 차이점이 존재한다. 그것은 테이블 모듈에서 사용되는 객체는 고유성이 존재하지 않는다는 점이다. 도메인 모델을 EJB로 구현하든 독자적인 형태로 구현하든 각각의 객체는 그 객체만의 고유성을 가지며, 다른 객체와 다른 객체로 인식된다. 하지만, 테이블 모듈에서는 고유 객체라는 개념은 존재하지 않으며 오직 데이터와 관련된 비즈니스 로직을 다루는 객체만 존재한다.

예를 들어, 회원과 관련된 비즈니스 로직을 도메인 모델과 테이블 모듈로 구현한다고 할 경우 다음과 같은 차이를 보이게 된다.

그림 1 - 좌측-도메인모델, 우측-테이블모듈

먼저 좌측의 도메인 모델을 사용할 경우의 코드를 살펴보자. 도메인 모델을 사용하는 경우 회원 가입 처리가 다음과 같은 코드를 통해서 이루어질 것이다.

    Member member = new Member();
    member.setId(id);
    member.setName(name);
    member.regist();

모메인 모델에서는 하나의 객체가 고유한 값을 갖고 있다. 즉, 도메인 모델에 기반한 Member 객체는 그 자체가 고유의 회원 정보를 나타내는 객체이며 regist() 메소드는 오직 고유 정보를 갖고 있는 회원에 대한 처리를 해준다. 회원 탈퇴를 처리하는 코드는 다음과 같은 형태를 띄게 될 것이다.

    Member member = someFindMemberMethod(memberId);
    member.secede();

도메인 모델은 개체(entity)의 고유성에 기반해서 객체를 생성하고 삭제하는 작업을 처리하게 되는데, 테이블 모듈은 생성된 데이터 객체가 고유성을 갖고 있지 않으며 [그림 1]에서와 같이 범용적인 클래스((RecordSet 클래스)에 데이터를 저장한다. 테이블 모듈에서 회원 등록 및 탈퇴 처리는 다음과 같은 코드를 통해서 이루어진다.

    RecordSet newData = new RecordSet();
    newData.set("id", id);
    newData.set("name", name);
    
    MemberTableModule tableModule = MemberTableModule.getInstance();
    tableModule.regist(newData);
    
    tableModule.secede(someId);

위 코드에서 중요한 건 RecordSet은 단지 데이터만 저장하고 있을 뿐 고유성을 갖지는 않는다는 점이다. 즉, 앞서 도메인 모델에 기반한 Member 클래스의 객체는 테이블 상의 하나의 행과 1대 1로 매핑되며, 서로 다른 Member 객체는 서로 다른 테이블의 행과 매핑되는 것이 도메인 모델의 기본 형태인 반면에, 테이블 모듈에서는 서로 다른 RecordSet 객체가 같은 행을 나타낼 수도 있게 된다.

또한, MemberTableModule 클래스는 오직 비즈니스 로직만을 처리한다. 고유성 문제는 객체에 저장되어 있는 특정 필드의 값이나(예를 들어 RecordSet의 "id" 프로퍼티 값) 메소드의 인자로 객체를 판별하는 고유값을 전달받음으로써(MemberTableModule 클래스의 secede() 메소드) 처리한다.

테이블 모듈의 구현 방법

테이블 모듈은 이름에서 알 수 있듯이 테이블 지향적인 구현 패턴이다. 테이블 모듈에 해당하는 클래스는 보통 한개의 인스턴스만 존재하는 싱글톤 패턴을 사용해서 구현하거나 또는 모든 비즈니스 로직 처리 메소드를 static으로 구현한다. (싱글톤 패턴의 구현 방법은 자바캔 기사인 'Singleton 패턴을 적용한 매니저 클래스와 견고한 웹 어플리케이션의 개발'을 참고하기 바란다.) 예를 들어, 싱글톤 패턴을 사용하여 MemberTableModule 클래스를 다음과 같이 구현할 수 있을 것이다.

    public class MemberTableModule {
        private MemberTableModule singleInstance = new MemberTableModule();
        public static MemberTableModule getInstance() {
            return singleInstance;
        }
        
        public void regist(RecoredSet data) {
            // RecordSet으로부터 데이터 읽어와 DB에 저장
            ...
        }
        ...
    }

도메인 모델이 엔티티 객체와 데이터베이스 사이의 매핑을 쉽게 처리하기 위해 데이터베이스 매퍼를 사용하는 것 처럼 테이블 모듈도 데이터베이스와 관련된 CRUD(Create, Read, Update, Delete) 작업을 쉽게 처리하기 위해 테이블 데이터 게이트웨이(Table Data Gate Way)를 사용한다. 테이블 데이터 게이트웨이는 특정한 테이블과 관련된 CRUD 작업을 수행해주는 보조 클래스라고 생각하면 된다. 예를 들어, 테이블 데이터 게이트웨이가 존재할 경우 아래 그림과 같이 메시지가 흘러갈 것이다.

그림2 - 테이블 모듈은 테이블 데이터 게이트웨이와 같은 테이블 CRUD 처리 모듈을 사용하여 데이터베이스 작업을 수행한다.

RecordSet은 테이블의 행의 집합을 나타내는 데이터로서 JDBC API의 ResultSet과 같은 구조를 갖는다. ResultSet 자체를 RecordSet으로 사용할 수도 있지만, 그것보다는 ResultSet으로부터 행 데이터를 읽어와 표처럼 값을 저장하는 RecordSet 클래스를 따로 구현하는 것이 좋다. 예를 들어, 테이블 데이터 게이트웨이에서는 다음과 같이 ResultSet으로부터 데이터를 읽어와 RecordSet에 저장하도록 코드를 작성할 수 있다.

    Statement stmt = null;
    ResultSet rs = null;
    
    try {
        ...
        rs = stmt.executeQuery(...);
        RecordSet recordSet = new RecordSet();
        
        while(rs.next()) {
            recordSet.newRecord(); // RecordSet에 새로운 레코드 생성
            recordSet.set("id", rs.getString("ID"));
            recordSet.set("name", rs.getString("NAME"));
        }
        return recordSet;
    } finally {
        ...
    }

테이블 모듈의 장단점

테이블 모듈의 장점은 도메인 모델 만큼 복잡하지 않다는 것이다. 고유성을 갖고 있는 객체를 사용하지 않기 때문에 객체의 상태를 관리할 필요가 없으며, 데이터를 참조할 때에만 RecordSet 객체를 생성해서 사용하면 된다.

테이블 모듈 안에서 또 다른 테이블 모듈을 호출할 수도 있기 때문에 손쉽게 하나의 비즈니스 로직 처리 메소드에서 여러 테이블에 쉽게 접근할 수 있다는 장점도 있다.

하지만 테이블 모듈은 테이블 중심의 구현 기법이기 때문에 객체 지향적인 기법을 사용하는 데에 제약이 따른다. 또한, 테이블 모듈이 생성하는 RecordSet은 비즈니스 영역의 특정한 개체(entity)와 매핑되지 않기 때문에 현실 비즈니스 영역의 개념을 테이블 모듈에 투영시키기 어렵다. 예를 들어, 도메인 모델에 기반해서 구현한 Member 객체는 회원 정보를 의미한다는 것을 알 수 있지만, 테이블 모듈의 RecordSet은 어떤 테이블 모듈 클래스로부터 생성된 RecordSet인지 분석해야만 RecordSet이 저장하고 있는 데이터의 의미를 파악할 수 있다.

RecordSet이 저장하는 데이터의 의미를 파악하는 작업을 좀더 쉽게 하기 위해서, 필자의 경우 테이블 모듈과 RecordSet 대신에 해당 데이터를 나타내는 자바빈 객체를 사용하는 방법을 권하곤 한다. 그 방법은 다음과 같다.

  • 테이블 모듈과 테이블 데이터 게이트웨이 사이에서는 RecordSet을 사용한다.
  • 테이블 모듈이 제공하는 비즈니스 로직 메소드는 도메인 영역의 개체를 나타내는 자바빈 객체를 사용하여 데이터를 주고 받는다.
예를 들어, 앞서 예제로 봤던 MemberTableModule의 regist() 메소드를 다음과 같이 구현할 수 있다.

    public class MemberTableModule {
        ...
        
        private MemberTableGateWay gateway = ...;
        
        // MemberBean은 비즈니스 도메인 영역의 회원 개체의 프로퍼티만 포함하는
        // 자바빈 객체로서 회원과 관련된 비즈니스 로직은 포함하고 있지 않다.
        public void regist(MemberBean mb) {
            // MemberBean으로부터 RecordSet 생성
            RecordSet recordSet = new RecordSet();
            recordSet.newRecord();
            recordSet.set("id", mb.getId());
            recordSet.set("name", mb.getName());
            
            // 테이블 데이터 게이트웨이 사이에서는
            // RecordSet을 사용
            gateway.regist(recordSet);        }
    }

테이블 모듈은 굉장히 유용한 패턴!

필자는 테이블 모듈과 비슷한 형태의 구성을 좋아하며, 실제 개발시에도 테이블 모듈과 비슷한 형태로 구현하는 경우가 많은 편이다. 완전한 도메인 모델의 경우는 개체의 고유성 개념이 존재하기 때문에, 고유성을 유지하는 데에 복잡한 코드가 필요하다.(EJB의 경우는 엔티티 빈으로 작성한다.) 하지만 웹 어플리케이션은 필요한 순간에만 데이터가 사용되는 경우가 많으며 객체의 고유성이 중요하지 않은 경우가 많다. 예를 들어, 게시판의 경우 글을 볼 때에만 데이터가 필요하며 그 데이터를 표현하는 객체를 여러번 생성한다 해도 문제가 되지 않는다. 따라서 객체의 고유성이 반드시 필요한 경우가 아니라면 테이블 모듈을 사용하는 것이 여러면에서 편리하다.

서비스 레이어


서비스 레이어(Service Layer)는 서비스를 제공하는 레이어를 별도로 제공하는 구조를 갖는다. 서비스 레이어는 클라이언트들이 공통적으로 필요로 하는 기능을 제공해야 할 때 알맞은 구조이며, 어플리케이션 로직을 담기에 적당한 곳이다.

그림 3 - 서비스 레이어는 서비스를 통합해서 제공한다.

위 그림은 서비스 레이어의 전형적인 구조를 보여주고 있다. 최상단 레이어에 위치한 것들은 서비스(어플리케이션)를 사용하는 클라이언트들이다. 하단 레이어는 비즈니스 로직을 구현하는 데 필요한 도메인 관련 컴포넌트 및 부대 서비스를 제공하는 모듈을 제공한다. 서비스 레이어는 하단 레이어에 있는 컴포넌트와 모듈을 사용하여 상단 레이어에 있는 클라이언트가 필요로 하는 서비스를 제공하게 된다.

즉, 서비스 레이어는 하단 레이어의 컴포넌트 및 서비스를 사용하여 클라이언트가 요구하는 비즈니스 로직을 수행하는 것이다.

서비스 레이어에는 주로 어플리케이션 로직이 위치한다. 서비스 클라이언트(즉, 위 그림에서 UI나 Gateway, Connector 등)들을 위한 흐름을 제어한다든지, 비즈니스 도메인에서 발생한 이벤트를 클라이언트에 전달해준다든지 등의 기능을 수행한다. 실제 업무 영영과 관련된 도메인 로직의 경우는 서비스 레이어의 하단에 위치한 비즈니스 도메인 컴포넌트에서 수행하게 된다.

비즈니스 로직의 두 가지 종류: 도메인 로직과 어플리케이션 로직

비즈니스 로직에는 크게 도메인 로직과 어플리케이션 로직의 두 가지가 존재한다. 먼저 도메인 로직은 문제 영역에 대한 것만을 처리한다. 예를 들어, 회원가입, 회원정보 변경과 같은 로직은 회원관리라는 문제 영역에 속하는 도메인 로직이다. 반면에 '회원가입 처리 후 가입축하 이메일 발송', '약관에 동의한 후 회원가입폼을 출력'과 같이 클라이언트가 따르게 될 워크플로우를 처리하는 것이 어플리케이션 로직에 해당한다.

즉, 서비스 레이어를 선택한 경우 전체적인 구성은 아래 그림과 같아지는 것이다.

그림 4 - 서비스 레이어의 역할은 어플리케이션 로직의 수행인 경우가 많다.

웹 어플리케이션에서 서비스 레이어를 사용하면 전체적인 실행 순서는 아래 그림과 같아진다. 즉, 사용자의 요청이 JSP에 전달되면 JSP는 서비스 레이어가 제공하는 기능을 실행하고, 서비스 레이어에서는 도메인 컴포넌트를 사용해서 클라이언트가 원하는 기능을 수행한 뒤 결과를 JSP에 리턴해준다. 그러면, 서비스 레이어로부터 결과를 전달받은 JSP는 클라이언트에 알맞은 결과 화면을 출력한다.

그림 5 - 서비스 레이어를 사용한 경우의 흐름

서비스 레이어의 구현 방법

웹 어플리케이션에서 서비스 레이어를 구현하기 위해서는 먼저 서비스 레이어가 제공해야 할 기능부터 찾아야 한다. 서비스 레이어가 제공하게 될 기능은 대부분 웹 어플리케이션에서 한번의 클릭시 실행되어야 할 기능과 동일한 구조를 갖게 된다. 예를 들어, '회원 가입', '아이디 중복 검사', '암호 이메일 발송' 등의 기능이 서비스 레이어에서 실행된다.

서비스 레이어 역할을 인터페이스로 추상화시켜주는 것이 좋지만 재사용성이 높지 않은 서비스 레이어라면 클래스로 구현해도 문제가 되지는 않는다. 회원과 관련된 기능을 처리하는 서비스 레이어를 인터페이스로 정의할 경우 다음과 같이 인터페이스를 작성할 수 있을 것이다.

    public interface MemberServiceLayer extends ServiceLayer {
        public void regist(MemberBean data) throws MemberServiceException;
        public void exists(String memberID) throws MemberServiceException;
        public void findPassword(String memberID) throws MemberServiceException;
    }

위 서비스 레이어 인터페이스의 구현체는 다음과 같이 회원 도메인 모델을 구현한 컴포넌트를 사용해서 어플리케이션에 알맞게 서비스 레이어를 구현하게 될 것이다.

그림 6 - 서비스 레이어를 사용한 구현 방식

위 그림에서 MemberComponent는 회원과 관련된 도메인 모델 컴포넌트에 해당하는 것으로서 서비스 레이어를 구현한 MemberServiceImpl은 MemberComponent를 사용하여 클라이언트가 원하는 기능을 수행하게 된다.

도메인 컴포넌트 부분은 앞서 '도메인 로직 패턴 1 - 트랜잭션 스크립트, 도메인 모델'에서 살펴봤던 '도메인 모델'을 사용하거나 또는 본 글의 앞 부분 주제인 '테이블 모듈'을 사용해서 구현할 수 있을 것이다. 서비스 레이어 부분은 '트랜잭션 스크립트'와 같은 방식으로 구현하게 된다.

위 그림에서 MemberServiceFactory는 MemberServiceLayer 인터페이스의 구현체를 생성할 때 사용되는 팩토리 클래스이다. JSP와 같이 서비스 레이어를 필요로 하는 곳에서 MemberServiceFactory 클래스를 사용해서 서비스 레이어를 구하게 된다.

    // MemberServiceFactory.createMemberService() 메소드는
    // MemberServiceImpl 클래스의 인스턴스를 리턴한다.
    MemberServiceLayer memberService = MemberServiceFactory.createMemberService();
    memberService.regist(..);
    ...

서비스 레이어의 장점

서비스 레이어의 장점은 어플리케이션 로직과 도메인 로직을 구분함으로써 각각의 재사용성을 높여준다는 점이다. 어플리케이션 로직을 나타내는 서비스 레이어의 인터페이스는 그와 비슷한 로직을 필요로 하는 다른 어플리케이션에서 재사용될 수 있으며, 도메인 로직만을 포함하고 있는 도메인 컴포넌트 역시 같은 도메인 로직을 필요료 하는 어플리케이션에서 재사용될 수 있을 것이다.

만약 하나의 컴포넌트가 어플리케이션 로직과 도메인 로직을 모두 포함하고 있다면, 이 컴포넌트는 어플리케이션 로직과 도메인 로직을 모두 필요로 하는 경우에 한해서만 재사용이 가능할 것이므로, 서비스 레이어를 통한 두가지 로직의 구분은 매우 큰 장점이 된다.

또한 서비스 레이어를 EJB의 세션빈으로 구현하게 될 경우 EJB 콘테이너를 통해서 트랜잭션을 처리할 수 있다는 장점도 얻을 수 있다.

+ Recent posts