Repository와 DataMapper의 책임 (w/o ORM)
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으로 숨기는 것도 가능하지만 역할은 거의 동일하다
참고) 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는 어떻게 해결하고 있는가?
- N+1 Select 문제는 fetch join으로 해결
- https://www.baeldung.com/jpa-mapping-single-entity-to-multiple-tables
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이기 때문
참고
- https://stackoverflow.com/questions/27996119/what-exactly-is-the-difference-between-a-data-mapper-and-a-repository
- 내가 생각하는 DDD 관점에서의 정의와 같다.
- https://proandroiddev.com/the-real-repository-pattern-in-android-efba8662b754
- Mapper에서 DataSource에 접근하는게 아니라 단순 변환(DTO <> Model)만 하고 있다. 접근은 Repository에서 한다. 그 부분이 위 정의와 조금 다르다.
- https://martinfowler.com/eaaCatalog/dataMapper.html
- Repository에 대한 내용은 없으나 DataMapper의 책임에 대한 단서를 얻을 수 있다.
- https://www.martinfowler.com/eaaCatalog/repository.html
- 여기서 얘기하는 Repository는 이 글에서 얘기하는 DataSource 추상화를 담당하는 Repository와는 다른 의미를 가진다.
- [MyBatis] 객체 안의 객체 매핑하기 (ResultMap과 DTO)
- DTO 사용하는 것의 장점
- 이 글에서와 반대로, 1개의 select, DTO로 2개 이상의 Domain Model을 가져와야 하는 경우
- 엔티티 클래스 설계와 퍼시스턴스 프레임워크 - benelog
- 좋은 글. DTO와 Domain Model에 대해서 까지.
- DAO와 REPOSITORY 논쟁 - 조영호님
- 도메인 레이어가 TRANSACTION SCRIPT 패턴으로 구성되고 퍼시스턴스 레이어에 대한 FACADE의 역할을 하는 객체가 추가될 때는 거리낌 없이 DAO라 부른다.
- 도메인 레이어가 DOMAIN MODEL 패턴으로 구성되고 도메인 레이어 내에 객체 컬렉션에 대한 인터페이스가 필요한 경우에는 REPOSITORY라고 부른다.
- https://docs.spring.io/spring-data/jdbc/docs/current/reference/html/#jdbc.domain-driven-design
- DDD - aggregate와 repository에 대해서
- [DB] ORM의 근본적인 장점
'System Design > Layered Arch' 카테고리의 다른 글
[마틴파울러] Layering 관련 글 모음 (0) | 2022.03.13 |
---|---|
Domain Model에 대해서 (0) | 2022.03.11 |
[Spring] MVC Layered Architecture : Controller와 Service의 책임 나누기 (0) | 2020.07.06 |
[Spring] MVC Layered Architecture : Map 보다 Data Class 사용해야 하는 이유 (0) | 2020.06.25 |
[Spring] MVC (0) | 2017.11.11 |