DDD-Lite@Spring
세미나 내용이 유익하다고 판단되, 동영상을 보면서 정리한 내용입니다.
2번째 시청하면서, 정명주님이 생각하는 DDD에 대한 개념이 잘 녹아있고, 간결하게 DDD 에 대해서 설명해주셔서 정리하고자 합니다.
유지보수 사항에서 항상 개발자는 고통받습니다.
왜 그럴까요?
왜냐하면서, 애플리케이션이 엄청 복잡하기 때문입니다. 크게 2가지로 복잡합을 구분해보면
- 우리가 해결해야 할 문제 자체
- 우리가 사용하는 기술과 도구
위기가 어떻게 오는가?
빠르고 간단하게 일정 드리븐 개발을 하다보니까-
빠르고 간단하게 개발했는데, 요구사항이 변경되고 점점 복잡해지면서
빅뱅식 개편을 많이 하는데, 잘못된 레거시에 의존하기 때문에 이 순환이 반복됩니다.
원인은 빠르고 간단하게 맞지않는 접근법을 이용하다보니까 계속 문제가 발생합니다.
어떻게 이 위기를 극복해야 될까?
DDD 에서 어떻게 해결하는지 살펴보자.
문제 공간을 > 해결 공간으로 변경해야 합니다.
해결 공간으로 뽑아내는 것을 전략적 패턴이라고 한다.
여기서 일반
, 지원
, 핵심
의 의미는 무엇이냐면,
일반
은 계정, 메일 등 대부분의 어플리케이션에 존재하는 기능을 일반 도메인지원
은 핵심 도메인을 도와주는 서브 도메인을 의미하는데, 이커머스를 예로 들면- 상품 조회, 배송 서비스 등이지원 서브 도메인
에 속합니다.핵심
은 이커머스를 예시로 하면,결재
와 같이 가장 힘들고, 어려운 도메인을 핵심이라고 합니다.
일반
도메인은 간단하기도 하니까, 제 3의 서브파트를 사서 적용하기도 합니다. 그럼 강사가 말하는 DDD-Lite 는 어디에 적용해야 하는가? 바로 핵심
도메인에 적용해야 됩니다.
DDD 의 전술적 패턴은 큰 그림을 그리는 것으로, 전략적 패턴은 작은 그림을 그리는 것이다. 전략, 국지적인 전술 패턴.
DDD 의 전술적 패턴은 패턴 언어들의 Collections 이라고 생각하면 됩니다.
에릭에반스의 DDD에 위 그림이 있습니다.
이제부터는 진짜 예시를 통해서 알아봅니다.
지식 탐구부터 시작합니다. 요구분석으로 하면서 모델링을 점차 고도화 시킵니다.
설계- 구축, 설계-구축 하는 것이 지식 탐구입니다.
강사님의 주요 도메인인 위키를 통해서 DDD-Lite 를 익힙니다.
Dooray 는 일반
, 지원
, 핵심
으로 나눠지고, Wiki 는 위키 페이지(문서) 들의 컨테이너를 의미합니다.
회색영역이 프로젝트이고, 일반으로는 계정, 검색이 있습니다.
관계를 파악하는 것이 중요합니다.
글로 써져있던 부분을 도식화 했습니다. 여기서 서비스 경계로 다시 도식화화면
여기서 핵심도메인 Wiki 에 집중해서 Wiki-Page 개념이 먼저 드러난다.
Entity로 명시적인 부분을 먼저 추출할 수 있습니다.!
데이터 드리븐하게 하면 속성에 집중
하게 되지만, DDD 하게 되면 행위
가 도메인을 명확하게 정의되어야 합니다. 이렇게 되면 도메인이 풍부해진다.
명세를 조금더 살펴보면 아래와 같습니다.
이 명세를 만족하기 위해서 아래와 같은 Entity가 만들어집니다.
여기서 create 는 static 으로 표현됩니다.
다름에 다른 버전 이력을 관리하는 요구사항이 추가되었다면?
행위를 먼저 생각하는게 중요하다고 했지만, 관계를 먼저 생각해보는것도 나쁘지 않은 방법이라고 생각한다.
엔티티는
- 식별가능하고,
- 생명주기가 있고,
- 행위가 우선한다.
- 행위와 그거와 관련된 속성을 가지고 있다.
그래서 응집력이 중요한 패턴이다.
ValueObject는 식별성을 가지고 있지는 않지만-불변으로 구현되서 부수효과가 없고 행위를 표현할 수 있습니다. 아주 최소한의 모델링을 할 때 도움이 되는 패턴입니다.
값객체를 찾는 냄새는 접두어가 동일하는 부분에서 입니다.
그럴 경우 값객체로 단순화 시키는 것을 추천합니다.
값객체까지 이제 알았고, 다음 명세서를 살펴봅시다.
첨부파일
을 추가하고, 또는 참조자들
이나본문 멘션
을 할 수 있습니다.
page 에 PageUser, PageFile 이 발생
희색 점선이 Aggregate 입니다.
도메인 모델에 불변식을 유지하는 경계가 됩니다.
데이터 변경의 단위가 됩니다.
이는 곳 DB로 옮기면 트랜잭션의 단위가 됩니다.
분산환경에서는 Lock 의 단위가 되기도 합니다.
한글로 하면
집합체
라고 합니다.
최초의 설계시 위와 같은 어그리게잇을 가졌는데, 도메인 모델 정제를 통해서 아래와 같이 변화되었습니다.
이유는, 페이지 유저 같은 경우 항상 묶여 다닌다. 그러나 PageFile 의 경우, 파일 하나만 다운로드 받게 해줘
와 같은 유스케이스가 생기면서 변화가 되었다.
AggregateRoot 를 통해서 무조건 접근해야 합니다. 만약 PageHistory
가 PageUser
에 접근해야 한다면, Page
에 먼저 접근해서 접근해야합니다.
Aggregate 를 잘 설계하면, 지속적 도메인 탐구를 통해
좋다.
이를 JPA로 구현하면
낙관적 오프라인 Lock 을 사용합니다.
pageUsers
에 영속성 전이를 하기 위해서 CascadeType.ALL 을합니다.
TODO Spring data JDBC 에서는 어떻게 할까?
Setter 가 아니라 의미있는 메소드로 표현한다.
본문이 변경되면 검색도 변경되어야 하는데, 어떻게 구현할까?
이벤트 방식과 배치식 방식이 있는데, 이벤트 방식으로 했습니다.
추가명세가 접수되었는데, 어떻게 처리했을까?
페이지 변경 내역
은 Wiki 서비스가 내부에서 알아서 하면 될 것 같습니다.
두레이 스트림에는 이벤트를 발행하면 될 것같아요.
여기서 Domain Event 가 발생합니다.
발행-구독 모델로 지원하고,
이벤트 후속 구독 작업은 트랜잭션에 포함되지 않습니다.
그럼 이벤트를 왜 쓰는가? 바로 가장 큰 목적 결합도를 아주 드라마틱하게 낮추기 위해서 사용합니다.
이벤트를 구독하는 입장과, 발행하는 입장을 나눠서 분할 정복하는 것이다.
흐름도를 통해 이해해보자.
Command 타입에서 시작하는데,
ApplicationService
는
- 파사드로 구현되서, thin 서비스로,
- 파사드로 구현되는데-
- 보안등의 애플리케이션 관심사는 처리하되- 도메인 로직은 절대 존재하지 않습니다.
단지, 도메인 모델을 로딩해서 거기에 도메인 로직을 위임한다. 그래서 파사드이다. 그래서 Thin 한거고,
그리고 ApplicationService 의 PageCommandService.changeContent 는 유즈케이스의 표현이다. 유즈케이스 하나가 메소드 하나이다.
Domain에 changeContent() 가 일어나면, 내부에서 마지막으로 도메인 이벤트를 등록합니다.
이벤트를 생성에서 어딘가에 정보를 전달합니다.
이벤트 프로세서가 받아서, 구독자에게 바인딩합니다. 이 순간에 트랜잭션은 종료됩니다. 그러나 trade-off 로 뒤에 종료되게 할 수 있습니다.
이때 구독하는 책임은 ApplicationService 의 책임입니다. 각각 필요에 맞게 구현합니다.
- 페이지 이력남기고
- 알림보내고
- SearchIndexable 에 보내서
Pub-sub 은 왠만하면 관심사로 분리합니다.
DomainEvent 를 구현하는 방식으로는 2가지 방식이 있다.
Spring-Data 에 의존할 경우에는 Trigger 에 의존하게 됩니다. Save method 를 사용할 때 발생합니다.
이벤트까지 봤고,
이제 2개를 비교해서 살펴봅시다.
그러나 Domain-Driven 하게 되면?
최소한의 의존도만 가집니다.
단순 DTO 수준으로 존재합니다.
이전에 말한 것과 같이 트랜잭션에 포함되면 안되기 때문에 이전 트랜잭션이 끝나고 실행될 수 있도록 설정하고, 새로운 트랜잭션을 만들도록합니다.
그리고, ApplicationService
이기 때문에 도메인로직이 없이 동작되어져야 합니다. 도메인 서비스에 위임해서 처리해야합니다.
도메인 서비스란?
무상태로서, 도메인을 처리하는 서비스로서 행위만 가진것을 의미합니다. 또한 속성도 가지지 않습니다.
여기서 docs 를 달았는데, 그 이유는 발행-구독의 단점은 추적이 안되기 때문입니다. 누가 발행을 했고, 누가 구독했는지 알 수 없습니다.
Spring 에 대한 Repository 에 대한 고찰입니다.
Repository 원래 데이터 영역에 있어서 POJO에 있어야 합니다. 그리고 구현에 대해서 만약 JPA와 비슷하다면 Trade-off 할 만한 가치가 있습니다.
이제 마지막으로 아키텍쳐와 모듈을 이야기합니다.
Repository, Entity 등 어디에 위치해야 하는지 감이 오지 않습니다.
레이어드 아키텍쳐는 치명적인 단점이 있습니다.
Primary Adapter 는 Secondary Adapter 로 변화되어집니다.
Primary Adapter는 Driving 하는 영역
Secondary Adapter는 Binding 되는 영역입니다.
기술에 대한 Port And Adapter
패턴이라고 합니다.
일반적으로 Primary Adapter 는 요청을 주면 응답을 받는 구간이므로, REST API, 웹 페이지, CLI 배치 등의 포함되고,
Secondary Adapter
는 기술적 처리의 통로입니다. 그래서 영속성을 담당하는 DB, Redis Cache, RabbitMQ 같은 메세징 인프라 또는 다른 서비스로 호출까지 포함됩니다. 기술적으로 바인딩되는 부분을 의미합니다.
본문을 수정하는 일을 할 경우의 도메인 위치에 대해서 설명합니다.
여기서 중요한건 헥사고날의 핵심은 바깥에서 안쪽으로 모여야 한다. 의존성의 화살표입니다.
계층형 아키텍쳐의 단점은 무엇이냐면? 결국에는 도메인이 인프라에 의존하게 됩니다. 이 말은 즉, 도메인 관심사와 기술적 관심사가 섞이게 된다는 것을 의미합니다.
그러면 정말 복잡성이 폭발적으로 증가할 수 있습니다.
그때 헥사고날아키텍쳐를 적용하면 도메인 관심사와 기술적 관심사를 완전히 불리하게 되고, 애플리케이션 간에도 분리하게 됩니다.
이번에는 이벤트를 구독하고 나서의 상태는 아래와 같습니다.
똑같이 의존성이 바깥에서 안쪽으로 의존하게 됩니다.
위 장표에서 보면, Notification Service
를 구현하는 DoorayStreamAdapter
가 있습니다. 이것을 잘 생각해보면 바로 DIP 입니다.
이렇게 함으로써, 도메인 모델의 순수성을 유지할 수 있게 됩니다. (망을 구축한다.)
모듈이란?
자바로 생각하면 패키지 구조입니다.
adapter, Application, domain.model 이렇게 3가지로 구분하는걸 추천합니다.
Presentation
은 표현영역으로, CLI 이나 웹 같은 REST API 처리하는 각종 컨트롤러가 위치하게 됩니다. Infrastructure
는 기술을 위치하는 것입니다. ELK, JPA, RabbitMQ 등을 의미합니다.
그리고 Service
는 Account, Project 등이 위치하게 됩니다.
여기서 Configuration 은 어디에 두면 좋을까? Framework 의 기술이므로, adapter 안쪽에 놓는 것을 추천합니다.
마지막으로 RabbitMQ
는 특별합니다. 나가는 통로도 되지만 들어오는 통로도 됩니다. PRIMARY 또는 Secondary Adapter가 되기도합니다.
결론은
복잡도의 차이를 표현하는 장표를 마틴파을로가 말한다.
학습곡선을 넘겨되면, 복잡성을 일정하게 유지할 수 있습니다.
마지막으로, 일반이나 지원 도메인은 중요하지 않아서 그냥 트랜잭션스크립트나 등 사용해도 무관할 것이라 생각한다. 다만 핵심이 도는 도메인을 DDD 로 하는 것이다.
끝
'행위 돌아보기' 카테고리의 다른 글
'내 스토리 w. 최원준 클럽장' 후기 (0) | 2022.11.17 |
---|---|
DB 트랜잭션 조금 이해하기 - 최범균 유튜브 리뷰 (0) | 2021.08.01 |
[동영상 정리]의존성을 이용해 설계 진화시키기 - 조영호 님 (0) | 2020.08.21 |
맛집 리스트 - 검색 (0) | 2019.07.28 |
함수형 사고 - [6] 전진하라. (0) | 2019.07.26 |
댓글