😀
fistkim TECH BLOG
  • Intro
  • 강의
    • Reactive Programming in Modern Java using Project Reactor
      • Reactor execution model 1
      • Reactor execution model 2
      • Reactor execution model 3 - parallelism
      • Reactor execution model 4 - overview
      • Transform
      • Combine
      • Side Effect Methods
      • Exception/Error handling
      • retry, retryWhen, repeat
      • BackPressure
      • Cold & Hot Streams
    • NEXTSTEP 클린코드 with java 9기
      • 정리노트
    • NEXTSTEP DDD 세레나데 2기
      • CH01 도메인 주도 설계 이해
      • CH02 크게 소리 내어 모델링 하기
      • CH03 도메인 주도 설계 기본 요소
      • CH04 도메인 주도 설계 아키텍처
      • CH05 도메인 이벤트
    • NEXTSTEP 인프라 공방 1기
      • 망 분리하기
      • 통신 확인하기
      • 도커 컨테이너 이해하기
      • [미션 1] 서비스 구성하기 실습
      • [미션 2] 서비스 배포하기 실습
      • 서버 진단하기
      • 어플리케이션 진단하기
      • [미션 3] 서비스 운영하기
      • 웹 성능 진단하기
      • 부하 테스트
      • k6
      • [미션 4] 성능 테스트
      • 리버스 프록시 개선하기
      • 캐싱 활용하기
      • [미션 5] 화면 응답 개선하기
      • Redis Annotation 및 설정
      • 인덱스 이해하기 & DB 튜닝
      • [미션 6-1] 조회 성능 개선하기
      • [미션 6-2] DB 이중화 적용
    • NEXTSTEP 만들면서 배우는 Spring 3기
      • CH01 올바른 방향 바라보기
      • CH02 HTTP 이해 - 웹 서버 구현
        • HTTP 파싱
        • HTTP 웹 서버 구현
      • CH03 MVC - @MVC 프레임워크 구현
        • Servlet 다시 짚기
        • Cookie, Session 다시 짚기
        • MVC 프레임워크 구현
      • CH04 나만의 라이브러리 구현
      • CH05 DI - DI 프레임워크 구현
      • CH06 Aspect OP
    • 스프링 시큐리티
      • 스프링 시큐리티 아키텍처
      • WebAsyncManagerIntegrationFilter
      • SecurityContextPersistenceFilter
      • HeaderWriterFilter
      • CsrfFilter
      • (+) 스프링 시큐리티 + JWT
      • (+) 마치며
    • 더 자바, 코드를 조작하는 다양한 방법
      • CH01 JVM 이해하기
      • (+) 클래스 로더 이해하기
      • CH02 바이트 코드 분석 및 조작
      • (+) jacoco
      • CH03 리플렉션
      • CH04 다이나믹 프록시
      • CH05 애노테이션 프로세서
    • 더 자바, 애플리케이션을 테스트하는 다양한 방법
      • CH01 JUnit 5
      • CH02 Mockito
      • (+) Spy vs Mock
      • CH03 도커와 테스트
      • CH04 성능 테스트
      • (+) VisualVM
      • (+) 테스트 자동화
      • CH05 운영 이슈 테스트
      • CH06 아키텍처 테스트
    • 모든 개발자를 위한 HTTP 웹 기본 지식
      • CH01 인터넷 네트워크
      • CH02 HTTP 기본
      • CH03 HTTP 메서드 속성
      • CH04 HTTP 메서드 활용
      • CH05 HTTP 상태코드
      • CH06 HTTP 헤더1 - 일반 헤더
      • CH07 HTTP 헤더2 - 캐시와 조건부 요청
      • (+) HTTPS 원리
    • 스프링 프레임워크 핵심 기술
      • CH01 IOC 컨테이너
      • CH02 AOP
      • (+) 스프링 의존성 관리
      • (+) 생성자 주입 장점
    • 코딩으로 학습하는 GoF의 디자인 패턴
      • 객체 생성
        • 싱글톤 패턴
        • 팩토리 메소드 패턴
        • 추상 팩토리 패턴
        • 빌더 패턴
        • 프로토타입 패턴
      • 구조
        • 어댑터 패턴
        • 브릿지 패턴
        • 컴포짓 패턴
      • 행동
        • (작성중)
    • 실전 Querydsl
      • CH01 프로젝트 환경구성
      • CH02 예제 도메인 모델
      • CH03 기본문법
      • CH04 중급 문법
      • CH05 실무활용 (스프링 데이터 JPA와 Querydsl)
      • CH06 스프링데이터JPA 가 제공하는 Querydsl 기능
      • (+) 별칭(alias)
      • (+) Slice 쿼리
    • 스프링 데이터 JPA
      • CH01 핵심개념이해 1
      • CH02 핵심개념이해 2
      • CH03 핵심개념이해 3
      • CH04 Spring Data Common
      • CH05 Spring Data JPA
    • 실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화
      • CH01 지연 로딩과 조회 성능 최적화
      • CH02 컬렉션 조회 최적화
      • CH03 전체 정리
    • 초보를 위한 쿠버네티스 안내서
      • CH01 쿠버네티스 시작하기
      • CH02 쿠버네티스 알아보기
      • CH03 쿠버네티스 실습 준비
      • CH04 쿠버네티스 기본 실습
    • Flutter Provider Essential
      • CH01 Introduction
      • CH02 Provider Overview
      • CH03 TODO App
      • CH04 Weather App
      • CH05 Firebase Authentication App
    • Flutter Bloc Essential
      • CH01 Introduction
      • CH02 Bloc Overview
      • CH03 TODO App
      • CH04 Weather App
      • CH05 Firebase Authentication App
    • Flutter Advanced Course - Clean Architecture With MVVM
      • CH01 Introduction
      • CH02 Clean Architecture 4 Layer
      • CH03 MVVM
      • CH04 Data Layer
      • (+) Data Layer - response to model
      • (+) Data Layer - Network
      • CH05 Domain Layer
      • CH06 Presentation Layer
      • CH07 Application Layer
      • (+) Application Layer - l10n
      • (+) Application Layer - DI
      • (+) Application Layer - environment
    • 자바 알고리즘 입문
      • CH01 문자열
      • CH02 Array(1, 2 차원 배열)
      • CH03 Two pointers, Sliding window[효율성: O(n^2)-->O(n)]
      • CH04 HashMap, TreeSet (해쉬, 정렬지원 Set)
      • CH05 Stack, Queue(자료구조)
      • CH06 Sorting and Searching(정렬, 이분검색과 결정알고리즘)
      • CH07 Recursive, Tree, Graph(DFS, BFS 기초)
      • CH08 DFS, BFS 활용
      • CH09 Greedy Algorithm
      • CH10 dynamic programming(동적계획법)
  • 도서
    • 만들면서 배우는 클린 아키텍처
      • 학습목표
      • CH01 계층형 아키텍처의 문제는 무엇일까?
      • CH02 의존성 역전하기
      • CH03 코드 구성하기
      • CH04 유스케이스 구현하기
      • CH05 웹 어댑터 구현하기
      • CH06 영속성 어댑터 구현하기
      • CH07 아키텍처 요소 테스트하기
      • CH08 경계 간 매핑하기
      • CH09 어플리케이션 조립하기
      • CH10 아키텍처 경계 강제하기
      • CH11 의식적으로 지름길 사용하기
      • CH12 아키텍처 스타일 결정하기
    • 클린 아키텍처
      • 들어가며
      • 1부 소개
        • 1장 설계와 아키텍처란?
        • 2장 두 가지 가치에 대한 이야기
      • 2부 벽돌부터 시작하기: 프로그래밍 패러다임
        • 3장 패러다임 개요
        • 4장 구조적 프로그래밍
        • 5장 객체 지향 프로그래밍
        • 6장 함수형 프로그래밍
      • 3부 설계 원칙
        • 7장 SRP: 단일 책임 원칙
        • 8장 OCP: 개방-폐쇄 원칙
        • 9장 LSP: 리스코프 치환 원칙
        • 10장 ISP: 인터페이스 분리 원칙
        • 11장 DIP: 의존성 역전 원칙
      • 4부 컴포넌트 원칙
        • 12장 컴포넌트
        • 13장 컴포넌트 응집도
        • 14장 컴포넌트 결합
      • 5부
        • 15장 아키텍처란?
    • 스프링 입문을 위한 자바 객체 지향의 원리와 이해
      • CH01 사람을 사랑한 기술
      • CH02 자바와 절차적/구조적 프로그래밍
      • CH03 자바와 객체 지향
      • (+) 자바 코드 실행에 따른 메모리 적재과정
      • CH04 자바가 확장한 객체 지향
      • CH05 객체 지향 설계 5 원칙 - SOLID
      • CH06 스프링이 사랑한 디자인 패턴
      • CH07 스프링 삼각형과 설정 정보
      • (부록) 람다(lambda)
    • 객체지향의 사실과 오해
      • CH01 협력하는 객체들의 공동체
      • CH02 이상한 나라의 객체
      • CH03 타입과 추상화
      • CH04 역할, 책임, 협력
      • CH05 책임과 메시지
      • CH06 객체 지도
      • CH07 함께 모으기
      • (+) 인터페이스 개념 바로잡기
    • 도메인 주도 개발 시작하기
      • CH01 도메인 모델 시작하기
      • CH02 아키텍처 개요
      • CH03 애그리거트
      • CH04 리포지터리와 모델 구현
      • CH05 스프링 데이터 JPA를 이용한 조회 기능
      • CH06 응용 서비스와 표현 영역
      • CH07 도메인 서비스
      • CH08 애그리거트 트랜잭션 관리
      • CH09 도메인 모델과 바운디드 컨텍스트
      • CH10 이벤트
      • CH11 CQRS
    • 자바 ORM 표준 JPA 프로그래밍
      • CH01 JPA 소개
      • CH02 JPA 시작
      • CH03 영속성 관리
      • CH04 엔티티 매핑
      • CH05 연관관계 매핑 기초
      • CH06 다양한 연관관계 매핑
      • CH07 고급 매핑
      • CH08 프록시와 연관관계 관리
      • CH09 값 타입
      • CH10 객체지향 쿼리 언어
      • CH11 웹 애플리케이션 제작
      • CH12 스프링 데이터 JPA
      • CH13 웹 애플리케이션과 영속성 관리
      • CH14 컬렉션과 부가 기능
      • CH15 고급 주제와 성능 최적화
      • CH16 트랜잭션과 락, 2차 캐시
    • 소프트웨어 세상을 여는 컴퓨터과학
      • CH01 컴퓨터 과학 소개
      • CH02 데이터 표현과 디지털 논리
    • 이펙티브 자바
      • 1 장 들어가기
      • 2장 객체 생성과 파괴
        • [01] 생성자 대신 정적 팩터리 메서드를 고려하라
        • [02] 생성자에 매개변수가 많다면 빌더를 고려하라
        • [03] private 생성자나 열거 타입으로 싱글턴임을 보증하라
        • [04] 인스턴스화를 막으려거든 private 생성자를 사용하라
        • [05] 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라
        • [06] 불필요한 객체 생성을 피하라
        • [07] 다 쓴 객체 참조를 해제하라
        • [08] finalizer 와 cleaner 사용을 피하라
        • [09] try-finally 보다는 try-with-resources 를 사용하라
      • 3장 모든 객체의 공통 메서드
        • [10] equals는 일반 규약을 지켜 재정의하라
        • [11] equals 를 재정의하려거든 hashCode도 재정의하라
        • [12] toString 을 항상 재정의하라
        • [13] clone 재정의는 주의해서 진행하라
        • [14] Comparable 을 구현할지 고려하라
      • 4장 클래스와 인터페이스
        • [15] 클래스와 멤버의 접근 권한을 최소화하라
  • 토픽
    • 서버 모니터링
      • CPU 사용량
      • 메모리 사용량
      • 스레드 풀
    • Spring Boot Monitoring
      • Spring actuator
      • Spring eureka
      • Prometheus
      • grafana
      • Spring actuator + Prometheus + grafana
    • JAVA 데일리 토픽
      • 메모리 누수(memory leak)
      • 객체 참조의 유형
      • 커스텀 스레드 풀
      • Mark And Compact
      • serialVersionUID 이해하기
      • 함수형 인터페이스
      • 메소드 참조
      • equals()와 hashCode()가 무엇이고 역할이 무엇인지
      • StringBuffer vs StringBuilder
      • String vs StringBuilder, StringBuffer
      • String interning
    • JAVA GC
    • 프로그래머스 문제 풀기
      • 해시
      • 스택/큐
      • 힙(Heap)
      • 정렬
      • 완전탐색
      • DFS/BFS
    • 데이터베이스 구성 및 작동 흐름
    • 데이터베이스 JOIN 원리
    • 객체지향생활체조 원칙
    • 상태(state), 상속(inheritance), 합성(composition) 의 상관관계
    • java enum은 메모리에 언제, 어떻게 할당되는가
    • Checked Exception vs UnChecked Exception
    • Reactive Streams 원리탐구 - 간단한 예제 직접 작성해보기
    • Flutter Basic
    • Flutter StatefulWidget 생명주기
    • Flutter 가 위젯을 그리는 원리
    • Flutter 클린 아키텍처
      • application layer
        • 패키지 구조 및 레이어 설명
        • environment
        • dependency injection
        • go_router
        • foreground & background
        • 다국어처리 (l10n, i18n)
        • Global 처리(시스템 점검, fore->back 등)
        • connection_manager
        • permission_manager
        • push_notification_manager
        • firebase 연동
      • data layer
        • 패키지 구조 및 레이어 설명
        • network
        • repository
      • domain layer
        • 패키지 구조 및 레이어 설명
      • presentation layer
        • 패키지 구조 및 레이어 설명
        • resources
    • 기술 관련 포스팅 읽기
  • 기타
    • 작업일지
      • 2023. 10
      • 2023. 09
      • 2023. 08
      • 2023. 07
      • 2023. 06
      • 2023. 05
      • 2023. 04
      • 2023. 03
      • 2023. 02
      • 2023. 01
      • 2022. 12
    • Business Model
      • 아이디어 불패의 법칙
      • 린 모바일 앱 개발
      • 린 스타트업
      • 제로투원
      • MIT 스타트업 바이블
      • 린치핀
    • 백로그 종합
