메뉴 바로가기 검색 및 카테고리 바로가기 본문 바로가기

한빛출판네트워크

IT/모바일

Enterprise Flex RIA 해부(10) : DTO냐 VO냐?

한빛미디어

|

2008-08-25

|

by HANBIT

13,073

제공 : 한빛 네트워크
저자 : Tony Hillerson
역자 : 이대엽
원문 : Anatomy of an Enterprise Flex RIA Part 10: DTOs or Vos

지난 기사에서는 애플리케이션에서 엔티티를 영속화하기 위한 액션 스크립트 코드와 자바 코드를 살펴보았다. 이번에는 그러한 엔티티들을 생성, 갱신, 삭제하는 코드가 들어 있는 서비스 계층을 살펴볼 것이다.

DTO냐 VO냐?

데이터가 포함된 객체를 한 시스템에서 다른 시스템으로 전달하는 작업을 처리하는 전통적인 엔터프라이즈 패턴은 데이터 전송 객체(DTO; Data Transfer Object, 종종 Value Object나 VO라 불리기도 한다)이다. 간혹 우리가 필요로 하는 것 보다 더 많은 양의 코드가 객체에 필요한 데이터를 데이터베이스에서 가져와 보다 경량 객체인 DTO로 들어간 다음 해당 객체가 필요할 때 전송되는데 사용되기도 한다. 이 경우 EJB 3.0이 데이터베이스에서 데이터를 가져오고 집어넣는 것에 관한 모든 일을 대신 해준다. 엔티티(entity)는 데이터베이스 테이블과의 매핑을 정의하고 전송과 조작을 위해 데이터를 유지할 책임을 이행한다.

한 가지 알아둘 것은 대부분의 엔터프라이즈 자바 진영에서는 DTO를 표준으로 삼고 있지만 Cairngorm 프로젝트에서는 DTO 대신 대부분 VO를 사용할 것이라는 점이다. 사실 DTO와 VO 모두 같은 것을 의미한다.

세션 빈

엔티티 빈(entity bean)과 더불어 세션 빈(session bean)은 서비스를 제공하고 비즈니스 프로세스를 관리하기 위해 EJB 3.0에서 구체화한 클래스의 한 타입이다. 물론 이러한 프로세스의 중심에는 비즈니스 로직에 중요한 엔티티가 자리잡고 있다. 세션 빈은 두 가지 종류로 나뉘는데 상태 유지 세션 빈(stateful session bean)과 상태가 없는 세션 빈(stateless session bean)이 있다. 상태 유지 세션 빈에는 여러 요청(request)에 걸쳐 한 요청에서 다른 요청까지 "대화상태(conversational state)"를 유지하는 세션의 개념이 포함되어 있다. 간단히 말해서 대화상태는 빈의 수명 동안 빈과 상호작용하는 클라이언트에 관한 모든 중요한 정보를 말한다. JBOSS와 같은 EJB 컨테이너는 빈의 이전 클라이언트에서 발생하는 다음 호출에 대해 빈이 대비할 수 있게 한다.

예제 애플리케이션에서는 대개 다는 아니더라도 RIA에서는 빈이 사용자의 상태를 추적할 필요는 없다. 일반적으로 상태는 RIA를 통해 클라이언트에 유지되는데, 이것은 클라이언트가 메모리상에 머무르면서 단순 웹 페이지처럼 요청-응답 주기에 제약을 받지 않기 때문이다. 그러므로 세션은 상태 유지 세션 빈과 동일하지만 어떠한 상태도 유지하는 않는 상태가 없는 빈을 통해 한 번에 하나의 요청으로 처리될 것이다. 상태 유지 빈은 엔티티에 대해 데이터 접근 객체(DAO, Data Access Object)와 같은 역할을 수행할 것이다. 이는 상태 유지 빈에서 객체를 데이터베이스에 넣고 가져 온다는 것을 의미하며 그런 점에서 상태 유지 빈은 비즈니스 로직을 책임지게 될 것이다.

