본문 바로가기
독서

도메인 주도 개발 시작하기

by 탁구치는 개발자 2023. 6. 30.

p.25

도메인 전문가와 개발자 간 지식 공유 요구사항을 올바르게 이해하려면 개발자와 전문가가 직접 대화하는 것이다. 개발자와 전문가 사이에 내용을 전파하는 전달자가 많으면 많을수록 정보가 왜곡되고 손실이 발생하게 되며, 개발자는 최초에 전문가가 요구한 것과는 다른 무언가를 만들게 된다.

요구사항을 명확하게 이해하기 위해서 담당자와 직접 comm. 한다.

 

p.28

도메인 모델을 표현할 때 클래스 다이어그램이나 상태 다이어그램과 같은 UML 표기법만 사용해야 하는 것은 아니다. 관계가 중요한 도메인이라면 그래프를 이용해서 도메인을 모델링 할 수 있다. 도메인을 이해하는 데 도움이 된다면 표현 방식이 무엇인지는 중요하지 않다.

 

p.31

도메인 계층은 도메인의 핵심 규칙을 구현한다. 주문 도메인의 경우 '출고 전에 배송지를 변경 할 수 있다'라는 규칙과 '주문 취소는 배송 전에만 할 수 있다'라는 규칙을 구현한 코드가 도메인 계층에 위치하게 된다.

흔히 entity, vo에 비즈니스 로직이 들어간다고 이해하면 되겠다. 도메인에 비즈니스 로직이 들어가니 도메인 모델링에 신경을 많이 써야 할 듯 하다.

 

p.35

도메인을 모델링할 때 기본이 되는 작업은 모델을 구성하는 핵심 구성요소, 규칙, 기능을 찾는 것이다. 이 과정은 요구사항에서 출발한다. 주문 도메인과 관련된 몇 가지 요구사항을 보자.

  • 최소 한 종류 이상의 상품을 주문해야 한다.
  • 한 상품을 한 개 이상 주문할 수 있다.
  • 주문할 때 배송지 정보를 반드시 지정해야 한다.

이와 같은 요구사항을 기반으로 Order에 대한 메서드를 만들 수 있게 된다.

 

p.41

도메인 지식이 잘 묻어나도록 코드를 작성하지 않으면 코드의 동작 과정은 해석할 수 있어도 도메인 관점에서 왜 코드를 그렇게 작성했는지 이해하는 데는 도움이 되지 않는다. 단순히 코드를 보기 좋게 작성하는 것뿐만 아니라 도메인 관점에서 코드가 도메인을 잘 표현해야 비로소 가독성이 높아지고 문서로서 코드가 의미를 갖는다.

 

p.54

도메인 모델에 get/set 메서드를 무조건 추가하는 것은 좋지 않은 버릇이다. 특히 set 메서드는 도메인의 핵심 개념이나 의도를 코드에서 사라지게 한다.

 

p.57

코드를 작성할 때 도메인에서 사용하는 용어는 매우 중요하다. 도메인에서 사용하는 용어를 코드에 반영하지 않으면 그 코드는 개발자에게 코드의 의미를 해성해야 하는 부담을 준다. OrderState의 STEP1, STEP2, STEP3, STEP4 와 같은 코드는 지양한다.

 

p.58

전문가, 관계자, 개발자가 도메인과 관련된 공통의 언어를 만들고 이를 대화, 문서, 도메인 모델, 코드, 테스트 등 모든 곳에서 같은 용어를 사용한다. 이렇게 하면 소통 과정에서 발생하는 용어의 모호함을 줄일 수 있고 개발자는 도메인과 코드 사이에서 불필요한 해석 과정을 줄일 수 있다.

 

p.81

필자는 개발 초년 시절 도메인 모델을 만들 때 DB 테이블의 엔티티와 도메인 모델의 엔티티를 구분하지 못해 동일하게 만들곤 했다. 도메인 모델의 엔티티와 DB 모델의 엔티티를 거의 같은 것으로 생각했었는데, 경험이 쌓일수록 도메인 모델에 대한 이해도 높아지면서 실제 도메인 모델의 엔티티와 DB 관계형 모델의 엔티티는 같은 것이 아님을 알게 되었다. 이 두 모델의 가장 큰 차이점은 도메인 모델의 엔티티는 데이터와 함께 도메인 기능을 함께 제공한다는 점이다. 예를 들어 주문을 표현하는 엔티티는 주문과 관련된 데이터뿐만 아니라 배송지 주소 변경을 위한 기능을 함께 제공한다.

 

p.98

온라인 쇼핑몰 시스템을 개발할 때 상위 수준 개념을 이용해서 전체 모델을 정리하면 전반적인 관계를 이해하는 데 도움이 된다. 이 그림을 보면 주문은 회원, 상품, 결제와 관련이 있다는 것을 쉽게 파악할 수 있다.

 

p.99

