Ask

persistence.xml file

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0"
 xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">

 <persistence-unit name="xyz" transaction-type="JTA">

  <provider>org.hibernate.ejb.HibernatePersistence</provider>
  <class>com......</class>
 </persistence-unit>

</persistence>

ApplicationContext.xml

<bean id="transactionManager"
    class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>

 <bean id="dataSource"
     class="org.springframework.jdbc.datasource.DriverManagerDataSource">
     <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver" />
     <property name="url" value="jdbc:oracle:thin:@localhost:1521:XE" />
     <property name="username" value="yyy" />
     <property name="password" value="yyy" />
</bean>

<bean id="entityManagerFactory"
    class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="persistenceUnitName" value="xyz" />
    <property name="dataSource" ref="dataSource" />
    <property name="jpaVendorAdapter">
        <bean
            class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
            <property name="databasePlatform" value="org.hibernate.dialect.Oracle10gDialect" />
            <property name="showSql" value="true" />
            <!-- <property name="generateDdl" value="true" /> -->
        </bean>
    </property>
</bean>

<bean id="theDao" class="com.cin.the.dataaccess.dao.the.TheJPA">
    <property name="entityManagerFactory" ref="entityManagerFactory"></property>
 </bean>

the error i get is

[PersistenceUnit: xyz] Unable to build EntityManagerFactory


Answer

The main problem was that the entites was not generated properly. so at the end of the stacktrace it was giving this error

Caused by: org.hibernate.MappingException: property mapping has wrong number of columns: com......date type: object

once the entity was generated correctly the problem was solved









나의 경우 entity와 실제 table를 매핑 시키지 않았음. 그래서 다음처럼 매핑 시킴

< before >

@Entity
public class SomeEntity {
    ...
}


< after >

@Entity
@Table(name="tableName")
public class SomeEntity {
    ...
}


Posted by linuxism
,


JPA에 대한 소개, 활용방안, Spring 프레임워크와 통합

Table of Contents

  1. JPA 란?
  2. JPA에서 주목할 부분
  3. 간단한 예제를 통한 JPA 소개
  4. EntityManager API
  5. Entity Lifecycle 과 Persistence Context
  6. META-INF/persistence.xml
  7. JAVA SE 에서 JPA 사용하기
  8. Spring 프레임워크와 통함
  9. 참고문헌

JPA 란?

JPA(Java Persistence API)는 EJB 2.x에서 DB에 접근하기 위해 사용되었던 Entity Bean을 
JSR-220(Enterprise JavaBeans 3.0)에서 대체하는 새로운 기술이다. 
JPA는 Entity Bean과는 아주 다른 POJO(Plain Old Java Object) 기반의 ORM(Object-Relational Mapping) 
프로그래밍 모델을 제공하며 기존에 존재하던 Hibernate와 같은 ORM솔루션과 유사하다. 
또한 EJB3.0에 국한되지 않은 범용적인 기술로 만들어 졌기 때문에 JAVA EE 와 SE 환경에서 
모두 사용 할 수 있으며 JAVA SE 5.0 Annotation을 사용하여 Java 객체에서 RDB로 Mapping하는 방법을 
단순화시켰다.

JPA에서 주목할 부분

  • POJO 기반의 단순한 Persistence Model
  • 표준화된 O/R 매핑
    • Annotation AND/OR XML 사용
    • 디폴트 규칙 적용으로 대부분의 경우 별도의 O/R 매핑을 지정할 필요가 없음.
  • 객체간의 상속관계 지원
  • EJBQL에 비해 확장된 쿼리 언어
    • Bulk Update/Delete, Subquery, Native Query 지원
  • Java EE And Java SE 환경 모두 지원
  • 프로바이더(Provider)를 플러그인 해서 사용 가능