아래는 상태가 없는 세션 빈을 생성하는 문법이다:
@Stateless
@Local(value={BookDAO.class})
public class BookDAOBean implements BookDAO {
}
엔티티 빈에 @Entity을 지정하는 것처럼 @Stateless와 대응되는 @Stateful은 클래스를 세션 빈으로 표시한다. 그렇지만 한 가지 또 다른 요구사항은 해당 빈에서 서로 다른 요청에 대해 어느 메소드가 노출되어야 하는지를 컨테이너에 알려주는 인터페이스를 구현하는 것이다. 이러한 요청은 빈은 여러 서버에 걸쳐 배포될 수 있기 때문에 로컬이나 원격에서 수행될 수 있다. 빈은 각기 서로 다른 요청 타입에 어느 메소드를 사용할 수 있는지를 표시하는 하나 이상의 인터페이스를 포함할 수 있다. 우리는 로컬 접근에 대해서만 노출할 필요가 있으므로 어느 클래스의 로컬 인터페이스에 @Local 어노테이션이 지정되어 있는지를 컨테이너에 알려줄 것이다. 아래는 그러한 인터페이스를 보여준다:
package lcds.examples.bookie.dao;
...
public interface BookDAO {
     public void persist(Book transientInstance);
     public void remove(Book persistentInstance);
     public Book merge(Book detachedInstance);
     public Book findById(int id);
     public List getAll();
     public List findByAuthor(Person author);
     public List findBySubject(Subject subject);
     public Collection findByName(String title);
     public void removeDetached(Book detachedInstance);
}
위 코드는 모두 하나의 책이나 책 모음에 대해 수행할 수 있는 것을 나열한 것이다. 아래는 책에 대한 세션 빈을 코드로 작성한 것이다:
package lcds.examples.bookie.dao.beans;

@Stateless
@Local(value={BookDAO.class})
public class BookDAOBean implements BookDAO {
 
     @PersistenceContext
     private EntityManager entityManager;
 
     public void persist(Book transientInstance) {
           try {
                 entityManager.persist(transientInstance);
            } catch (RuntimeException re) {
                 throw re;
            }
      }
 
     public void remove(Book persistentInstance) {
           try {
                 entityManager.remove(persistentInstance);
            } catch (RuntimeException re) {
                 throw re;
            }
      }
위의 두 persist와 remove 메소드는 책 클래스의 인스턴스를 인자로 받아들인다. persist 메소드는 아직 데이터베이스에 존재하지 않는 엔티티에 대해 호출되어야 하며, remove 메소드는 데이터베이스에서 엔티티를 삭제할 것이다. 이 두 메소드는 같은 이름을 가진 EntityManager 타입의 객체에 들어있는 메소드를 호출한다. 그렇지만 주의할 점은 그 객체는 결코 인스턴스화되지 않는다는 것이다. 그 대신 EntityManager에는 @PersistenceContext 어노테이션이 지정되어 있다. 이것은 세션 빈이 EJB 3.0에서 어떻게 처리되는지를 일부 보여준다. EntityManager는 런타임에 인스턴스화되며 컨테이너는 현재 빈이 실행되고 있는 영속화 컨텍스트(persistence context)와 일치하는 EntityManager 인스턴스를 해당 세션 빈에 넘겨준다. 만약 하나 이상의 인스턴스가 존재하면 어노테이션에 원하는 것을 지정할 수도 있지만, 예제에서는 영속화 컨텍스트가 단순히 하나의 영속화 단위이므로 예제의 영속화 단위에 들어있는 빈은 모두 동일한 엔티티 관리자를 획득하게 된다.

이렇게 컨테이너가 객체에서 필요로 하는 객체의 알맞은 인스턴스를 부여하는 과정을 의존성 주입(dependency injection)이라 한다. 엔티티 관리자는 런타임에 세션 빈에 주입된다. 컨테이너가 적절한 방법으로 엔티티 관리자를 인스턴스화기 때문에 우리는 코드에서 그것을 어떻게 이루어지는 알 필요가 없는데, 이것은 우리가 코드를 작성하는 동안에는 알고 싶지 않은 일들이 있을 수도 있기 때문이다. 이제 몇 가지 메소드를 더 살펴 봄으로써 각 엔티티에 대해 다른 빈에서 수행할 연산의 유형을 확인해 보자:
    public Book merge(Book detachedInstance) {
          try {
                Book result = 
                     entityManager.merge(detachedInstance);
                return result;
           } catch (RuntimeException re) {
                     throw re;
           }
     }