Powered by GitBook
On this page
  • DDD 세레나데와 nextstep
  • ‘과정 소개’ 에서 얻은 것
  • 들어가며
  • 학습목표
  • 도메인 주도 설계 등장 배경
  • 레거시 코드
  • 이론정리
  • 1. 우발적 복잡성(accidental complexity) 과 본질적 복잡성(essential complexity)의 차이(feat. DDD의 목적)
  • 2. 도메인과 도메인 모델의 정의
  • 3. 빈약한 도메인 모델(Anemic Domain Model)
  • 4. 거대한 서비스 레이어(Big Service Layer)
  • 실습하기
  • 테스트 코드 작성하기
  • 마무리
  1. 강의
  2. NEXTSTEP DDD 세레나데 2기

CH01 도메인 주도 설계 이해

DDD 세레나데와 nextstep

내가 처음으로 수강한 nextstep의 강의는 TDD 클린코드 과정이었다. 수업을 듣기 전과 후로 코드를 작성하는 습관이 매우 많이 변하였고, ‘좋은 코드’ 에 대한 나만의 기준을 정립하는데 큰 도움을 얻을 수 있었다.

그래서 그 수업 이후로 nextstep에서 진행하는 수업의 퀄리티에 강한 믿음(?)을 갖게 되어서 가격이 좀 부담이 되지만 가급적 백엔드에 해당하는 모든 수업은 다 수강하려고 하고 있다. 결국 투자를 통해서 나는 시간을 사는 것이라 생각하고 내가 열심히만 한다면 내게 훨씬 더 남는 길이기 때문이다.

