저작권 안내: 저작권자표시 Yes 상업적이용 No 컨텐츠변경 No

스프링5 입문

JSP 2.3

JPA 입문

DDD Start

인프런 객체 지향 입문 강의

처음 회사라는 곳에 들어가서 업무를 하던 시절, 그 회사에서 UML로 클래스 다이어그램을 그리는 사람은 나뿐이었다. 개발자가 10여명 정도 되는 작은 회사였는데 회사를 다니는 2년 8개월 동안 데이터 모델인 ERD를 그리는 사람이 1-2명 있는 정도였다.


사실 내가 그린 클래스 다이어그램은 데이터 모델이나 다름 없었다. 지금처럼 엔티티, 밸류에 대한 고민도 할 수 없었던 시기라 사실상 ERD를 클래스 다이어그램으로 그린 것이나 다름없었다. 약간의 차이라면 데이터뿐만 아니라 로직이나 다른 역할의 클래스들도 표현하곤 했다는 것이었다.


대학시절에 객체 지향이나 도메인 모델에 대해 제대로 공부하지 못했던 관계로 직장 생활을 하면서 눈치밥으로 도메인 모델이 이런 건가하는 감을 잡아 왔다.


사회생활 초기에는 ERD나 다름 없는 클래스 다이어그램이 도메인 모델 같았다. 특정 방법론을 따르는 SI 프로젝트에 참여했을 때 설계는 모두 데이터 중심이었고, 지식이 부족했던 당시에 읽었던 책들도 비슷한 내용을 담고 있었다.


그런데, 설계에 대한 여러 책들을 읽다보니 도메인 모델은 그런 것이 아니었다. 명쾌하게 답을 내릴 수 없었지만, 도메인 모델이란 것은 단순히 ERD의 클래스다이어그램 버전은 아니라는 생각이 들었고, 조금 더 객체 설계에 가깝다는 느낌을 받게 되었다.


[도메인 모델이란?]


아직도 도메인 모델이 뭐다라고 꼭 찝어 한 줄로 정의할 수 없기에, 도메인 모델이 뭔지 알아보고자 이런 저런 문서를 뒤져보았다.