상위 수준에서 모델이 어떻게 엮여 있는지 알아야 전체 모델을 망가뜨리지 않으면서 추가 요구사항을 모델에 반영할 수 있는데, 세부적인 모델만 이해한 상태로는 코드를 수정하는 것이 꺼려지기 때문에 코드 변경을 최대한 회피하는 쪽으로 요구사항을 협의하게 된다. 꼼수를 부려 당장 돌아가는 코드를 추가할 수는 있지만 이런 방법은 장기적으로 코드를 더 수정하기 어렵게 만든다. 복잡한 도메인을 이해하고 관리하기 쉬운 단위로 만들려면 상위 수준에서 모델을 조망할 수 있는 방법이 필요하다.

 

p.135

삭제 요구사항이 있더라도 데이터를 실제로 삭제하는 경우는 많지 않다. 관리자 기능에서 삭제한 데이터까지 조회해야 하는 경우도 있고 데이터 원복을 위해 일정 기간 동안 보관해야 할 때도 있기 때문이다. 삭제 플래그를 사용

 

p.205

도메인 로직을 도메인 영역과 응용 서비스에 분산해서 구현하면 코드 품질에 문제가 발생한다. 첫 번째 문제는 코드의 응집성이 떨어진다. 도메인 데이터와 그 데이터를 조작하는 도메인 로직이 한 영역에 위치하지 않고 서로 다른 영역에 위치한다는 것은 도메인 로직을 파악하기 위해 여러 영역을 분석해야 한다는 것을 의미한다. 두 번째 문제는 여러 응용 서비스에서 동일한 도메인 로직을 구현할 가능성이 높아진다.

 

p.206

소프트웨어의 가치를 높이려면 도메인 로직을 도메인 영역에 모아서 코드 중복을 줄이고 응집도를 높여야 한다.

 

p.210

필자는 한 클래스가 여러 역할을 갖는 것보다 각 클래스마다 구분되는 역할을 갖는 것을 선호한다. 한 도메인과 관련된 기능을 하나의 응용 서비스 클래스에서 모두 구현하는 방식보다 구분되는 기능을 별도의 서비스 클래스로 구현하는 방식을 사용한다. memberService 안에 changePassword, register 등의 메서드를 changePasswordService, registerService 와 같이 클래스로 구현하는 것을 선호한다는 의미이다.

 

p.211

인터페이스가 명확하게 필요하기 전까지는 응용 서비스에 대한 인터페이스를 작성하는 것이 좋은 선택이라고 볼 수는 없다. (소스 파일만 많아지고 구현 클래스에 대한 간접 참조가 증가해서 전체 구조가 복잡해짐)

 

p.216

응용 서비스에서 표현 영역에 대한 의존이 발생하면 응용 서비스만 단독으로 테스트하기가 어려워진다. 게다가 표현 영역의 구현이 변경되면 응용 서비스의 구현도 함께 변경해야 하는 문제도 발생한다. HttpServletRequest 와 같은 코드

 

p.247

운영자는 기존 배송지 정보를 이용해서 배송 상태로 변경했는데 그 사이 고객은 배송지 정보를 변경했다는 점이다. 즉, 애그리거트의 일관성이 깨지는 것이다. 애그리커트에 대해 사용할 수 있는 대표적인 트랜잭션 처리 방식에는 선점잠금과 비선점잠금의 두 가지 방식이 있다.

 

p.249

선점 잠금은 보통 DBMS가 제공하는 행단위 잠금을 사용해서 구현한다. 오라클을 비롯한 다수의 DBMS가 for update와 같은 쿼리를 사용해서 특정 레코드에 한 커넥션만 접근할 수 있는 잠금장치를 제공한다.

 

p.251

선점 잠금에 따른 교착 상태는 상대적으로 사용자 수가 많을 때 발생할 가능성이 높고, 사용자 수가 많아지면 교착 상태에 빠지는 스레드는 더 빠르게 증가한다. 더 많은 스레드가 교착 상태에 빠질수록 시스템은 아무것도 할 수 없는 상태가 된다. JPA에서 선점 잠금을 시도할 때 최대 대기 시간을 지정할 수 있다. (hint) JPA의 javax.persistence.lock.timeout 힌트 사용

 

p.276

처음 도메인 모델을 만들 때 빠지기 쉬운 함정이 도메인을 완벽하게 표현하는 단일 모델을 만드는 시도를 하는 것이다. 한 도메인은 다시 여러 하위 도메인으로 구분되기 때문에 한 개의 모델로 여러 하위 도메인을 모두 표현하려고 시도하면 오히려 모든 하위 도메인에 맞지 않는 모델을 만들게 된다. 하위 도메인에 따라 다른 용어를 사용하는 경우도 있다. (아래 예시) 회원 도메인 --- 회원 --> 사람 주문 도메인 --- 주문자 --> 사람 배송 도메인 --- 보내는 사람 --> 사람 이렇게 하위 도메인마다 같은 용어라도 의미가 다르고 같은 대상이라도 지칭하는 용어가 다를 수 있기 때문에 한 개의 모델로 모든 하위 도메인을 표현하려는 시도는 올바른 방법이 아니며 표현할 수도 없다.

 

p.303