지금까지 nextstep에서 TDD 클린코드, 인프라 공방을 수강했는데 DDD 세레나데는 내가 nextstep에서 수강하게 된 세번째 수업의 수업 이름이다. 강의 이름에서 유추할 수 있듯이 강의의 내용은 DDD를 이론과 실습을 겸하여 다루는 것이다.

이번 포스팅을 시작으로 시리즈 형식으로 DDD 세레나데 수업에서 다룬 DDD에 대한 이론적인 부분들과 실습을 진행하면서 알게된 것들을 정리하려고 한다(참고로 현재 내가 수강하고 있는 DDD 세레나데 2기는 2주에 한 번씩 강의와 미션이 진행되고 있고 이미 여러 강의가 진행되었다)

‘과정 소개’ 에서 얻은 것

사실 ‘도메인 주도 설계’가 첫번째 강의는 아니다. ‘과정 소개’ 라고 해서 간략하게 수업이 어떤 방식으로 진행 되는지, 어떤 마인드로 이를 진행했으면 좋겠는지에 대해서 다루는 수업이 첫 주차 수업이었다.

그래서 ‘과정 소개’ 수업에서는 특별히 정리할 내용이 많지 않아서 사실상 두번째 수업인 ‘도메인 주도 설계 이해’ 수업을 첫 번째 수업으로 정리하고 ‘과정 소개’에 관해서는 인상적이고 기억에 남는 부분만 정리했다.

