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 를 처음부터 적용하기 보다는 주로 현재 프로젝트를 리팩토링하면서 적용할 일이 많기 때문에, 리팩토링을 위한 사전 준비를 하는 것이었다. 그래서 미션의 주된 내용은 요구사항을 정리하는 것과 테스트 코드를 작성하는 것이었다.
요구사항 정리하기
요구사항 정리 미션은 이런 식으로 각 도메인 별로 요구사항을 하나의 명제로 구체적으로 정의하는 것이었는데, 별 것 아닌 것 같았지만 막상 요구사항들을 카테고리화 하고 구체화 하는 과정에서 서비스가 결국 어떤 것들을 만족시켜야 하는지에 대한 이해도가 깊어졌다.
지금은 미션이니까 하는 것이지만 실무에서도 이걸 적어보는 것이 괜찮겠다는 생각이 들었다. 왜냐하면 보통 일을 할 때는 요구사항을 기획서를 보고 이해하게 되는데 이것이 때로는 구체적이지 않아서 이를 파악하는 과정에서 자칫 ‘상식’이 들어가게 되기 때문이다.
만약 그 ‘상식’이라는 것이 기획자의 의도와 맞으면 괜찮은데 그게 아니라면 기획 의도와는 다르게 프로그램이 동작하게 된다. 만약 이러한 방법대로 기획서를 보고 다시 나의 이해로 문장으로 구체화를 해보면 그냥 눈으로 기획서를 이해할때와는 달리 빈틈이 더 잘 보일 것 같다는 생각이 들었다.
테스트 코드 작성하기
테스트 코드는 실무에서 해온 그대로 해도 미션을 통과하기에는 충분했던 것 같다. 그리고 이미 TDD 자바 수업을 들었기에 nextstep에서 요구하는 테스트 코드의 수준을 어느정도 알고 있어서 미션을 진행하는 것이 더 용이했다.
되게 재미있었고 신기했던 부분은 Repository에 대한 구조이다. 최상위에 인터페이스를 하나 만들어서 사용하고자 하는 메소드를 구현해주고 실제로 로직에서는 JpaRepository를 사용했고, 테스트 코드에서는 InMemoryRepository를 사용했는데 이런 방식은 처음 봤고 처음 해봤다.
생각해보면 슬라이스 테스트가 목적이라 했을 때 실제로 DB가 작동할 필요가 없고 DB가 작동한 것처럼 데이터가 저장되고 읽혀지는 동작이 되기만 하면 테스트에는 문제가 없기 때문이다.
그래서 테스트에서 사용되는 InMemoryRepository는 아래와 같이 Map의 힘을 빌려서 작성했다.
그리고 이렇게 만들어준 InMemoryRepository를 아래와 같이 직접 생성해서 넣어준다.
@Spy 의 경우 각 테스트 마다 ‘테스트 하고자 하는 것’에만 집중하게 하기 위해서 각 테스트의 테스트 조건 설정을 용이하게 하기 위해서 붙여줬다. 이런 이유 뿐만이 아니라 테스트 상황에서 외부에 API를 호출 해야하는 경우 응답을 고정적으로 조작해서 사용하는 것이 테스트 하는데 여러모로 편하다. 외부 호출이 건당 돈을 받는다던가, 응답이 늦다던가 하는 문제가 있다면 특히 그렇다.
그러면 아래와 같이 간단하게 테스트 코드를 짜기가 용이해진다.
일단 성능면에서 테스트 속도가 엄청나게 빨랐다. 테스트라면 성능에 대해서는 그렇게 중요하지 않다고 생각될 수도 있지만, CD/CI 과정에서 테스트 시간이 곧 배포에 걸리는 시간에 영향을 주기 때문에 신속한 장애 대응에 있어서는 분명 마이너스적인 부분이다.
그리고 테스트 코드가 양이 많아진다면 더욱 위와 같은 맥락에서 성능이 중요한 이슈가 된다. 물론 테스트라는 것이 동일한 환경을 갖추는 방식으로 운영에서 실제 사용될 DB를 쓰거나 InMemory 방식인 H2를 쓰거나 테스트 컨테이너를 쓰는 등 여러 방법이 있겠지만 일단 위 방식대로 진행해보니 테스트 클래스 전체를 싹 돌려보는데 있어서 속도가 정말 혁명적(?)으로 빨라져서 정말 편하고 좋았다.
마무리
‘도메인 주도 설계 이해’는 이정도 내용으로 진행이 되었다. 최대한 강의 자료와 강의에서 중요하다고 생각되는 부분만 추려서 나의 깨달음을 더해서 정리했다.
이번 수업은 DDD 자체보다는 DDD를 하기 위한 준비운동 같은 개념이었는데, 나는 TDD 수업을 이미 들었기 때문에 상대적으로 정리할 게 많지 않았던 것이지 만약 TDD 수업을 듣지 않은 상태에서 이 수업을 들었다면 코딩 스타일부터 정리할 내용이 무척 많았을 것이다.
다음 수업은 ‘크게 소리 내어 모델링 하기’ 로 DDD 의 전략적 설계에 대한 내용을 다루게 된다.
Last updated