    public void removeDetached(Book detachedInstance) {
          remove(merge(detachedInstance));
     }
persist와 remove 메소드는 기본적으로 SQL의 insert와 delete 문에 매핑된다. merge는 두 가지 경우에 사용될 수 있다. 첫 번째 경우는 직접적으로 SQL의 update문에 해당되는 것인데, merge는 엔티티로부터 변경 내역을 받아들여 데이터베이스를 갱신하기 위해 대기열(queue)에 해당 변경 내역을 추가한다. 그런데 EJB에는 EntityManager에 의해 관리되는 엔티티의 개념이 포함되어 있다. 엔티티 관리자의 remove 메소드가 호출되었을 때 실제로 일어나는 일은 엔티티가 해당 엔티티를 데이터베이스에서 제거하기 위해 대기열에 추가한 관리자에서 제거되는 것이다. 그러나 그 객체는 여전히 존재할 수도 있는데, 코드에서 그 객체를 참조하고 있을 수도 있기 때문이다. 또한 그 객체에 merge를 수행하여 엔티티 관리자로 되돌려 보내는 것도 가능하다.

이 경우는 준영속(detached) 상태에 있는 엔티티와 엔티티 관리자에 의해 관리되는 엔티티의 차이점을 보여준다. 그럼 준영속 상태에 있는 엔티티에 대해 persist와 merge 메소드를 호출하는 것에는 어떤 차이가 있을까? 사실 아무런 차이가 없다. 두 경우 모두 객체가 존재하지 않는다면 삽입될 것이다. persist 메소드는 엔티티가 이미 데이터베이스 형태로 존재한다면 문제를 겪을 것이나, merge는 그렇지 않을 것이다.

우리는 종종 데이터베이스에서 개체 관리자로 보내지 않았을 지도 모를 객체를 클라이언트 측에서 보낼 것이므로 만일의 경우에 대비해서 새로운 객체와 기존 객체를 갱신하기 위해 merge 메소드를 상당히 자주 호출할 것이다. 이는 내가 removeDetached 메소드를 작성한 이유를 설명해 주기도 한다. 즉 가끔씩 우리는 미처 엔티티 관리자로 로딩되지 않은 객체를 삭제하고 싶을 수도 있으며, 우리가 그러한 과정을 더 복잡하게 만들지 않는 한 그것을 알지 못할 것이다. 만약 준영속 상태에 있는 엔티티를 제거하려 한다면 엔티티 관리자는 이 객체를 알지 못하므로 오류가 발생될 것이다. 이러한 경우에는 먼저 객체를 merge하여 엔티티 관리자에서 관리하게 한 다음 제거하는 것이 더 쉽다.
    public Book findById(int id) {
          try {
                Book instance = entityManager.find(Book.class, id);
                return instance;
           } catch (RuntimeException re) {
                throw re;
           }
     }
    
    public List getAll() {
          Query q = entityManager.createQuery(
                    "from Book b" +
                    " order by b.title"
               );
               List results = q.getResultList();
               return results;
     }

    public List findByAuthor(Person author) {
          Query q = entityManager.createQuery(
               "from Book b" +
               " where b.author.id = :author_id"
          );
          q.setParameter("author_id", author.getId());
          List results = q.getResultList();
          return results;
     }
필자는 위 코드에 세션 빈을 어떻게 다룰 지에 대한 예제에서 했던 것과 같이 메소드를 몇 개 더 추가하였다. findById 메소드는 개체 관리자의 find(Class, int) 메소드를 호출한다. 이 메소드는 주어진 클래스가 어디에 매핑되는지를 확인한 다음 ID와 일치하는 행을 찾아온다. 간단하다.

getAll 메소드에서는 질의를 사용하고 있다. 질의는 개체 관리자에서 생성되며, EJB에서는 표준 질의로 SQL 대신 EJBQL을 사용한다. EJBQL은 SQL과 거의 흡사하지만, 여러분은 EJBQL을 이용하여 테이블 대신 클래스를, 컬럼 대신 프로퍼티를 참조할 수 있다. 질의의 결과는 손쉽게 리스트로 변환된다.

findByAuthor 메소드는 EJB 3.0의 또 다른 질의 기능을 보여준다. SQL와 마찬가지로 EJBQL에도 where 절이 있다. 알아둘 점은 where절의 문법에서 :<매개변수명>의 형식으로 네임드 파라미터(named parameters)를 지정할 수 있다는 것이다. 위 라인에서 우리는 질의를 생성한 다음 setParameter 메소드를 호출하여 저자의 ID를 매개변수로 지정했으므로 질의는 특정한 저자 ID를 가진 책을 모두 찾을 것이다.

[그림 13]은 데이터 프로젝트의 나머지 세션 빈을 보여준다.


[그림 13] 데이터 프로젝트의 나머지 세션 빈

다음 연재에서는 TestNG를 이용하여 애플리케이션에서 자바로 작성되어 있는 부분을 테스트하는 것에 관해 살펴볼 것이다. 여기를 클릭하면 전체 시리즈(영문)를 볼 수 있다.
TAG :
댓글 입력
자료실

최근 본 책0