nextstep 수업이 참 좋은 것이 ‘개발’만을 가르치는 것이 아니라는 것이다. 강사님의 강의나 리뷰어님들의 피드백을 보면 항상 느껴지는 것이 수용적인 태도와 친화적인 커뮤니케이션 스타일이다. 이러한 맥락에서 ‘과정 소개’ 수업 내용에 아래와 같은 내용이 있었다.

프로그래밍 역량에 대한 생각의 변화

프로그래밍 역량은 개발자가 갖추어야할 전부라고 생각하기 보다, 개발자가 해야 하는 많은 일들 중 하나일 뿐이라고 생각하는 자세 프로그래밍 역량 외에도 테스트, 배포 자동화, 고객/구성원들과의 협업, 문화 만들기 등에도 관심을 가지는 자세

타 직군도 마찬가지겠지만 협업 태도는 개발자에게 정말 필요한 태도이며, 매우 높은 기대치로 요구되어야 하는 역량이라고 생각한다.

왜냐하면 궁극적으로 프로젝트 전체의 좋은 결과를 위해서는 때때로 동료의 결과물 혹은 과정에 대해서 ‘이것이 더 좋지 않을까?’ 와 같은 의견을 전달해야할 필요가 있고, 반대로 이러한 의견을 받게 될 확률이 높기 때문이다.이러한 과정은 필연적으로 긍정적 의미의 ‘비판’적인 커뮤니케이션이 될 수 밖에 없다.