주문 바운디드 컨텍스트와 결제 바운디드 컨텍스트간의 강결합 때문이다. 주문이 결제와 강하게 결합되어 있어서 주문 바운디드 컨텍스트가 결제 바운디드 컨텍스트에 영향을 받게 되는 것이다. 이런 강한 결합을 없앨 수 있는 방법이 있다. 바로 이벤트를 사용하는 것이다. 특히 비동기 이벤트를 사용하면 두 시스템 간의 결합을 크게 낮출 수 있다. 한번 익숙해지면 모든 연동을 이벤트와 비동기로 처리하고 싶을 정도로 강력하고 매력적인 것이 이벤트다.

 

p.304

이벤트라는 용어는 '과거에 벌어진 어떤 것'을 의미한다. 예를 들어 사용자가 암호를 변경한 것을 '암호를 변경했음 이벤트'가 벌어졌다고 할 수 있다. 이벤트가 발생한다는 것은 상태가 변경됐다는 것을 의미한다. '암호 변경됨 이벤트'가 발생한 이유는 회원이 암호를 변경했기 때문이고, '주문 취소됨 이벤트'가 발생한 이유는 주문을 취소했기 때문이다. 이벤트는 발생하는 것에서 끝나지 않는다. 이벤트가 발생하면 그 이벤트에 반응하여 원하는 동작을 수행하는 기능을 구현한다.

 

p.308

이벤트를 사용하면 서로 다른 도메인 로직이 섞이는 것을 방지할 수 있다. 구매 취소 로직에 이벤트를 적용함으로써 환불 로직이 제거된 것을 알 수 있다. cancel() 메서드에서 환불 서비스를 실행하기 위해 사용한 파라미터도 없어졌다. 환불 실행 로직은 주문 취소 이벤트를 받는 이벤트 핸들러로 이동하게 된다. 이벤트를 사용하여 주문 도메인에서 결제(환불) 도메인으로의 의존을 제거했다. Events.raise(new PasswordChangedEvent(id.getId(), newPassword));

 

p.319

'A하면 일정 시간 안에 B 하라'는 요구사항에서 A하면은 이벤트로 볼 수도 있다. '회원 가입 신청을 하면 인증 이메일을 보내라'는 요구사항에서 '회원 가입 신청을 하면'은 '회원 가입 신청함 이벤트'로 볼 수 있다. 따라서 '인증 이메일을 보내라'기능은 '회원 가입 신청함 이벤트'를 처리하는 핸들러에서 보낼 수 있다. 'A하면 이어서 B하라'는 요구사항 중에서 'A하면 최대 언제까지 B하라'로 바꿀 수 있는 요구사항은 이벤트를 비동기로 처리하는 방식으로 구현할 수 있다. 다시 말해서 A이벤트가 발생하면 별도 스레드로 B를 수행하는 핸들러를 실행하는 방식으로 요구사항을 구현할 수 있다.

 

p.339

멱등성 연산을 여러 번 적용해도 결과가 달라지지 않는 성질을 멱등성이라고 한다. 배송지 정보 변경 이벤트를 한 번 처리하나 여러 번 처리하나 결과적으로 동일 주소를 값으로 같는다. 같은 이벤트를 여러 번 적용해도 결과가 같으므로 이 이벤트 핸들러는 멱등성을 갖는다. 이벤트 핸들러가 멱등성을 가지면 시스템 장애로 인해 같은 이벤트가 중복해서 발생해도 결과적으로 동일 상태가 된다. 이는 이벤트 발생이나 중복 처리에 대한 부담을 줄여준다.

 

p.341

@TransactionalEventListener 스프링 트랜잭션 상태에 따라 이벤트 핸들러를 실행할 수 있게 한다. phase = TransactionPhase.AFTER_COMMIT 이 값을 사용하면 스프링은 트랜잭션 커밋에 성공한 뒤에 핸들러 메서드를 실행한다. 중간에 에러가 발생해서 트랜잭션이 롤백 되면 핸들러 메서드를 실행하지 않는다. 이 기능을 사용하면 이벤트 핸들러를 실행했는데 트랜잭션이 롤백 되는 상황은 발생하지 않는다.

 

느낀점

controller, service, domain model, repository

비즈니스 처리를 담당해야 하는 곳은 도메인이다.

service 계층에서는 트랜잭션, dto변환, 도메인의 순서를 보장

도메인 영역 entity, vo 해당

도메인이 비즈니스 로직을 가지고 객체 지향의 특성을 적극 활용하는 방법을 도메인 모델 패턴이라고 한다.

ddd 에서는 domain 모델이 비즈니스 로직을 가진다는 사실을 알게 됐다.

'독서' 카테고리의 다른 글

한 번이라도 모든 걸 걸어본 적 있는가  (1) 2023.07.24
저는 삼풍 생존자입니다  (0) 2023.07.21
에고라는 적  (0) 2023.07.16
기대를 현실로 바꾸는 혼자 있는 시간의 힘  (0) 2023.07.06
어른 공부  (0) 2023.06.25
면접관을 위한 면접의 기술  (0) 2023.06.23
존 도어 OKR  (0) 2023.06.22
대통령의 글쓰기  (0) 2023.06.20