간단한 예제를 통한 JPA 소개

  • 예제에 사용될 데이터 모델

    그림1> OrderApp 데이터 모델
  • DB의 데이터 모델은 JPA에서 Entity로 표현된다.
  • Entity는 데이터 모델을 객체 모델로 표현한 것으로 EJB 2.x의 Entity Bean과 유사하지만
    JPA에서는 별도의 인터 페이스 없이 POJO class로 표현된다.
  • class에서는 Entity임을 표시하기 위해 @Entity Annotation이 달려 있다.
    소스1> Customer.java — Customer 테이블과 Mapping
    /**
     * Customer entity.
     */
    
    @Entity  //---이 클레스가 Entity임을 표시 ---//
    @Table(name="ORDERAPP_CUSTOMER")
    @NamedQuery(name="findCustomerByName", query="select c from Customer c where c.name=:name")
    public class Customer implements Serializable {
    
        // ---Annotation이 필더에 달려 있을 경우 Field access type이라 한다.--- //
        @Id
        private String id;
        private String name;
        private String address;
    
        // ---cascade가 아래와 같이 설정되면 Customer entity가 생성/삭제 될때 --- //
        // ---해당되는 entity도 같이 생성/삭제된다.                           --- //
        @OneToMany(mappedBy="customer", cascade={CascadeType.PERSIST, CascadeType.REMOVE})
        private List<Orders> orders;
        
        // ---Entity에서 Defult constructor 는 필수이다--- //
        public Customer(){}
        
        public Customer(String id, String name, String address) {
            this.id = id;
            this.name = name;
            this.address = address;
        }
    
        public String getId() {return id;}
    
        public String getName(){return name;}
        
        public String getAddress(){return address;}
        
        public void setAddress(String address){this.address = address;}
        
        public List<Orders> getOrders(){return orders;}
        
        public void setOrders(List<Orders> orders) {this.orders = orders;}
    
        public void addOrder(Orders o){
            if(orders == null)
                orders = new LinkedList<Orders>();
            orders.add(o);
        }
    }
    
  • Entity는 기본적으로 하나의 Entity에 하나의 DB 테이블에 매핑된다.
    (M:N 관계가 존재시는 테이블이 하나더 존재한다)
    소스2> Item.java — Item 테이블과 Mapping
    /**
     * Item entity.
     */
    @Entity
    @Table(name="ORDERAPP_ITEM")
    @NamedQuery(name="findAllItems", query="select i from Item i")
    public class Item implements Serializable {
    
        private long id;
        private String name;
        private double price;
        
        public Item() {}
        
        public Item(String name, double price){
            this.name = name;
            this.price = price;
        }
    
        // ---Annotation이 프로퍼티에 달려 있을 경우 Property access type이라 한다.--- //
        @Id @GeneratedValue
        public long getId() {return id;}
    
        public void setId(long id) {this.id = id;}
        
        public String getName(){return name;}
        
        public void setName(String name){this.name = name;}
    
        public double getPrice() {return price;}
    
        public void setPrice(double price) {this.price = price;}
    }
    
    소스3> Order.java — Order 테이블과 Mapping
    /**
     * Order entity.
     */
    @Entity
    @Table(name="ORDERAPP_ORDERS")
    public class Orders implements Serializable {
        
        @Id @GeneratedValue
        private Long id;
        @ManyToOne
        private Customer customer;
        @ManyToMany
        @JoinTable(name="ORDERAPP_ORDERS2ITEMS")
        private Collection<Item> items;
        
        public Orders() {}
        
        public Long getId() {return id;}
        
        public void setId(Long id) {this.id = id;}
        
        public Customer getCustomer(){return customer;}
        
        public void setCustomer(Customer c){this.customer = c;}
        
        public Collection<Item> getItems(){return items;}
        
        public void setItems(Collection<Item> items){this.items = items;}
    }
    
  • 예제 소스에 사용된 O/R 매핑 표준 Annotation
    • Logical annotation
      • @id - Primary Key에 해당하는 ID필드임을 표시한다.
      • @GeneratedValue - DB에 의해서 값이 자동으로 생성되는 필드임을 표시한다.
    • Physical annotation
      • @Table - Table mapping
  • 예제 소스에 사용된 Relationships 표준 Annotation
    • @OneToMany - 1:N 관계를 표현하기 위한 필드임을 표시한다.
    • @ManyToOne - N:1 관계를 표현하기 위한 필드임을 표시한다.
    • @ManyTOMany - M:N 관계를 표현하기 위한 필드임을 표시한다.
    • @JoinTable - 관계를 표현할 때 어떤 테이블을 사용할 것인지에 대해 표시한다.
  • 예제 소스에 사용된 Query 표준 Annotation
    • @NamedQuery - Static query 를 entity class에 직접 명기
  • Cascaed Type (연쇄작업 설정)
    • CascadeType.PESIST - 개체 삽입
    • CascadeType.MERGE - 업데이트
    • CascadeType.REMOVE - 삭제
    • CascadeType.REFRESH - 리플레쉬
    • CascadeType.ALL - 전부 적용