상호간의 신뢰와 더 나은 결과물에 대한 바람이 있으면 결국 동료의 비판적인 의견은 ‘고마운 것’ 이 된다. 왜냐하면 코드 및 설계가 결국 동료의 의견 덕분에 더 효율적이고 좋아지고 나의 실력도 성장할 수 있기 때문이다.

같은 맥락에서 나 역시 동료에게 더 나은 방법에 대한 의견이 있다면 전달할 수 있어야 한다. 이렇게 하지 않으면 결국 더 나은 결과물이 나올 확률을 떨어트리는 것이며 동료의 성장 기회도 날려버리게 될 수 있다고 생각한다. 나 역시 의견을 전달하고 이것에 대해 서로 피드백하는 과정에서 성장할 수도 있는데, 의견을 전달해보지 않으면 이런 기회를 애초에 포기해버리는게 된다.

결론은 개발자에게 개발 실력은 최우선이라기 보다는 ‘기본’이라는 것이다. 혼자 예술 추구하느라고 동료들의 말을 무시한다던가, 본인이 옳다고만 생각해서 여러 사람들을 불편하게 만드는 사람을 실제로 겪어보니 강의 자료에 나온 저 문구가 결코 가볍게 보이지 않았다.

레거시에 대한 이야기도 있었는데, 내용이 참 좋았다.

레거시 코드에 대한 생각의 변화

레거시 코드를 바라볼 때 짜증나고, 고통스런 일로 바라보기 보다, 재미있고, 도전적인 문제로 바라보는 자세

예전에 자바지기 박재성님이 유투브에서 우리는 현재 진행형으로 레거시를 만들고 있다고 하셨던 적이 있었는데 참 공감이 갔다. 레거시라는 단어를 넓게 보면 사실 손을 떠나는 순간 모든 코드가 레거시이기 때문이다.

이런 맥락에서 개발자들은 레거시를 계속 만들어내고, 계속해서 레거시를 다루게 될 수 밖에 없다고 말할 수 있다. 그렇기에 레거시를 대하는 자세는 개발을 하는 대부분의 시간에 적용되는 자세이기에 중요할 수 밖에 없다.

따라서 레거시를 대할 때 성가신 일이나 지루한 일이라는 인식 보다는 ‘나라면 어떻게 했을까’, ‘뭘 어떻게 변화시키면 기능은 유지하면서 성능은 좋아질까’, ‘어떻게 바꾸면 구조가 더 좋아질까’와 같은 호기심이 기반된 자세가 본인 스스로에게도 좋다고 생각된다.

들어가며

본격적으로 첫 수업 주제였던 ‘도메인 주도 설계 이해’에 대해서 정리를 시작한다. 의식적인 학습을 위해서 매 포스팅의 시작에는 ‘학습목표’를 구체적으로 적을 것이다.

학습목표가 곧 내가 ‘무엇을 알고 싶었는가’를 정의하는 것이고, 학습이 끝나고 나서 ‘몰랐던 것들을 결국 알게 되었는가’를 검증해보는 지침이 되기 때문이다.

학습목표

도메인 주도 설계 등장 배경

  • 우발적 복잡성(accidental complexity) 과 본질적 복잡성(essential complexity)의 차이를 이해한다.(feat. DDD의 목적)

  • 도메인과 도메인 모델의 정의에 대해서 이해한다.

레거시 코드

  • 빈약한 도메인 모델(Anemic Domain Model) 에 대해서 이해한다.

  • 거대한 서비스 레이어(Big Service Layer) 에 대해서 이해한다.

이론정리

