사전 지식
JDBC로 Aggregate Root의 Repository를 설계하면서 도메인 모델 순수성을 지키려고 하다 보면, JDBC로는 도메인 모델 생성/수정/삭제 로직으로 영속성 데이터를 변경할 수 없다.
- 우리가 JPA의 Dirty Checking 에 익숙해져있기에 눈치채지 못하지만, 일반적으로 도메인 모델이 수정되었다고 해당 사항이 즉시 반영되진 않는다.
- JPA는 (양방향+CascadeType.PERSIST+orphanRemoval = true의 경우) 리스트에 추가/삭제하는 것만으로 객체의 생성/삭제 로직을 자동으로 SQL 문으로 만들어 쿼리해준다.
- 하지만, JDBC의 생성/삭제는 Dirty Checking을 통한 자동 쿼리 생성이 불가능하고, 결국 어느 메소드를 통해 SQL을 실행시켜줘야 한다.
참고 1) 싱글 스레드 기반의 Node.js의 대표적인 ORM인 TypeORM은
JPA처럼 멀티스레드 환경에서 동작하는 방식이 아니기 때문에 Transaction 별로 1차 캐시를 갖기 어렵고,
변경 감지 로직 자체가 스레드를 지나치게 오래 점유하기 때문에, 변경감지가 지원되지 않는다.
따라서, save() 호출 시, DB에 다시 select 쿼리를 날려, 예전 DB 버전과의 차이를 직접 대조하는 방식을 사용한다고 한다.
참고 2) JDBC라도 도메인 로직 내에 존재하는 생성/수정/삭제 메소드는 검증 로직을 위해 만들어두는게 좋다.
그렇다면, 해당 영속성 저장소에 SQL을 어떻게 보내는 것이, 도메인 모델의 순수성을 해치지 않으면서 애그리거트 루트의 Repository를 순수하게 유지할 수 있는 방법이 될까?
JDBC에서 child Entity를 생성/수정/삭제하는 방법
1. 애그리거트 루트를 save() 시, 해당 메소드 내에서 모든 컬렉션을 제거하고, child Entity를 다시 insert 하기
- Spring Data JDBC가 기본으로 사용하는, 단순하면서 확실한 방법이다.
- 애초에 해당 배치 연산이 부하가 되는 경우(5000~1만 개 이상의 child entity)라면, 애초에 child entity로 설계된 것이 잘못됐다는 관점이다.
2. 애플리케이션 레이어에서, 직접 Repository에 정의된 child Entity의 생성/수정/삭제 메소드를 호출하기
이렇게 되면, 애플리케이션 레이어가 어느정도 비즈니스 로직을 담당하게 된다.
따라서, 리포지토리에 해당 child Entity를 직접 변경하는 메소드가 필요해진다.
3. 수정된 데이터의 경우, 이벤트를 기록하여 인프라에서 변경을 진행하도록 만들기.
해당 이벤트의 구독자인 인프라 레이어의 핸들러는 이벤트 핸들링을 동기적으로 실행하게 함으로써, 도메인 레이어는 영속성 변화를 알지 못하지만, 인프라 레이어에서 해당 작업을 받아 처리하게 만들 수 있다.
또한, 이는 도메인 레이어에 존재하는 Repository 인터페이스도 Child Entity의 생성/수정/삭제 로직을 알지 않아도 되게 만들어준다.
하지만, 긴 설명에서 느껴지듯이 구현 복잡도가 높고, 한 트랜잭션 내에서 동작해야 하는 일이 이벤트를 통해 실행됨으로 인해, 코드가 비-직관적이 되는 단점이 존재한다.
- Spring Data Commons는 save() 메소드 호출 시, 애그리거트 루트에 기록된 모든 이벤트를 발행한다.
- 이 방식은, Child Entity를 위한 DAO가 따로 필요해진다.
팩토리를 이용해 애그리거트를 구성하는 방법(Re-Hydration)
그렇다면, 팩토리를 이용해 영속성 저장소에 있던 애그리거트 내 객체들을 전부 가져오는 방법은 뭘까?
1. 애그리거트 루트 엔티티에 해당 child entity를 추가하는 메소드 넣어두기.
장점
- 구현이 간편하다.
단점
- 도메인 로직에 영속성 인프라를 위한 메소드가 추가된다.
- 이 메소드는 infra 계층에서 호출되어야 하기 때문에, 반드시 접근제어자가 public이 되어야 한다.
- package-private, protected로도 해결할 수가 없다.
- 따라서, 남용될 여지가 있기 때문에 주석 혹은 어노테이션으로 다른 동료들이 사용하지 않아야 함을 알 수 있도록 설명을 추가해두어야 한다.
2. infra 레이어에서 Reflection API을 이용해 직접 주입하기.
정말 "잘" 구현하면 우아한 코드가 되지만, 정말 잘 구현하기 어려운 방법이다.
장점
- 클린 아키텍처 원칙을 준수하며, 도메인 모델의 순수성이 보장된다.
- The Dependency Rule
- Frameworks and Drivers
- 단적인 예로, infra 레이어로 간주하는 스프링 프레임워크는 리플렉션을 적극 사용하고 있다.
단점
- 인프라 레이어가 컴파일 타임에 잡히지 않는 도메인 객체의 "필드명"에 의존하게 된다.
- 상위 모듈에서 리플렉션이 사용되지 않도록 조심해서 사용해야 한다.
- 실행 경로 상 리플렉션 호출이 전체 TPS·p99 Latency 와 같은 요구사항을 만족시켜야 한다.
- 리플렉션 전/후에 철저한 로깅을 수행해, 디버깅 문제가 없도록 해야 한다.
나는 이 두가지 선택은 팀 내 그라운드 룰로 명확히 정의된 뒤 사용되어야 한다고 생각한다.
'Article > 도메인 주도 설계 이해하기' 카테고리의 다른 글
도메인 이벤트 vs. 애플리케이션 이벤트. 그리고 이벤트의 설계 방법 (0) | 2025.04.28 |
---|---|
도메인 이벤트와 State Machine 시스템 설계 - 강아지 키우기 (0) | 2025.04.05 |
도메인 서비스 - DDD의 적용, JPA 엔티티와의 비교 (0) | 2025.04.03 |
DDD 트릴레마 - 도메인 모델 완전성 vs 도메인 모델 순수성 (0) | 2025.04.01 |
객체지향 기초 - SOLID 원칙 (0) | 2025.03.07 |