우선, '도메인 모델'이란 단어에 출현하는 '도메인'과 '모델'에 대한 이해가 먼저 필요할 것 같다. '도메인Domain'에 대한 정의부터 뒤져보기로 했다. 위키피디아님의 정의(http://goo.gl/gw4w84)에 따르면, '도메인 공학'은 다음과 같다.

A domain is a field of study that defines a set of common requirements, terminology, and functionality for any software program constructed to solve a problem in the area of computer programming, known as domain engineering.


컴퓨터 프로그래밍으로 문제를 해결하기 위해 만들 소프트웨어 프로그램을 위한 요구사항, 용어, 기능을 정의하는 학문 영역이 도메인 공학이다.

이 정의에 따르면, '도메인'은 해결하고자 하는 문제 영역 정도가 될 것 같다.


위키피디아님이 도메인 모델에 대한 정의(http://goo.gl/Bna2U)도 다음과 같이 내려주셨다.

A domain model in problem solving and software engineering is a conceptual model of all the topics related to a specific problem. It describes the various entities, their attributes, roles, and relationships, plus the constraints that govern the problem domain. It does not describe solutions to the problem.


소프트웨어 공학에서 도메인 모델이란 특정 문제와 관련된 모든 주제의 개념 모델이다. 도메인 모델은 다양한 엔티티, 엔티티의 속성, 역할, 관계, 제약을 기술한다. 문제에 대한 솔루션을 기술하지 않는다.

예전에 얼핏 봤던 방법론이 요구사항 분석 과정에서 UML로 개념 모델을 만들었는데 이 때의 개념 모델이 도메인 모델에 해당된다. 유스 케이스나 유저 스토리 같은 것이 도메인의 동적 측면을 보여준다면, 도메인 모델은 도메인의 정적 구조를 보여준다.


도메인 모델은 지식을 공유하고 소통하는 도구로 사용하기에 적합하다. 요구사항 분석 과정에서 분석가(기술팀)는 구축한 도메인 모델을 통해 자신이 올바르게 이해했는지 확인할 수 있고, 반대로 업무 전문가들도 자기들끼리 의견을 맞추는데 도메인 모델이 도움이 된다.


아키텍처에서 도메인 레이어의 결과물을 도메인 모델로 부르기도 한다. 프리젠테이션 레이어, 어플리케이션 레이어, 도메인 레이어, 인프라스트럭처 레이어와 같은 구조에서, 도메인 레이어는 도메인의 개념, 도메인의 정보, 도메인의 규칙을 표현하는 책임을 진다.


예전에는 분석가(컨설턴트)라는 사람들이 이런 도메인 모델을 만들어서 구현 담당자에게 넘겨주고 떠나곤 했다. 이를 넘겨 받은 구현자들은 도메인 모델을 다시 구현 모델(즉, 소프트웨어 설계)로 변환하는 과정을 거친 뒤에 구현을 진행했다. 그런데 여기서 문제는 도메인 모델이 구현 모델로 넘어가는 과정에서 많은 도메인 지식들이 유실되면서 실제 도메인 전문가가 요구하는 소프트웨어가 만들어지지 않는다는 것이다. 게다가 모든 요구사항은 프로젝트가 진행되는 동안 완성되어 나가기 때문에 구현 과정에서 발견되는 도메인에 대한 통찰이나 개념들이 도메인 모델에 다시 반영되지 않는 문제도 있다.


도메인과 구현 사이의 불일치는 (프로그래머와 도메인 전문가 사이의) 불필요한 해석 과정을 야기하고, 이는 잘못된 소프트웨어를 만드는 원인이 되기도 한다. 이런 불일치를 해소하기 위한 노력 중 하나가 도메인 주도 설계Domain-Driven Design이다. DDD는 도메인 모델의 적용 범위를 구현까지 확장하는데, 이를 통해 도메인 지식이 구현 코드에 반영되도록 하고 있다.


------

  • 조영호 님 : 도메인은 저희 입장에서 소프트웨어를 개발하는 대상 영역정도로 생각해도 무방합니다. 택시 앱을 만든다면 택시 기사님께 콜을 하고, 탑승하고, 요금을 지불하는 전 과정이 도메인이 됩니다. 물론 프로젝트를 할 때는 이 중에서 소프트웨어로 개발될 범위로 한정해서 범위를 좁하게 됩니다. 이렇게 개발 대상과 범위를 간단히 도메인이라고 봐도 무방할 것 같습니다. 도메인 모델이란 도메인을 모든 사람이 동일한 관점에서 이해할 수 있고 공유할 수 있도록 단순화시킨 것이라고 보시면 됩니다. 이게 꼭 클래스 다이어그램의 형식으로 표현될 필요는 없지만 객체지향 프로그래밍을 하는 경우에는 일반적으로 클래스 다이어그램의 표기법을 사용해서 도메인 모델을 정리하는게 여러모로 유용합니다. 이렇게 하는 이유는 객체지향 패러다임에서 사용하는 유사한 기법에 기반하는게 코드와 모델을 유사한 형태로 유지하는데 이롭기도 하고 일단 도메인 모델을 이해하면 그 모델을 기반으로 코드를 쉽게 이해하고 수정할 수 있기 때문이죠. 다른 패러다임인 경우에는 그 패러다임에서 구현하기 쉬운 형태로 작성하면 되겠죠. 마지막에 아키텍처 상에서 말하는 도메인 모델은 마틴 파울러가 PEAA에서 언급한 것으로 도메인 레이어를 객체지향적으로 구현하는 패턴을 가리키는 용어입니다. 즉 패턴의 일종이고 원래의 도메인 모델과는 약간 거리가 있습니다.
  • 박성철 님 : 도메인 모델이란 용어 자체만 보면 문제 영역을 개념적으로 모델링한다는 평범한 의미인데 이 용어가 OOA/D 분야에서 사용되었기 때문에 객체 모델링이 함의된 것 같습니다. 흔히 도메인 모델이라고 하면 정적 데이터 요소를 표현하는 것으로 국한하는 것 같은데 동적인 요소(예를 들어 유즈 케이스)까지 고려가 되어야 할 것 같습니다.


Posted by 최범균 madvirus

댓글을 달아 주세요

  1. kayano 2015.04.14 18:06 신고  댓글주소  수정/삭제  댓글쓰기

    정말 블로그처럼

도메인 모델의 데이터를 뷰에 노출하는 방법 때문에 늘상 고민을 하는데, 그런 고민을 하는 이유는 도메인 객체의 기능을 뷰에 노출하고 싶지 않기 때문이다. 도메인 객체가 단순 데이터 구조라면 도메인 객체를 그대로 (JSP나 Velocity와 같은) 뷰에 노출해도 setter 메서드만 조심해서 사용하면 크게 문제될 것이 없다. 하지만, 도메인 객체가 암호 변경, 만료일 연장과 같은 도메인 기능을 제공하고 있다면, 뷰 코드에서 이들 기능을 실행하지 않는 것을 선호하는데, 그 이유는 최대한 한 레이어에서 기능 실행을 제어하길 원하기 때문이다. (예를 들어, 도메인 기능은 바로 위 어플리케이션 레이어에서만 실행하도록 하고 컨트롤러나 뷰에서는 실행하지 않도록 함으로써, 도메인 기능을 한 곳에서만 실행하도록 코드를 제어하고 싶다.)


본 글에서는 도메인 객체를 뷰에 전달할 때 사용할 수 있는 세 가지 방법을 살펴볼 것이며, 이를 통해 여러분은 각 방법 중 자신에게 알맞은 방식을 선택할 수 있을 것이다.


방법1: 그냥 전달하기


이 방법은 가장 쉬운 방법이다. 그냥 도메인 객체를 그대로 뷰에 전달하는 방법이다. 예를 들어, 스프링 컨트롤러에서 다음과 같이 뷰에 도메인 객체를 그대로 전달한다.


public class UserInfoController {

    private UserRepository userRepository;


    @RequestMapping("/user/info")

    public String info(ModelMap modelMap) {

        User user = userRepository.findOne(getUserId());

        if (user == null) {

            return "user/userNotFound";

        }

        modelMap.addAttribute("user", user);

        return "user/userInfo";

    }

    ...

}


JSP와 같은 뷰 코드에서는 다음과 같이 user 객체를 사용한다.


${user.id} , ${user.name}


-- 연관된 객체에 접근

${user.team.name} 


-- 도메인 객체가 제공하는 기능 실행 가능

${user.changePassword('newpassword')}


위 방식은 다음의 특징이 있다.

  • 뷰 코드에서 연관된 객체에 접근할 수 있도록 해야 한다.
    • JSP와 같은 ORM 기술을 사용할 경우, 트랜잭션 범위를 뷰 영역까지 확장하거나 연관된 객체를 미리 읽어오는(eager loading) 등의 처리가 필요하다.
  • 도메인 객체가 제공하는 기능을 뷰에서 실행하는 것에 대한 엄격한 규칙이 필요하다.
    • 암호 변경과 같은 기능을 뷰에서 실행할 수 있다는 것은 도메인 기능 실행을 요청하는 곳이 여러 영역으로 분산된다는 의미인데, 이는 유지보수에 도움이 되지 않는다. 따라서, 뷰에서는 도메인 기능을 실행하지 않도록 제한하는 것이 좋다.


방법2: 읽기 전용 인터페이스


두 번째 방법은 읽기 전용 인터페이스를 만드는 것이다. 예를 들어, 다음과 같이 데이터 제공을 위한 전용 인터페이스를 작성한다. 예를 들어, 다음과 같이 정보 제공을 하는 인터페이스를 작성한다.


public interface ReadonlyUser {

    public Long getId();

    public String getName();

    public ReadonlyTeam getTeam();

}


public interface ReadonlyTeam {

    public Long getId();

    public String getName();

}


public class User implements ReadonlyUser {

    @Override

    public Long getId() { return id; }

    @Override

    public String getName() { return name; }

    @Override

    public Team getTeam() { return team; } // 자바는 리턴타입에 하위 타입 지정이 가능하다.


    public void changePassword(String newPassword) { ... }

}


public class Team implements ReadonlyTeam {

    ...

}


읽기 전용 인터페이스를 만들었다면, 읽기 전용 인터페이스를 이용해서 데이터를 읽어오는 객체를 추가한다.


public class UserDataLoaderImpl implements UserDataLoader {


    public ReadonlyUser getUser(Long id) {

        User user = userRepository.findOne(id);

        throwExceptionWhenUserIsNull(user);

        return user;

    }

}


이제 컨트롤러는 다음과 같이 UserDataLoader를 사용해서 읽기 전용 User 타입을 가져온다.


public class UserInfoController {

    private UserDataLoader userDataLoader;


    @RequestMapping("/user/info")

    public String info(ModelMap modelMap) {

        try {

            ReadonlyUser user = userDataLoader.getUser(getUserId());

            modelMap.addAttribute("user", user);

            return "user/userInfo";

        } catch(UserNotFoundException ex) {

            return "user/userNotFound";

        }

    }

    ...

}


그런데, 위와 같이 코드에서는 읽기전용 인터페이스를 사용했다고 하더라도 뷰 코드에서는 리플렉션을 이용하기 때문에 실제로는 다음과 같이 도메인 기능을 사용할 수 있다.


-- 리플렉션으로 실행 가능

${user.changePassword('newpassword')}


따라서, 완전한 읽기 전용 객체를 전달하고 싶다면, 다음과 같이 User 객체가 아닌 프록시 객체를 만들어서 전달하는 것이 좋다.


public class UserProxy implements ReadonlyUser {

    private User user;

    private ReadonlyTeam team;


    public UserProxy(User user) {

        this.user = user;

        this.team = new TeamProxy(user.getTeam());

    }


    @Override

    public Long getId() { return user.getId(); }

    ...

    @Override

    public ReadonlyTeam getTeam() { return team; }

}


DataLoader에서는 다음과 같이 User를 리턴하는 대신에 UseProxy를 생성해서 리턴한다.


public class UserDataLoaderImpl implements UserDataLoader {


    public ReadonlyUser getUser(Long id) {

        User user = userRepository.findOne(id);

        throwExceptionWhenUserIsNull(user);

        return new UserProxy(user);

    }

}


이제 뷰에는 UserProxy 객체가 전달되므로, 완전한 읽기 전용 객체를 뷰에서 사용하게 되며, 더불어 도메인 기능을 뷰에서 실행할 수 있는 가능성도 없어졌다.


방법3: Exporter 사용하기


세 번째 방법은 자신이 필요한 데이터만 가져가도록 도메인 객체에 Exporter 인터페이스를 추가하는 것이다. 이 방법을 사용하려면 다음과 같이 도메인 객체의 데이터를 받을 수 있는 인터페이스를 먼저 정의한다.


public interface UserExporter<T> {

    public void id(Long id);

    public void name(String name);

    public void team(Long teamId, String teamName);

    public T build();

}


도메인 객체는 UserExporter에게 데이터를 전달해주기 위한 메서드를 제공한다.


public class User {


    public <T> T export(userExporter<T> expoter) {

        exporter.id(this.id);

        exporter.name(this.name);

        exporter.team(this.team.getId(), this.geam.getName());

        return exporter.build();

    }

}


뷰 영역에 데이터를 전달해주어야 하는 객체는 다음과 같이 필요한 데이터를 받는 Exporter 구현체와 DTO를 만든다.


public class DataLoaderImpl .. {


    public UserDto getUser(Long id) {

        User user = userRepository.findOne(id);

        throwExceptionWhenUserIsNull(user);

        return user.export(new MyUserExporter());

    }


    private class MyUserExporter implements UserExporter<UserDto> {

        private UserDto userDto;

        public MyUserExporter() {

            userDto = new UserDto();

        }

        public void id(Long id) { userDto.setId(id); }

        public void name(String name) { userDto.setName(name); }

        ...

        public UserDto build() { return userDto; }

    }

}


DataLoader를 사용하는 코드는 UserDto를 사용하므로, 결과적으로 뷰는 데이터만 사용하게 된다.



Posted by 최범균 madvirus

댓글을 달아 주세요

웹 어플리케이션의 비즈니스 로직 처리와 관련된 도메인 로직 패턴인 트랜잭션 스크립트와 도메인 모델에 대해서 살펴본다.

트랜잭션 스크립트

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

은행의 계좌 이체 서비스를 생각해보면 '잔고 확인->받는 사람 확인->이체 실행->잔고 감소'의 로직 순서가 하나의 로직으로 처리되어야 하고, 이 과정에서 한번이라도 오류가 발생하면 모든 처리가 취소되고, 모든 과정이 성공해야 비로서 처리가 완료된다. 즉, 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에서 하나의 엔티티 빈 객체는 테이블 상의 하나의 행을 나타낸다. 또한, 서로 다른 엔티티 빈 객체는 테이블의 서로 다른 행에 해당한다. 즉, 각각의 엔티티 빈은 서로 다른 개체를 나타내는 고유 객체인 것이다.

관련링크:
Posted by 최범균 madvirus

댓글을 달아 주세요

  1. 햄과함께 2018.02.27 08:29 신고  댓글주소  수정/삭제  댓글쓰기

    덕분에 이해하고 갑니다. 감사합니다. :)