1. 우발적 복잡성(accidental complexity) 과 본질적 복잡성(essential complexity)의 차이(feat. DDD의 목적)

기본적으로 여기서 사용된 ‘복잡성’ 이라는 단어는 소프트웨어를 만들때 발생 및 경험하게 되는 복잡도를 의미한다. 강의 자료에서는 이에 대해서 아래와 같이 안내하고 있다.

본질적 복잡성은 문제 자체에서 발생하며 문제의 범위를 줄이지 않고는 제거할 수 없다. 반면에 우발적 복잡성은 솔루션으로 인해 발생하며 프레임워크, 데이터베이스 또는 기타 인프라가 될 수 있다.

결국 본질적 복잡성은 소프트웨어가 다뤄야할 문제 자체에서 발생하기 때문에 줄이기가 힘들고, 우발적 복잡성은 효율적이지 못한 설계 등 문제 외적 요소로 인해 발생하는 것이라는 의미이다.

강의 자료에서는 여기저기 흩어져있는 비즈니스 규칙을 어떻게 모을까 고민하다가 로버트 C. 마틴의 ‘클린 아키텍처’를 발견하게 되었다고 적혀있는데, 아직 나도 다 읽어보진 못했지만 지금까지 파악한 내용을 바탕으로 보면 비즈니스 규칙을 한데 모은다는 목적만 놓고 봤을 때 ‘클린 아키텍처’가 매우 적절한 처방으로 보인다.

결국 ‘비즈니스 규칙을 한 데 모으면 우발적 복잡성이 낮아진다’가 결론이고 이를 위한 해답이 DDD 라는 것이 복잡성에 관한 이야기가 나온 맥락이다.

본질적 복잡성을 다루는 부분에서 개인적으로 강의 자료에서 조금 간과되고 있는 포인트가 있다는 생각이 들었다. 강의 자료에서는 본질적 복잡성을 단지 문제 자체가 만들어내는 어쩔 수 없는 복잡성으로 인지하고 있는 느낌이었다.

물론 동의는 하지만 문제 자체가 만들어내는 복잡성이 100이라면 경우에 따라 어떤 기획자의 손을 거치는가에 따라서 100이 150이 될 수도, 200이 될 수도 있다는 생각이 든다.

결국 문제를 최대한 쉽게 본질적인 것으로 환원하여 정의하고 솔루션 역시 최대한 본질에 가깝게 정립하는 것이 우발적 복잡도도 낮추는데 기여할 수 있다고 본다. 그리고 더 나아가서 이것이 곧 서비스가 만들어내는 value 에도 영향을 미친다고 생각한다.

2. 도메인과 도메인 모델의 정의

도메인은 우리가 자주 쓰는 단어지만 사실 도메인이라는 단어 자체가 뜻이 너무 광범위하다 정의를 한번 짚고 넘어갈 필요성이 있다고 생각이 들었는데 마침 강의 자료에서 이를 정확히 정의하고 있었다.

소프트웨어는 사람의 욕망과 욕구를 해결하려고 만든 창조물입니다. 사람들의 욕망과 욕구가 개발자에게 전달됐을 때 우리는 그것을 도메인이라고 부릅니다. — 조영호

조영호 님의 인용구가 도메인이라는 것을 개발자 입장에서 잘 표현하고 있다는 생각이 든다. 왜냐하면 보통 ‘해당 도메인의 전문가’, ‘도메인을 잘 안다’ 와 같은 말은 보편적으로 특정한 비즈니스의 서비스 카테고리에 대해서 서비스가 어떤식으로 어떤 value를 소비자들에게 전달하여야하고, 소비자들은 어떤 것들을 원하는지에 대해서 잘 알때 사용한다고 생각하기 때문이다.

도메인 모델은 이를 모델링 한 것인데 강의 자료에서 도메인 모델에 대해서 단지 어떠한 다이어그램이나 산출물 같은 것으로 정의한 것이 아니라 ‘목적을 가진 의사소통 수단’이라고 정의하고 있었는데 상당히 인상적이었다.

여기에 덧붙이자면 스스로 도메인을 이해하기 위해서 추상적인 영역을 사람이 이해하기 쉽게 구체화한 것으로도 정의할 수 있는 것 같다. 꼭 소통을 목적으로 하지 않아도 말이다.

뒤에 ‘크게 소리 내어 모델링 하기’ 수업에서 실제로 모델링을 실습하는데 거기서 시각화하는 과정을 거친다.

3. 빈약한 도메인 모델(Anemic Domain Model)