EntityManager API

  • DB에서 엔티티를 가져오거나 생성, 삭제 하는 일은 모두 EntityManager API를 통해서 이루어진다.
    EntityManager는 누가 관리되는가에 따라 크게 두가지로 구분된다.
    • Container-managed EntityManager

      JAVA EE 환경에서만 사용되며 컨테이너가 EntityManager 인스턴스의 Lifecycle을 관리한다.
      JTA 트랜잭션에 자동으로 연결되기 때문에 JAVA EE 환경에 유용하다.

      • Container-managerd 에서 EntityManager 객체를 가져오는 방법
        Container-managerd EntityManager의 경우 인젝션이나 lookup을 통해서 얻어올 수 있다.
        1. 인젝션 이용. 해당 필드나 메소드에 @PersistenceContext표기
          @PersistenceContext
          private EntityManager em;
          
        2. lookup 이용. Class에 @PersistenceContext를 표기해 EntityManager를
          ENC(Environment Naming Context) 네임스페이스에 등록
          @PersistenceContext(name="em", unitName="HR")
          public class HelloBean implements Hello {
          // ...
          InitialContextic = new InitialContext();
          EntityManager em = (EntityManager)ic.lookup("java:comp/env/em");
          
    • Application-managerd EntityManager

      JAVA EE, JAVA SE 두 환경 모두 사용되며 개발자가 EntityManager 인스턴스의 Lifecycle을 직접 관리하며
      트랜젝션도 직접 관리해야 한다.

      • Application-managerd 에서 EntityManager 객체를 가져오는 방법
        Application-managerd EntityManager의 경우 EntityManagerFactory API를 통해서 얻어올 수 있다.
        @PersistenceContext
        private EntityManagerFactory emf;
        // ...
        EntityManager em = emf.createEntityManager();
        
  • Entity 객체를 만드는 것은 일반 JAVA 객체를 생성하듯이 new 생성자를 사용하면 되며 이를
    DB에 저장하려면 다음과 같이 Entitymanager.persist() 메소드를 호출해야 한다.
    Customer customer = new Customer(id, name, address);
    em.persist(customer);
    
  • DB에서 하나의 Entity 객체를 가져오려면 EntityManager.find() 메소드를 호출한다.
    이경우에는 Primary Key에 해당하는 값을 통해서 Entity를 가져올 수 있다.
    Customer e = em.find(Customer.class, id);
    
  • Java Persistence Query Language를 이용하여 모든 Entity들을 가져오거나 특정 필드값을 비교하여
    Entity를 가져올 수 있다.
    Query query = em.createQuery("select c from Customer c");
    List list = query.getResultList();
    
  • Entity 필드를 수정하려면 Entity 객체의 필드를 수정하면 된다.
    해당 필드는 EntityManager.flush() 를 호출하거나 트렌잭션이 commit 될때 DB와 동기화 된다.
    Customer e = em.find(Customer.class, id);
    c.setAddress(address);
    
  • Entity를 삭제할 때에는 EntityManager.remove() 를 호출한다.
    em.remove(customer);
    
  • Entity간의 관계(Relationship)는 다른 Entity에 대한 레퍼런스로 표현된다.
    관계를 수정할려면 레퍼런스 값을 바꾸면 된다.
    ManyTOMany나 OneToMany 같이 여러 개의 Entity와 관계를 맺고 있을때는 Collection객체로 표현된 
    값을 바꾼다.
    public class Customer //...
    
    @OneToMany(mappedBy="customer", cascade={CascadeType.PERSIST, CascadeType.REMOVE})
    private List<Orders> orders;
    
    public void addOrder(Orders o) {
        orders.add(o);
    }
    

Entity Lifecycle 과 Persistence Context

  • Persistence Context ?
    현재 관리되고 있는 Entity 인스턴스들의 집합을 말함.
    Entity 인스턴스의 상태는 EntityManager 오퍼레이션과 밀접한 관련이 있다.
  • EntityManager 오퍼레이션에 따른 Entity의 Lifecycle

    그림2> EntityManager 오퍼레이션에 따른 Entity의 Lifecycle
    • NEW - Entity instance가 생성되었을 때의 상태로 DB와 연결되어 있지 않다.
      EntityManager.perisit()를 통해서 DB에 저장되며 Managed 상태가 된다.
    • Managed - Entity instance가 Persistence Context에 의해서 관리되는 상태로 Entity 인스턴스
      값의 변화가 DB에 반영된다.
      Container-managed EntityManager의 경우 트랜잭션이 종료되면 모든 Entity는 detached 상태가 된다.
    • Detached - Entity instance가 Persistence Context에 의해서 더이상 관리되지 않는 상태로 DB와 연결되지 않는다.
      EntityManger.merge()를 통해서 다시 managed 상태로 만들 수 있다.
    • Removed - DB에서 해당 Entity가 삭제될 상태이다.

Java Persistence Query Language

EJBQL과 비슷하지만 JPQL은 Bluk Update/Delete, Subquery, Native Query(DB-specific SQL) 들을 지원한다.

  • Query API
    Query Object는 EntityManager의 createQuery()를 이용해 생성한다.
    • setParameter(), setMaxResults(), setFirstResult()
    • getResultList(), getSingleResult(), executeUpdate()
  • Dynamic Query
    동적으로 생성하여 사용하는 query
    Query query = em.createQuery("select c from Customer c where name=:name");
    query.setParameter("name", name);
    List list = query.getResultList();;
    
  • Static Query
    Entity Class에 선언해 두고 필요할때 마다 불러 쓰는 query
    @NamedQuery 와 @NamedNativeQuery 가 있다.
    @Entity
    @NamedQuery(name="findCustomerByName", query="select c form Customer c where c.name=:name")
    public class Customer { //.... }
    
    Query query = em.createNamedQuery("findCustomerByName");
    query.setParameter("name", name);
    List list = query.getResultList();
    
  • An extension of EJBQL
    • Projection list
    • Explicit JOINS
    • Subqueries
    • GROUP BY, HAVING
    • EXISTS, ALL, SOME/ANY
    • Bulk UPDATE, DELETE

META-INF/persistence.xml

  • Entity Class를 패키징 할때 META-INF/persistence.xml 디스크립터가 꼭 필요하다.
  • persistence.xml은 관련 Entity Class들과 이에 대한 DB DataSource 설정 등과 같이
    Persistence-Unit에 대한 정보를 담고 있다.
  • Persistence-Unit은 Entity를 묶는 단위이며 이것은 하나의 DB에만 매핑된다.
  • Persistence-Unit의 UnitName은 EntityManager or EntityManagerFactory를 얻을때 사용된다.
  • persistence.xml 예제
     
    <?xml version="1.0" encoding="UTF-8"?>
    <persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence">
      <persistence-unit name="orderunit">
        <jta-data-source>jdbc/sample</jta-data-source>
        <properties>
          <!-- vendor-specific -->
          <!--
            <property name="toplink.ddl-generation" value="create-tables"/>
          -->
        </properties>
      </persistence-unit>
    </persistence>
    

JAVA SE 에서 JPA 사용하기

  • JAVA SE 환경에서 사용 할 때는 Application-managed EntityManager 만 사용할 수 있다.
  • JDBC 드라이버에 국한된 로컬(resource-local) 트랜잭션만 쓸 수 있다.
    • EntityTransaction API를 이용하여 직접 트랜잭션 컨트롤을 해주어야 함.
  • EntityManagerFactory를 얻는 방법 - javax.persistence.spi.Persistence Bootstrap API 사용
    EntityManagerFactory emf = Persistence.createEntityManagerFactory("orderunit");
    EntityManager em = emf.createEntityManager();
    //...
    EntityTransaction tx = em.getTransaction();
    tx.begin();
    em.persist(customer);
    tx.commit();
    //...
    em.close();
    emf.close();
    
  • META-INF/persistence.xml
    JAVA EE 환경과는 달리 모든 Entity 클래스를 명시적으로 나열해 주어야 한다.
    //...
      <persistence-unit name="orderunit">
        <class>orderapp.entities.Customer</class>
        <class>orderapp.entities.Item</class>
        <class>orderapp.entities.Order</class>
    //...
    

Spring 프레임워크와 통함

아래 문서에서 정보를 얻으시기 바랍니다
Spring과 Java Persistence API의 사용
The Spring Framework - Reference documentation

참고문헌


출처 - http://www.javajigi.net/pages/viewpage.action?pageId=5924



Posted by linuxism
,


2012/05/19 04:39

Entity와 Value Object Domain Driven Design

사실 이 두가지 개념을 어떻게 풀어 나가야 할지 참으로 난해하다. 왜냐하면 'Entity'와 'Value Object'는 개발자가 어떤 관점에서 바라보느냐에 따라 이리도 될 수 있고 저리도 될 수 있는 참 모호한 개념이기 때문이다.

Entity

일반적으로, entity란 존재하는 것, 즉 실체를 의미한다. 이 용어의 어원은 라틴어의 ens에서 나왔으며, 사물의 존재와 그것의 품질 사이에 구별을 짓는다. entity가 되기 위해 필요한 일은, 그저 존재하는 것이 전부이다. 어떤 것이 존재한다는 사실은, 그것이 다른 존재나 entity로부터 분리되어있음을 암시하는 듯하다. 프로그래밍이나 공학에서, 그리고 아마도 많은 다른 상황들에서, 이 용어는 명확한 사물이든, 이름이 붙여져 있지 않은 추상화된 아이디어든 관계없이 단일체를 인식하는데 사용된다.

by TERMS

굉장히 비슷한 성격을 갖고 있는 두 개념이기에 우선 공통부분을 살펴보고 왜 'Entity'와 'Value Object'가 구분될 수 있는지 살펴보자. 우선 'Entity'와 'Value Object'는 둘다 서비스 내에서 유일함을 인정받는 오브젝트이며 정확히 구분될 수 있는 식별성을 갖고 있다. 그리고 둘 다 하나 이상의 속성을 가질 수 있으며 이러한 고유 상태값은 데이터베이스에 기록되어 보존할 수 있다.

그 뿐만이 아니다.'Entity'와 'Value Object'는 서로가 서로를 참조하는 집합체가 될 수도 있는데, 'Entity'가 'Value Object'를 감싸안은 'AGGREGATE'(집합체)가 될 수도 있고 반대로 'Value Object'에서 'Entity'를 참조할 수도 있다.
이렇게만 놓고 본다면 둘은 굉장히 비슷한 성격을 가지고 있는데다 그 역할이 거의 흡사해, 구분 짓는 것의 의미가 없어보일지도 모른다. 실제로 많은 개발자들이 빈약한 도메인 오브젝트를 일괄적으로 'VO'라는 네이밍('Value Object'의 줄임말)을 짓는 것을 보면 이런 구분을 잘 이해하지 못하는 경우도 비일비재한 듯 싶다.

물론 빈약한 도메인을 활용하는 'MVC' 아키텍쳐에서 굳이 'Entity'와 'Value Object'를 구분지어줄 필요는 없으므로 불필요한 성격분류를 할 필요는 없다. 다만 도메인 주도 설계에 큰 관심이 있고 실제로 서비스에 적용해보고자 할 때에는 둘의 성격을 반드시 구분지어줄 필요가 있다.

Value Object

'ValueObject'는 'J2EE'의 패턴 중 하나로 일정한 입력값을 정리하여 보관 및 유지해 두는 오브젝트이다. J2EE 패턴에서는 'Trasfer Object'라고도 하는데 본래에는 'EJB'에서 원격 메소드 호출의 회수를 줄임과 동시에 퍼포먼스의 저하를 막는 목적으로 개발된 패턴이다.

그렇다면 본격적으로 둘의 성격이 어떻게 구분지어질 수 있는지 살펴보자. 궁극적으로 둘의 성격은 하나의 조건으로 정확하게 분리될 수 있는데 그 조건은 다름 아닌 연속성이다. (연속성이란 식별 가능한 오브젝트의 속성들이 필요에 따라 변경될 수도 있다는 것을 의미한다.)

보다 빠른 이해를 위해 카페에서 커피잔을 들고 있는 한 남자를 상상해보자. 우리는 이 남자와 커피잔 모두 구분지어 질 수 있는 (식별성을 가진) 오브젝트가 될 수 있다는 사실을 알 수 있다. 여기서 너무 디테일한 시간적인 연속성은 제외하고 남자와 커피잔을 코드 상 오브젝트라 인지하면서 바라보았을 때, 남자라는 오브젝트는 입고 있는 옷이라던가 헤어스타일, 기분 같은 것들이 지속적으로 변경될 수 있다는 점을 알 수 있다.

반대로 우리는 커피잔이라는 오브젝트가 변할 수가 없다는 사실 또한 알고 있다. (물론 '변할 수 있다'라는 주장도 가능하다. 일단 필자의 이야기를 끝까지 들어보자!) 커피잔이라는 오브젝트는 커피잔 자체가 사라지고 새로운 커피잔 오브젝트가 등장하지 않는 우리가 앞서 생각했던 커피잔이 변경될 소지는 거의 없다.

그러므로 우리는 여기서 연속성이라는 성격으로 두 오브젝트, 남자와 커피잔의 성격을 분리할 수 있게 되었다. 남자라는 오브젝트는 연속성을 가지고 있으므로 우리는 실체가 존재한다하여 'Entity'라고 정의할 수 있다. 그리고 커피잔은 변경될 수 없는 불변의 가치를 가지고 있으므로 'Value Object'라고 부를 수 있다.

문제는 이러한 관점이 극히 주관적이라는 것이다. 만약 당신이 이 커피잔을 시간이 흐름에 따라 부식되는 상태를 표현한다거나 담겨있는 커피 양의 차이를 세밀하게 표현하고자 하는 이유를 들어 커피잔을 'Entity'라 주장할 수도 있다! 그리고 이러한 이유는 상황에 따라 매우 타당하게 받아들여질 수 있을 것이다.

결론적으로 보자면 위와 같은 분류가 가능했던 까닭이 단지 필자가 전체적인 상황을 남자라는 오브젝트를 중심으로 이해했기 때문에 커피잔이 'Value Object'가 됬을 뿐, 실상 둘다 'Entity'가 될 수도 있고 'Value Object'가 될 수 있다.

그렇다면 두 오브젝트는 도대체 어떻게 분리해야 할까? 이것은 어디까지나 설계자 스스로의 역량이다. 사물의 본질을 잘 이해하고 이것이 변하지 않는 고유의 성질을 갖고 있다면 설계자 스스로가 판단하여 'Value Object'로 분류하면 되고 연속성을 가지고 있다면 'Entity'로 분류하면 된다.

가끔 설계 단에서 하나의 오브젝트를 놓고 'Entity'와 'Value Object'를 구분짓는 논쟁이 벌어지곤 하는데… 솔직히 이건 정말 무의미한 소모전이다. 왜냐하면 대부분의 경우, 서로 주장하는 이유가 맞는 말이기 때문이다. 아마 역사상 '엄마가 좋아 아빠가 좋아' 질문 다음으로 가장 짜증나고 답답한 논쟁이라고 생각해도 될 것이다.

혹여나 개발자 사이에서 이러한 논쟁의 시비가 붙는다면 일단 서로의 이유는 잠시 접어두고 서비스의 성격만 고려하여 결론짓는 것이 가장 유리하다. 괜히 앞으로 있지도 않을 디테일한 성격을 들어 이것을 'Entity'라 지정할 필요도 없고 너무 값을 'static'하게 가져가려고만 해서도 안된다. 이런 성격의 분리는 서비스의 성격만 고려하더라도 분명한 결론에 도달할 수 있다.

마지막으로 기술적인 이야기만 짤막하게 하고 넘어가자면 'Value Object'는 연속성을 가지지 않는 오브젝트이므로 내부 프로퍼티를 final로 지정하여 변경을 불가하는 것이 좋다.



출처 - http://springmvc.egloos.com/624397






2012/08/25 22:56

Service Domain Driven Design

도메인 주도 설계에서 말하는 'Service'란 근본적으로 'Entity'나 'Value Object'와 다르지 않다. 물론 다르지 않다는 개념 자체가 'Service'가 데이터 단위로 활용된다거나 일정한 값을 가지고 있다는 뜻은 아니고 동일하게 도메인 레이어 (또는 계층)에서 'Entity'나 'Value Object'와 같은 오브젝트 따위로 취급되는 것을 일컫는다.

이게 개념은 말처럼 쉬운데 사실 기존의 개발풍토에 익숙한 개발자라면 실질적으로 깨닫고 코드로 옮기기까지 매우 많은 시행착오를 겪을 수 있다. 왜냐하면 보통 개발환경에서 통념 상 'Service'는 하나의 계층으로 'DAO' 계층에서 작성된 오브젝트를 근간으로 크리티컬한 로직을 작성하거나 처리하는 용도로 사용되곤 하지만 도메인 주도설계에서 'Service'는 객체의 행위 또는 활동을 표현하는 단위로 사용되기 때문이다.

필자가 겪은 시행착오를 하나 살펴보자. 다음의 코드는 처음 서비스 오브젝트를 만들 때 습관적인 개발방식에 따라 작성한 서비스 인터페이스이다.
public interface UserService {
User add(User user);
User update(User user);
void remve(User user);
...
}

좀 급하게 만드느라 약간 'DAO (Data Access Object)'스런 코드를 만들긴 했지만 어찌됬든 일반적인 개발환경의 관점에서 바라보자면 위의 코드는 충분히 서비스 계층에서 끌고 나갈 수 있는 코드이기도 하다. 이런 코드를 도메인 주도설계에서는 맹목적인 서비스라고도 하는데 서비스 자체가 하나의 도메인 오브젝트('Entity' 또는 'Value Object')에 종속적이고 행동이나 활동에 근간을 두지 않고 'Management'의 성격을 띄게 때문이다.

도메인 주도설계에서는 이러한 'Management', 즉 생존주기와 관련된 것들은 도메인 스스로가 결정하게 되어있으며 이것을 기존의 코드 방식처럼 다른 서비스 또는 'DAO'에 위임하는 것을 지향하지 않는다.

그렇다면 위의 코드를 도메인 주도설계 목적에 맞게 바꾸어 본다면 어떻게 바뀔 수 있을까? 아마 아래와 같이 작성할 수도 있겠다.

public interface AccountService {
User register(final String email, final String password);
User unsubscribe(final User user);
...
}

필자는 위의 'UserService'를 'AccountService'로 바꾼 다음에 그 성격을 사용자의 생존주기에 대한 행위를 기준으로 작성하였다. 서비스를 작성할 때 주의해야 할 점은 자신이 만든 메서드를 직접 소리내어 말해보았을 때 이것이 어색함없이 받아들일 수 있어야 하는데 도메인 주도설계가 줄곧 말하는 유비쿼터스 언어의 특성을 살리기 위해서도 그러하고 기본적으로 좋은 메서드를 만드는 좋은 방법 때문이기도 하다.

'register' 메서드와 'unsubscribe' 메서드를 소리내어 말해보자. 각각 "이메일과 패스워드로 사용자를 등록합니다.", "사용자를 탈퇴시킵니다."라고 말할 수 있겠다. 그리고 이런 방식으로 서비스를 작성하다보면은 서비스가 특정 도메인에 종속되지 않고 독립적인 성격을 띈 도메인 오브젝트로 만들어낼 수 있다.

물론 도메인 주도설계 방식으로 'Service'를 만들 때 주의해야 할 점은 'Entity' 또는 'Value Object'에서도 충분히 다룰 수 있는 기능들을 모조리 'Service'로 제작해서는 안된다는 것이다. 보통 많은 개발자들이 도메인 오브젝트에 어떠한 로직을 집어넣기 보단 빈약한 도메인 오브젝트 방식(프로퍼티와 'get...', 'set...' 으로 캡슐화만한 오브젝트)으로 작업해오다보니 도메인에 어떠한 로직이 들어가야 올바른지 헷갈려 하는 경우가 많다. 게다가 도메인 주도설계는 아키텍터, 또는 어플리케이션 특성에 따라 매우 주관적인 코드가 될 수 있으므로 이런 로직들에 대해서는 개발자들 간의 충돌이 일어날 수도 있고, 어떠한 부분에서는 매우 애매모호한 경우가 발생하여 개발에 차질을 빚기도 한다.

이러한 상황에서 명확한 기준은 가급적 'Entity', 'Value Object'. 특히 'Root Entity'에 대해서는 'Aggregate' 경계 내의 오브젝트에 대한 통제 권한을 스스로 관리하게끔하고 그 이외의 서로 다른 'Aggregate'를 가진 도메인 끼리에 대해서는 서비스로 제작하여 처리하는게 올바른 듯 하다.

by 거짓말


출처 - http://springmvc.egloos.com/726522

Posted by linuxism
,