예제 : Member

 

 

traditional Java EE 패턴에서의 정의

  • Spring에서 제공하는 @Repository 는 DAO 의미를 지닌다. (javadoc 참조)
  • MyBatis에서 제공하는 @Mapper sql mapper 의미를 지닌다.
  • 따라서 layer는 아래와 같이 표현되어야 한다.

```kt

@Service ---> @Repository ---> @Mapper ----> mapper.xml || annotation-string

               DAO             sql mapping

```

 

  • 관습적으로 @Repository와 @Mapper를 동일한 layer로 간주하는 경우가 많은데, 서로 다른 layer로 간주해야 한다.
  • "dao와 mapper의 차이" 로 검색해보면, 마치 두 개념이 같은 추상화 수준이며 서로 양립 불가한 것 처럼 보이는 글도 많다. 
  • 양립 불가한 것 처럼 보이는 설명을 도식화 하면 아래와 같다. (Repository와 Mapper 둘 중 하나를 골라서 써라. 라는 뉘앙스로 보인다)

```kt

@Service    --->    @Repository      --->    mapper.xml

                sqlSession.select("mapper.xml")

ㄴ 이는 엄밀히 말하면 layer 생략은 아니지만 그렇게 보일 수 있다.

 

@Service    --->    @Mapper          --->    mapper.xml

ㄴ Repository layer가 생략됐다. 좋지 않다.

```

 

  • 그러나 둘은 양립 불가한 개념이 아니라, 둘 다 사용해야 하는 별도의 layer다.
  • 이렇게 layer를 생략하는 것은 좋지 않다. 아래 @Mapper를 직접 사용하는 케이스는 Repository layer를 생략했으므로 해당 layer에서 수행해야 하는, 쿼리로 숨길 수 없는 data structure, schema에 대한 추상화를 Service layer에서 수행해야 한다.

 

Repository layer & DTO는 아래와 같은 물음에 대한 해답이 된다.

  • Repository
    • member_half_tbl, member_the_other_half_tbl 가져 올 때는 join으로 간단하지만, insert/update/delete 할 때는? (굳이 프로시저 써야 할까?)
    • DTO를 사용한다면, Domain Model <> DTO 변환은? 비즈니스 관심사가 아닌데 Service layer에서 해야 할까?
    • persistence io 객체인 DTO field에 대한 null check 등 validation은? 쿼리에서 하기는 껄끄럽고, Service에서 하자니 뭔가 안맞는 경우는?
  • DTO
    • 반대로 성능 때문에 join해야 하거나, join 하지 말아야 하는 경우는?
    • 배치에서 N+1 select를 피하기 위해 여러 비즈니스 모델에 사용되는 데이터를 한 번에 가져와야 한다면?
    • DTO가 없다면 query가 변경될 때 Domain Model도 변경되어야 한다
    • 특히 DTO 없이 Domain Model인 Member를 그대로 사용해 persistence io 하는 경우 자주 보이는 안티패턴이 있는데, [`` 불완전한 member(반쪽) + 불완전한 member(나머지) = 완전한 member``] 를 만드는 케이스다.
    • 참고 ) [MyBatis] 객체 안의 객체 매핑하기 (ResultMap과 DTO) 방법 사용해서 DTO를 xml 단의 ResultMap으로 숨기는 것도 가능하지만 역할은 거의 동일하다

 

 

 

Repository와 Mapper 둘 다 사용

 

참고) JPA를 쓴다면?

  • JPA를 쓰는 경우는 일반적으로, Domain Model에 JPA annotation을 직접 붙이기 때문에 persistence 객체와 Domain Model이 합쳐진 형태다. 따라서 보통은 별도의 persistence DTO가 필요하지 않다. (하지만 항상 그런 것은 아니다)
    • Domain Model에 대해서 
    • Domain Model은 POJO로 유지하는 것이 좋지만, JPA 애너테이션을 붙이는 것이 Domain Model의 설계를 변경하도록 압력을 가하지는 않으므로 이로 인해 모델의 책임을 해치게 될 가능성은 낮다.
    • 그렇다고 항상 JPA annotation을 Domain Model에 붙여야 하는가? 그렇지 않다. 일반적으로 Domain Model에 붙이지만, Domain Model에 JPA annotation을 끼워 맞추기 위해 Domain Model에 변형이 가해져야 하는 경우는 별도의 persistence DTO class를 생성하고 여기에 @Entity를 붙이는 것이 더 낫다. (여기서 더 낫다는건, 상기한 관리하기 더 쉬운 시스템이 된다는 의미다.)
    • 이렇게 어떤 Domain Model에는 @Entity가 붙어있고, 어떤 Domain Model은 POJO이면서 별도의 persistence DTO @Entity를 이용해 입출력하게 되면 시스템 내 일관성은 떨어지게 된다. 하지만 이는 어떤 persistence framework를 써도 피할 수 없다. 일반적으로 가능한 domain model을 직접 DB 입출력에 사용하는 것이 선호되지만, domain model과 persistence 간 불일치를 시스템 전역에서 아예 피하는 것은 불가능하기 때문이다.
  • 상기한 문제를 JPA는 어떻게 해결하고 있는가?

 

DDD 관점에서의 정의

Repository

  • Repository는 interface다.
  • 어디에 저장되는가/어디서 불러오는가를 추상화 하는 책임을 지니고 있다.
    • 즉 각기 다른 DataSource(file, DB, network, ...)를 추상화 하여 어디서 가져오든 신경쓰지 않도록 한다.
  • @Repository  javadoc에서 DDD-style repository를 언급하고 있다.

 

Data Mapper

  • app 단의 Object를 해당 DataSource의 데이터로 Mapping 해주는 책임을 지닌, DataSource specific한 구현체다.
    • app 단의 data structure와 DataSource의 data structure가 1:1로 딱 들어맞지 않을 수 있기 때문에 이에 대한 변환 layer가 필요하다. (예제 : Member와 같은 케이스. 또는 상속의 개념이 DB에는 없다 등등.)
    • 따라서, Data-driven development에서 벗어나 Domain-driven development 하기 위해서 꼭 필요한 layer다.
    • 자연히 database access code 등을 포함하게 된다.
  • Repository 내에서 Data Mapper를 사용한다.
    • Repository는 어떤 DataSource를 사용할지는 결정 할 수 있지만, 이 데이터를 어떻게 Mapping할지 까지는 모른다.
    • Repository에서 Mapper를 불러와 사용한다는건 즉, 해당 Mapper와 연결된 DataSource를 사용한다는 의미이고, 데이터를 어떻게 Mapping할지는 Mapper의 책임이다.
  • MyBatis @Mapper 애너테이션은, 여기서 의미하는 Data Mapper 보다는 작은 의미의 Mapper다.
    • 쿼리만 제어 가능하므로 한계가 있다. 완전한 기능의 Data Mapper로 동작하기 위해서는 바깥의 wrapper가 필요하다.

 

정의 대로 라면, Domain Model을 파라미터로 받아 DTO로 변환하는 작업은 Mapper에서 수행하는 것이 더 알맞아 보이지만, 실제로는 Repository에서 수행하는 것으로 절충하는 것이 더 괜찮은 경우가 있다.

  • 여러 data source에서 얻어온 데이터를 조합하여 하나의 Domain Model을 만들어야 하는 경우, 이를 조합할 수 있는 것은 Repository이기 때문

 

참고