빈약한 도메인 모델이란 객체들이 상태와 행위가 정의되어 있지 않고 단순히 데이터 홀더로 존재하는 모델을 의미한다. 즉, 단순한 DTO로 getter와 setter만 사용하면서 동작되는 모델인 것이다.

그렇다면 반대로 빈약하지 않기 위해서는 어떠해야 하는 것일까? 어떤 상태가 그럼 이상적인 상태인가? 객체가 상태와 행위를 갖고 있으면서 비즈니스 로직을 캡슐화 하여 안에서 담고서 이를 처리해야 빈약하지 않다고 말할 수 있다. 빈약한 도메인 모델은 거대한 서비스 레이어가 탄생하는데 기여하게 된다.

4. 거대한 서비스 레이어(Big Service Layer)

이 용어는 검색해도 나오지 않는 것 같다. 여기서 사용된 거대한의 의미는 부정적인 의미로 사용된 형용사이다. 서비스 레이어가 응집력있게 구분되어서 하나의 레이어로 존재하는 것이 아니고 위에서 설명한 빈약한 도메인 모델에 의해서 비즈니스 로직이 여기저기 산재해 있다보니 서비스 기능을 하는 레이어 자체가 매우 거대해진(=두꺼워진) 것을 의미한다.

실습하기

이번 수업에서는 DDD 자체에 대한 실습보다는 DDD를 적용하기 위한 준비단계를 하는 실습과제가 주어졌다.

실무에서는 DDD 를 처음부터 적용하기 보다는 주로 현재 프로젝트를 리팩토링하면서 적용할 일이 많기 때문에, 리팩토링을 위한 사전 준비를 하는 것이었다. 그래서 미션의 주된 내용은 요구사항을 정리하는 것과 테스트 코드를 작성하는 것이었다.

요구사항 정리하기

- 메뉴그룹
   - 메뉴그룹 생성
      -null 또는 "" 을 생성될 메뉴그룹의 이름으로 지정할 수 없다.
   - 메뉴그룹 전체 조회- 단품
   - 단품 생성
      - null 또는 음수값을 생성될 단품의 가격으로 정할 수 없다.
      - null 또는 비속어를 생성될 단품의 이름으로 정할 수 없다.
   - 단품 변경
      - 단품 가격 변경
         - null 또는 음수값으로 가격을 변경할 수 없다.
   - 단품 전체 조회

요구사항 정리 미션은 이런 식으로 각 도메인 별로 요구사항을 하나의 명제로 구체적으로 정의하는 것이었는데, 별 것 아닌 것 같았지만 막상 요구사항들을 카테고리화 하고 구체화 하는 과정에서 서비스가 결국 어떤 것들을 만족시켜야 하는지에 대한 이해도가 깊어졌다.

지금은 미션이니까 하는 것이지만 실무에서도 이걸 적어보는 것이 괜찮겠다는 생각이 들었다. 왜냐하면 보통 일을 할 때는 요구사항을 기획서를 보고 이해하게 되는데 이것이 때로는 구체적이지 않아서 이를 파악하는 과정에서 자칫 ‘상식’이 들어가게 되기 때문이다.

만약 그 ‘상식’이라는 것이 기획자의 의도와 맞으면 괜찮은데 그게 아니라면 기획 의도와는 다르게 프로그램이 동작하게 된다. 만약 이러한 방법대로 기획서를 보고 다시 나의 이해로 문장으로 구체화를 해보면 그냥 눈으로 기획서를 이해할때와는 달리 빈틈이 더 잘 보일 것 같다는 생각이 들었다.

테스트 코드 작성하기

테스트 코드는 실무에서 해온 그대로 해도 미션을 통과하기에는 충분했던 것 같다. 그리고 이미 TDD 자바 수업을 들었기에 nextstep에서 요구하는 테스트 코드의 수준을 어느정도 알고 있어서 미션을 진행하는 것이 더 용이했다.

되게 재미있었고 신기했던 부분은 Repository에 대한 구조이다. 최상위에 인터페이스를 하나 만들어서 사용하고자 하는 메소드를 구현해주고 실제로 로직에서는 JpaRepository를 사용했고, 테스트 코드에서는 InMemoryRepository를 사용했는데 이런 방식은 처음 봤고 처음 해봤다.

생각해보면 슬라이스 테스트가 목적이라 했을 때 실제로 DB가 작동할 필요가 없고 DB가 작동한 것처럼 데이터가 저장되고 읽혀지는 동작이 되기만 하면 테스트에는 문제가 없기 때문이다.

그래서 테스트에서 사용되는 InMemoryRepository는 아래와 같이 Map의 힘을 빌려서 작성했다.

public class InMemoryMenuRepository implements MenuRepository {    Map<UUID, Menu> menus;    public InMemoryMenuRepository() {
        menus = new HashMap<>();
    }    @Override
    public List<Menu> findAllByProductId(UUID productId) {
        return Collections.emptyList();
    }    @Override
    public Menu save(Menu menu) {
        menu.setId(UUID.randomUUID());
        menus.put(menu.getId(), menu);
        return menu;
    }    @Override
    public Optional<Menu> findById(UUID uuid) {
        return Optional.ofNullable(menus.get(uuid));
    }    @Override
    public List<Menu> findAll() {
        return new ArrayList<>(menus.values());
    }    @Override
    public List<Menu> findAllById(List<UUID> uuids) {
        return menus.keySet().stream()
                .filter(uuids::contains)
                .map(uuid -> menus.get(uuid))
                .collect(Collectors.toList());
    }
}

그리고 이렇게 만들어준 InMemoryRepository를 아래와 같이 직접 생성해서 넣어준다.

@Spy 의 경우 각 테스트 마다 ‘테스트 하고자 하는 것’에만 집중하게 하기 위해서 각 테스트의 테스트 조건 설정을 용이하게 하기 위해서 붙여줬다. 이런 이유 뿐만이 아니라 테스트 상황에서 외부에 API를 호출 해야하는 경우 응답을 고정적으로 조작해서 사용하는 것이 테스트 하는데 여러모로 편하다. 외부 호출이 건당 돈을 받는다던가, 응답이 늦다던가 하는 문제가 있다면 특히 그렇다.

@ExtendWith(MockitoExtension.class)
class MenuServiceTest {    private final MenuRepository menuRepository = new InMemoryMenuRepository();    @Spy
    private final PurgomalumClient purgomalumClient = new MockPurgomalumClient();    @Spy
    private final ProductRepository productRepository = new InMemoryProductRepository();    @Spy
    private final MenuGroupRepository menuGroupRepository = new InMemoryMenuGroupRepository();...}

그러면 아래와 같이 간단하게 테스트 코드를 짜기가 용이해진다.

@DisplayName("메뉴 생성 - 생성될 메뉴는 이미 존재하는 메뉴 그룹 중 하나에 속해야한다.")
@Test
void createValidationMenuGroup() {
    Menu actual = menuRequest;    when(menuGroupRepository.findById(actual.getMenuGroupId())).thenReturn(Optional.empty());
    assertThatThrownBy(() -> menuService.create(actual))
            .isInstanceOf(NoSuchElementException.class);
}

일단 성능면에서 테스트 속도가 엄청나게 빨랐다. 테스트라면 성능에 대해서는 그렇게 중요하지 않다고 생각될 수도 있지만, CD/CI 과정에서 테스트 시간이 곧 배포에 걸리는 시간에 영향을 주기 때문에 신속한 장애 대응에 있어서는 분명 마이너스적인 부분이다.

그리고 테스트 코드가 양이 많아진다면 더욱 위와 같은 맥락에서 성능이 중요한 이슈가 된다. 물론 테스트라는 것이 동일한 환경을 갖추는 방식으로 운영에서 실제 사용될 DB를 쓰거나 InMemory 방식인 H2를 쓰거나 테스트 컨테이너를 쓰는 등 여러 방법이 있겠지만 일단 위 방식대로 진행해보니 테스트 클래스 전체를 싹 돌려보는데 있어서 속도가 정말 혁명적(?)으로 빨라져서 정말 편하고 좋았다.

마무리

‘도메인 주도 설계 이해’는 이정도 내용으로 진행이 되었다. 최대한 강의 자료와 강의에서 중요하다고 생각되는 부분만 추려서 나의 깨달음을 더해서 정리했다.

이번 수업은 DDD 자체보다는 DDD를 하기 위한 준비운동 같은 개념이었는데, 나는 TDD 수업을 이미 들었기 때문에 상대적으로 정리할 게 많지 않았던 것이지 만약 TDD 수업을 듣지 않은 상태에서 이 수업을 들었다면 코딩 스타일부터 정리할 내용이 무척 많았을 것이다.

다음 수업은 ‘크게 소리 내어 모델링 하기’ 로 DDD 의 전략적 설계에 대한 내용을 다루게 된다.

PreviousNEXTSTEP DDD 세레나데 2기NextCH02 크게 소리 내어 모델링 하기

Last updated 1 year ago

https://www.simplethread.com/why-does-it-take-so-long-to-build-software/