├── README.md ├── chapter1 ├── pkch93.md └── seyun.md ├── chapter10 ├── images │ ├── 10.1.jpeg │ └── 10.3.jpeg ├── kooku0.md └── 아키텍처_경계_강제하기.md ├── chapter11 ├── [11장] 의식적으로 지름길 사용하기.md └── pkch93.md ├── chapter12 ├── [Eunmi]12.아키텍처스타일정하기.md └── mskim.md ├── chapter2 ├── images │ ├── 1.2.png │ ├── 2.1.png │ ├── 2.2.png │ ├── 2.3.png │ └── 2.4.jpeg ├── kooku0..md └── 김민석.md ├── chapter3 ├── jiaekim.md └── seyun.md ├── chapter4 ├── [4장] 유스케이스 구현하기_김광훈.md └── jiaekim.md ├── chapter5 ├── [5장] 웹 어댑터 구현하기_김광훈.md └── 김기현.md ├── chapter6 ├── [Eunmi]영속성-어댑터-구현하기.md ├── mskim.md └── pics │ ├── 1.png │ ├── 2.png │ ├── 3.png │ ├── 4.png │ ├── 5.png │ └── 6.png ├── chapter7 ├── [Eunmi]테스트-하기.md ├── pics │ ├── 1.jpg │ ├── 2.jpg │ ├── 3.jpg │ ├── 4-2.png │ ├── 4.jpg │ ├── 5-2.png │ ├── 5.jpg │ ├── 6-2.png │ └── 6.jpg └── pkch93.md ├── chapter8 ├── 08_경계_간_매핑하기.md ├── images │ ├── 8.1.png │ ├── 8.2.png │ ├── 8.3.png │ └── 8.4.png └── kooku0.md └── chapter9 ├── jiaekim.md └── seyun.md /README.md: -------------------------------------------------------------------------------- 1 | # Get-Your-Hands-Dirty-on-Clean-Architecture 2 | 만들면서 배우는 클린 아키텍처 : 자바 코드로 구현하는 클린 웹 애플리케이션 책 스터디 3 | 4 | ## 일정 5 | - 매주 화요일 21시 - 6 | 7 | ## 발표 자료 8 | 9 | | Chapter | 발표자료 | 질문목록 | 10 | |:------------------------:|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:| 11 | | 01장: 계층형 아키텍처의 문제는 무엇일까? | [박경철](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/blob/main/chapter1/pkch93.md) / [김세윤](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/blob/main/chapter1/seyun.md) | [김지애](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/issues/2) / [김세윤](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/issues/4) / [김광훈](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/issues/5) / [김기현](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/issues/10) / [공은미](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/issues/11) / [구민규](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/issues/14) | 12 | | 02장: 의존성 역전하기 | [구민규](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/blob/main/chapter2/kooku0..md) / [김민석](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/blob/main/chapter2/%EA%B9%80%EB%AF%BC%EC%84%9D.md) | [김광훈](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/issues/6) / [김지애](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/issues/7) / [박경철](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/issues/9) / [공은미](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/issues/12) / [구민규](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/issues/15) / [김기현](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/issues/16) | 13 | | 03장: 코드 구성하기 | [김지애](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/blob/main/chapter3/jiaekim.md) / [김세윤](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/blob/main/chapter3/seyun.md) | [박경철](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/issues/19) / [김기현](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/issues/22) / [김민석](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/issues/25) / [공은미](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/issues/27) / [구민규](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/issues/30) | 14 | | 04장: 유스케이스 구현하기 | [김광훈](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/blob/main/chapter4/%5B4%EC%9E%A5%5D%20%EC%9C%A0%EC%8A%A4%EC%BC%80%EC%9D%B4%EC%8A%A4%20%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0_%EA%B9%80%EA%B4%91%ED%9B%88.md) / [김지애](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/blob/main/chapter4/jiaekim.md) | [박경철](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/issues/21) / [김기현](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/issues/26) / [공은미](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/issues/28) / [김민석](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/issues/29) / [구민규](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/issues/31) | 15 | | 05장: 웹 어댑터 구현하기 | [김광훈](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/blob/main/chapter5/%5B5%EC%9E%A5%5D%20%EC%9B%B9%20%EC%96%B4%EB%8C%91%ED%84%B0%20%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0_%EA%B9%80%EA%B4%91%ED%9B%88.md) / [김기현](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/blob/main/chapter5/%EA%B9%80%EA%B8%B0%ED%98%84.md) | [김기현](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/issues/32) / [김지애](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/issues/34) / [김세윤](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/issues/36) / [박경철](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/issues/38) / [공은미](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/issues/40) / [구민규](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/issues/41) | 16 | | 06장: 영속성 어댑터 구현하기 | [공은미](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/blob/main/chapter6/%5BEunmi%5D%EC%98%81%EC%86%8D%EC%84%B1-%EC%96%B4%EB%8C%91%ED%84%B0-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0.md) / [김민석](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/blob/main/chapter6/mskim.md) | [김지애](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/issues/35) / [김세윤](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/issues/37) / [박경철](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/issues/39) / [구민규](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/issues/42) | 17 | | 07장: 아키텍처 요소 테스트하기 | [공은미](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/blob/main/chapter7/%5BEunmi%5D%ED%85%8C%EC%8A%A4%ED%8A%B8-%ED%95%98%EA%B8%B0.md) / [박경철](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/blob/main/chapter7/pkch93.md) | [김민석](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/issues/45) /[김광훈](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/issues/50) / [김지애](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/issues/55) | 18 | | 08장: 경계 간 매핑하기 | [김기현](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/blob/main/chapter8/08_%EA%B2%BD%EA%B3%84_%EA%B0%84_%EB%A7%A4%ED%95%91%ED%95%98%EA%B8%B0.md) / [구민규](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/blob/main/chapter8/kooku0.md) | [김민석](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/issues/46) / [김기현](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/issues/49) / [김광훈](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/issues/51) / [박경철](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/issues/53) / [김지애](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/issues/56) | 19 | | 09장: 애플리케이션 조립하기 | [김지애](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/blob/main/chapter9/jiaekim.md) / [김세윤](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/blob/main/chapter9/seyun.md) | [박경철](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/issues/61) / [김광훈](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/issues/63) / [김민석](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/issues/66) / [공은미](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/issues/67) | 20 | | 10장: 아키텍처 경계 강제하기 | [구민규](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/blob/main/chapter10/kooku0.md) / [김기현](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/blob/main/chapter10/%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98_%EA%B2%BD%EA%B3%84_%EA%B0%95%EC%A0%9C%ED%95%98%EA%B8%B0.md) | [김지애](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/issues/60) / [박경철](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/issues/62) / [김민석](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/issues/65) / [공은미](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/issues/68) | 21 | | 11장: 의식적으로 지름길 사용하기 | [김광훈](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/blob/main/chapter11/%5B11%EC%9E%A5%5D%20%EC%9D%98%EC%8B%9D%EC%A0%81%EC%9C%BC%EB%A1%9C%20%EC%A7%80%EB%A6%84%EA%B8%B8%20%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0.md) / [박경철](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/blob/main/chapter11/pkch93.md) | [김지애](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/issues/70) / [구민규](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/issues/73) / [김기현](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/issues/75) / [공은미](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/issues/78) | 22 | | 12장: 아키텍처 스타일 결정하기 | [공은미](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/blob/main/chapter12/%5BEunmi%5D12.%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98%EC%8A%A4%ED%83%80%EC%9D%BC%EC%A0%95%ED%95%98%EA%B8%B0.md) / [김민석](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/blob/main/chapter12/mskim.md) | [김지애](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/issues/71) / [김기현](https://github.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/issues/76) | 23 | 24 | 25 | ## 진행 방법 26 | - 발표자는 발표자료를 준비해서 Github에 업로드 합니다. 27 | - 매주 발표자 4명, 질문준비자가 4명입니다. 28 | - 미발표자는 질문을 준비합니다. (최소 한챕터당 1개 이상을 올리는것으로 합니다.) -> github에 올려주세요. 29 | - 매주 밋업시간에 발표를 진행하고 발표 진행 후 질문에 대한 토론을 합니다. 30 | 31 | ## 발표 순서 32 | image 33 | 34 | - 01장: 계층형 아키텍처의 문제는 무엇일까?(김세윤, 박경철) / 02장: 의존성 역전하기(김민석, 구민규) -> 1주 35 | - 03장: 코드 구성하기(김지애, 김세윤) / 04장: 유스케이스 구현하기(김광훈, 김지애) -> 2주 36 | - 05장: 웹 어댑터 구현하기(김기현, 김광훈) / 06장: 영속성 어댑터 구현하기(김민석, 공은미) -> 3주 37 | - 07장: 아키텍처 요소 테스트하기(공은미, 박경철) / 08장: 경계 간 매핑하기(김기현, 구민규) -> 4주 38 | - 09장: 애플리케이션 조립하기(김세윤, 김지애) / 10장: 아키텍처 경계 강제하기(구민규, 김기현) -> 5주 39 | - 11장: 의식적으로 지름길 사용하기(박경철, 김광훈) / 12장: 아키텍처 스타일 결정하기(김민석, 공은미) -> 6주 40 | 41 | ## 보증금 42 | - 총 : 20,000원 43 | - 벌금 : 5,000원 44 | - 남은 금액은 다시 돌려드립니다. 45 | - 벌금의 10% 운영비 제출합니다. 46 | - 벌금의 90% 나눠 갖거나, 회식비로 사용하겠습니다. 47 | -------------------------------------------------------------------------------- /chapter1/pkch93.md: -------------------------------------------------------------------------------- 1 | # 1장. 계층형 아키텍처의 문제는 무엇일까? 2 | 3 | - [1장. 계층형 아키텍처의 문제는 무엇일까?](#1장-계층형-아키텍처의-문제는-무엇일까) 4 | - [계층형 아키텍처의 문제점](#계층형-아키텍처의-문제점) 5 | - [Database 기반의 설계를 유도한다.](#database-기반의-설계를-유도한다) 6 | - [Database 주도 설계?](#database-주도-설계) 7 | - [레이어의 경계를 무시할 수 있다.](#레이어의-경계를-무시할-수-있다) 8 | - [테스트를 어렵게 만든다.](#테스트를-어렵게-만든다) 9 | - [Use Case를 숨길 수 있다.](#use-case를-숨길-수-있다) 10 | - [병렬로 작업하는 것을 어렵게 만듬](#병렬로-작업하는-것을-어렵게-만듬) 11 | - [마무리](#마무리) 12 | 13 | Layered architecture의 레이어 `Layers`는 서로 다른 레이어에 영향을 주지 않고 독립적으로 관리할 수 있다. 14 | 15 | ![](https://user-images.githubusercontent.com/30178507/153755148-88ba67fc-880d-49db-9117-66cfb8f32ef3.png) 16 | 17 | 즉, 요청을 받아 적절한 비즈니스 로직으로 라우팅하는 **Web** 레이어와 도메인 엔티티의 현재 상태를 수정, 조회하기 위한 **Persistence** 레이어에 상관없이 **Domain** 레이어에서 비즈니스 로직을 작성할 수 있다. **Web**과 **Persistence** 레이어는 **Domain** 레이어의 로직에는 영향을 미치지 않기 때문에 구현 기술을 자유롭게 바꿀 수 있다. 18 | 19 | 이렇게 레이어를 잘 활용하면 현재 구현된 기능에 영향을 주는 것 없이 새로운 기능을 구현할 수 있다. 따라서 좋은 Layerd Architecture는 options을 열어두고 외부 요인과 변화하는 요구사항에 빠르게 대처할 수 있도록 도와준다. 20 | 21 | 단, 잘못하면 레이어는 잘못된 습관이 스며들 수 있도록 하는 많은 측면들이 존재하며 시간이 지날수록 애플리케이션의 변화를 어렵게 만든다. 22 | 23 | ## 계층형 아키텍처의 문제점 24 | 25 | ### Database 기반의 설계를 유도한다. 26 | 27 | 전통적인 Layered Architecture는 데이터베이스에 기초한다. 이는 몇몇 이유에서 문제가 있다. 28 | 29 | 먼저 비즈니스 로직을 작성하는데는 오로지 비즈니즈 로직의 요구사항을 이해하고 정확하게 구현하는 것에 초점을 맞춰야한다. 그래야 이를 바탕으로 Persistence와 Web 레이어를 구축할 수 있다. 30 | 31 | 이 과정에서 데이터베이스 중심적인 설계를 불러오는 요소는 ORM 프레임워크를 사용하는 것이다. ORM 프레임워크를 Layered Architecture에 함께 사용하는 것은 Persistence 측면에서 비즈니스 로직을 녹일 우려가 있다. 32 | 33 | ![](https://user-images.githubusercontent.com/30178507/153755150-24e184b7-1f62-41da-b947-af69b0a2a741.png) 34 | 35 | 즉, Domain 레이어의 Entity는 Persistence 레이어와 강한 결합을 이끌게 된다. 이는 도메인 로직을 구현하면서 eager, lazy 로딩 문제, 트랜잭션 관리, 캐시 초기화 등의 문제들을 신경쓰게 만든다. 또한 Persistence 레이어의 코드가 사실상 Domain 레이어의 코드에 통합되어 있기 때문에 다른 코드가 없으면 어느 하나 변경을 하기 어렵게 만든다. 36 | 37 | > 즉, Persistence 레이어에 속하는 ORM 프레임워크를 함부로 변경할 수 없다는 것을 의미하는 것으로 보인다. 38 | 39 | #### Database 주도 설계? 40 | 41 | 저자가 말하는 Database 주도 설계란 무엇을 말하는걸까? 42 | 43 | 처음에는 비즈니스 로직을 쿼리에 녹여내는 설계를 말하는게 아닐까 싶었다. 비즈니스 로직이 쿼리에 녹아들도록 만들기 떄문에 쿼리를 먼저 생각해볼 수 밖에 없으므로 이런 이유가 Layered Architecture가 데이터베이스 주도의 설계를 유도하는게 아닐까 싶었다. 44 | 45 | 다만, 저자가 영속성 계층에 대한 이야기와 JPA를 예시로 들면서 영속성 관점과 비즈니스 규칙을 섞을때에 대한 이야기를 하는거 보면 비즈니스 로직이 쿼리에 녹아 드는 부분을 이야기하는 것은 아니라고 생각이 든다. 46 | 47 | 그러면 결국 영속성 계층을 먼저 생각하는 것을 Database 주도 설계라고 이야기 하는 걸로 보인다. 다만, 애플리케이션을 구성하는 요소 중에 데이터베이스와 쿼리 성능을 생각하지 않을 수 없을텐데 이에 대한 고민이 선행되지 않아도 괜찮을까라는 의문이 든다. 48 | 49 | ### 레이어의 경계를 무시할 수 있다. 50 | 51 | Layered Architecture는 기본적으로 하위 바탕이 되는, 의존하고 있는 레이어에는 접근을 허용한다. 단, 이 부분이 Layered Architectured 스타일에서 이 룰을 강제하지 않는다. 때문에 레이어에 어울리지 않는 각종 컴포넌트, 유틸 클래스 등이 하위 레이어에 몰릴 수 있다. 52 | 53 | ![](https://user-images.githubusercontent.com/30178507/153755153-b8cdf128-d73d-45bc-9134-afa8b8f8a17e.png) 54 | 55 | 따라서 위와 같이 Persistence 레이어에 모든것이 몰려있는 기이한 현상이 나타날 수 있다. 56 | 57 | > 위 캡처와 같이 Helper나 Utils 클래스는 특정 레이어에 상관없이 접근할 수 있는 것이 좋다. 58 | 59 | 우리 아키텍처에서 위와 같은 현상을 막길 원한다면 아키텍처 룰을 통해 강제하거나 만약 강제하지 못한다면 레이어는 최선이 아닐 수 있다. 60 | 61 | ### 테스트를 어렵게 만든다. 62 | 63 | ![](https://user-images.githubusercontent.com/30178507/153755156-9493f5a5-e466-4d6c-bcba-74bb54f66bb6.png) 64 | 65 | 위와 캡처와 같이 중간 레이어를 무시하고 Web 레이어가 바로 Persistence 레이어에 접근하는 경우가 있을 수 있다. 처음 위와 같이 사용한다면 괜찮아 보일 수 있지만 계속해서 위와 같은 형태가 이어진다면 다음 2가지 문제가 있을 수 있다. 66 | 67 | 먼저, 기능 확장에 문제가 생길 수 있다. Web 레이어에서 단일 단위로 접근하여 만든 비즈니스 로직은 더 많은 도메인 로직이 추가될수록 애플리케이션 전반에 필수 도메인 로직이 분산되고 책임이 뒤섞여 복잡도를 높일 수 있다. 68 | 69 | 두번째는 Web 레이어의 테스트 문제이다. Domain 레이어 뿐만 아니라 Persistence 레이어에 목킹 처리가 필요하다. 이는 테스트 작성에 복잡도를 더할 수 있다. 복잡한 테스트는 결국 일정 등의 이유로 테스트가 작성되지 않는 로직으로 이어질 수 있다. 테스트 코드를 작성하는 것보다 의존성을 이해하고 목킹하는데 더 많은 시간이 걸리기 때문이다. 70 | 71 | ### Use Case를 숨길 수 있다. 72 | 73 | 우리는 보통 기능을 추가하거나 변경할 때 올바른 위치를 찾기 마련이다. 때문에 아키텍처는 이를 빠르게 찾을 수 있도록 도와줄 수 있어야한다. 단, Layered Architecture가 이를 얼마나 도와줄 수 있나? 74 | 75 | 앞서 본 것처럼 Layered Architecture는 도메인 로직이 여러 레이어에 걸쳐서 쉽게 흩어질 수 있다. 이는 새로운 기능을 추가하는데 올바른 위치를 찾는 것을 어렵게 만든다. 거기에 더해서 Layered Architecture는 도메인 서비스의 크기에 대해서 어떠한 룰로 강제를 하지 않는다. 이는 하나의 도메인 서비스가 여러 Use Case에 대한 도메인 로직을 가질 수 있다는 의미이고 그만큼 비대해진다는 의미이다. 76 | 77 | ![](https://user-images.githubusercontent.com/30178507/153755157-2b61ef69-d2a2-4cf4-b8ff-29141c747e6b.png) 78 | 79 | 위와 같이 비대해진 서비스는 너무 많은 의존성을 가지므로 테스트를 어렵게 만들 뿐만 아니라 Use Case를 담당하는 서비스를 찾기 어렵게 만든다. 80 | 81 | ### 병렬로 작업하는 것을 어렵게 만듬 82 | 83 | 만약 3명의 개발자가 있고 하나의 기능에서 각각 Web, Domain, Persistence 도메인을 맡아서 개발한다고 가정한다. 이 경우 3명의 개발자가 각각 기능을 구현하기 위해서 작업을 시작할 수 없다. Domain 레이어의 작업자는 Persistence 작업이 될 때까지 대기해야하며 Web 레이어 작업자 또한 Domain 레이어 작업을 대기해야한다. 84 | 85 | 3명의 개발자가 각 레이어 간 interface나 스팩을 정의해두고 로직을 병렬적으로 개발할수도 있다. 단, 이 경우는 데이터베이스 기반의 설계를 하지 않는 경우에만 가능하다. 86 | 87 | ## 마무리 88 | 89 | Layered Architecture는 위와 같은 문제가 있다. 만약 엄격한 룰을 강제하여 사용하고 있다면 Layered Architecture는 더 유지보수하기 좋은 아키텍처가 될 수 있다. 90 | 91 | 단, 그만큼 Layered Architecture는 잘못된 것들을 허용한다는 것을 의미한다. 따라서 Layered Architecture의 문제점을 염두하면서 개발한다면 보다 유지보수하기 좋은 소프트웨어를 구축하는데 도움을 줄 수 있다. -------------------------------------------------------------------------------- /chapter1/seyun.md: -------------------------------------------------------------------------------- 1 | # 1. 계층형 아키텍처의 문제는 무엇일까? 2 | 3 | > 잘 만들어진 계층형 아키텍처는 선택의 폭을 넓히고 변화하는 요구사항과 외부 요인에 빠르게 적응할 수 있게 해준다. 바로 이게 아키텍처의 전부다. 4 | > 5 | 6 | ## 전통적인 구조 7 | 8 | ![image](https://user-images.githubusercontent.com/53366407/150629287-b598f9e7-a79f-47e5-be5d-f4b10c59c4c0.png) 9 | 10 | 1. 맨 위의 **웹 계층(UI)**에서는 요청을 받아 도메인 혹은 비즈니스 계층에 있는 서비스로 요청을 보낸다. 11 | 2. 서비스에서는 필요한 비즈니스로직을 수행하고, 도메인 엔티티의 현재 상태를 조회하거나 변경하기 위해 영속성 계층(Data Access)의 컴포넌트를 호출한다. 12 | 13 | ### 우리가 만드는 애플리케이션의 목적은 무엇인가? 14 | 15 | 우리는 보통 비즈니스를 관장하는 규칙이나 정책을 반영한 모델을 만들어서 사용자가 이러한 규칙과 정책을 더욱 편리하게 활용할 수 있게 한다. 16 | 17 | 이때 우리는 상태(state)가 아니라 행동(behavior)을 중심으로 모델링한다. 18 | 19 | 어떤 애플리케이션이든 상태가 중요한 요소이긴 하지만 행동을 상태를 바꾸는 주체이기 때문에 행동이 비즈니스를 이끌어 간다. 20 | 21 | ## 그렇다면 계층형 아키텍처의 문제점이 무엇일까? 22 | 23 | ### 계층형 아키텍처는 데이터베이스 주도 설계를 유도한다. 24 | 25 | > 전통적인 계층형 아키텍처의 토대는 데이터베이스(영속성 계층)이다. 웹 계층은 도메인 계층에 의존하고, 도메인 계층은 영속성 계층에 의존하기 때문에 자연스레 데이터베이스에 의존하게 된다. 26 | > 27 | 28 | - 그럼 왜 영속성 계층을 토대로 만들게 되는가? 29 | 30 | 데이터베이스의 구조를 먼저 생각하고 도메인 로직을 구현하기 때문이다. 이렇게 되는 이유는 ORM(object-relational mapping, 객체 관계 매핑) 프레임워크를 사용하기 때문이다. ORM 프레임워크를 계층형 아키텍처와 결합하면 비즈니스 규칙을 영속성 관점과 섞고 싶어진다. 그러나 도메인 계층에서 데이터베이스 엔티티를 사용하는 것은 영속성 계층과의 강한 결합을 유발한다. 31 | 32 | 서비스는 도메인 로직이 아니라 영속성 계층 관련된 작업들을 대부분 하게 되고, 영속성 코드가 도메인 코드에 녹아 들어가면 둘 중 하나만 바꾸는 것이 어려워지며, 유연하고 선택의 폭을 넓혀주는 계층형 아키텍처의 목표와 정확히 반대가 된다. 33 | 34 | 35 | - 그렇다면 어떻게 개발을 해야 하는가? 36 | 37 | 애플리케이션에서는 도메인 로직을 먼저 만들고 도메인 로직이 맞았다고 생각될때, 이를 기반으로 영속성 계층과 웹 계층을 만들어야 한다. 38 | 39 | 40 | ### 지름길을 택하기 쉬워진다. 41 | 42 | > 전통적인 계층형 아키텍처에서 전체적으로 적용되는 유일한 규칙은 특정한 계층에서는 같은 계층에 있는 컴포넌트나 아래에 있는 계층에만 접근 가능하다는 것이다. 43 | > 44 | - 그럼 여기서 말한 지름길은? 45 | 46 | 접근을 해야 하는 클래스가 있다면 단순히 하위 계층으로 내리는 것이다. 47 | 48 | - 그럼 문제는? 49 | 50 | 영속성 계층(최 하단 계층)은 컴포넌트를 아래 계층으로 내릴수록 비대해지며, 어떤 계층에도 속하지 않는 것처럼 보이는 헬퍼 컴포넌트나 유틸리티 컴포넌트들이 이처럼 아래 계층으로 내릴 가능성이 큰 후보다. 51 | 52 | 53 | ### 테스트하기 어려워 진다. 54 | 55 | > 계층형 아키텍처를 사용할 때 일반적으로 나타나는 변화의 형태는 계층을 건너뛰는 것이다. 엔티티의 필드를 단 하나만 조작하면 되는 경우에 웹 계층에서 바로 영속성 계층에 접근하면 도메인 계층을 건드릴 필요가 없지 않을까? 도메인 계층을 건너뛰는 것은 도메인 로직을 코드 여기저기에 흩어지게 만든다는 것이다. 56 | > 57 | 58 | 1. 단 하나의 필드를 조작하는 것에 불과하더라도 도메인 로직을 웹 계층에 구현하게 한다는 것이다. 59 | - 유즈 케이스가 확장되면? 책임이 섞이고 핵심 도메인 로직들이 퍼질 확률이 높다. 60 | 2. 웹 계층 테스트에서 도메인 계층뿐만 아니라 영속성 계층도 모킹해야 한다는 것이다. 61 | - 이렇게 되면 단위 테스트가 어려워 진다. 62 | 3. 시간이 흘러 웹 컴포넌트의 규모가 커지면 다양한 영속성 컴포넌트에 의존성이 많이 쌓이면서 테스트의 복잡도를 높이며, 테스트 작성 및 이해시 의존성을 이해하고 목을 만드는데 더 많은 시간이 걸리게 된다. 63 | 64 | ### 유스케이스를 숨긴다. 65 | 66 | > 기능을 추가하거나 변경할 적절한 위치를 찾는 일이 빈번하기 때문에 아키텍처는 코드를 빠르게 탐색하는 데 도움이 돼야 한다. 이런 관점에서 계층형 아키텍처는 어떻게 우리의 발목을 잡을까? 넓은 서비스는 코드 상에서 특정 유스케이스를 찾는 것을 어렵게 만든다. 67 | > 68 | 69 | 넓은 서비스는 영속성 계층에 많은 의존성을 갖게 되고, 다시 웹 레이어의 많은 컴포넌트가 이 서비스에 의존하게 된다. 그럼 서비스를 테스트하기도 어려워지고 작업해야 할 유스케이스를 책임지는 서비스를 찾기도 어려워 진다. 70 | 71 | 77 | 78 | ### 동시 작업이 어려워진다. 79 | 80 | > 지연되는 소프트웨어 프로젝트에 인려을 더하는 것은 개발을 늦출 뿐이다. 81 | - 맨머스 미산: 소프트웨어 공학에 관한 에세이, 프레더릭 P. 브록스 82 | > 83 | 84 | 새로운 유스 케이스를 추가한다고 하고 개발자가 3명이 있다면 한명은 웹 계층에 필요한 기능을 추가하고, 다른 한명은 도메인 계층, 나머지 개발자는 영속성 계층을 추가할수 있는데 이렇게 할 수 있는가? 85 | 86 | 모든 것이 영속성 계층 위에 만들어지기 때문에 영속성 계층을 먼저 개발하고 도메인 계층 마지막으로 웹 계층을 만들어야 한다. 87 | 88 | 코드에 넓은 서비스가 있다면 서로 다른 기능을 동시에 작업하기가 더욱 어렵다. 같은 파일을 작업하기 때문에 merge conflict와 같은 문제가 생길 확률이 높다. 89 | 90 | ## 출처 91 | - [만들면서 배우는 클린 아키텍처 - 자바 코드로 구현하는 클린 웹 애플리케이션](https://www.aladin.co.kr/shop/wproduct.aspx?ItemId=283437942) 92 | -------------------------------------------------------------------------------- /chapter10/images/10.1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/726b041246229b733566547278b45fd044534dfe/chapter10/images/10.1.jpeg -------------------------------------------------------------------------------- /chapter10/images/10.3.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/726b041246229b733566547278b45fd044534dfe/chapter10/images/10.3.jpeg -------------------------------------------------------------------------------- /chapter10/kooku0.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 10 아키텍처 경계 강제하기 3 | --- 4 | 5 | 일정 규모 이상의 모든 프로젝트에서는 시간이 지나면서 아키텍처가 서서히 무너지게 된다. 계층 간의 경계가 약화되고, 코드는 점점 더 테스트하기 어려워지고, 새로운 기능을 구현하는 데 점점 더 많은 시간이 든다. 6 | 7 | 아키텍처 내의 경계를 강제하는 방법과 함께 아키텍처 붕괴에 맞서 싸우기 위해 취할 수 있는 몇 가지 조치를 살펴보자. 8 | 9 | ## 경계와 의존성 10 | 11 | 아키텍처의 어디에 경계가 있고, '경계를 강제한다'는 것이 어떤 의미인지 먼저 살펴보자. 12 | 13 | ![img](./images/10.1.jpeg) 14 | 15 | > 아키텍처 경계를 강제한다는 것은 의존성이 올바른 방향을 향하도록 강제하는 것을 의미한다. 16 | 17 | 가장 안쪽의 계층에는 도메인 엔티티가 있다. 애플리케이션 계층은 애플리케이션 서비스 안에 유스케이스를 구현하기 위해 도메인 엔티티에 접근한다. 어댑터는 인커밍 포트를 통해 서비스에 접근하고, 반대로 서비스는 아웃고잉 포트를 통해 어댑터에 접근한다. 마지막으로 설정 계층은 어댑터와 서비스 객체를 생성할 팩터리를 포함하고 있고, 의존성 주입 메커니즘을 제공한다. 18 | 19 | 의존성 규칙에 따르면 계층 경계를 넘는 의존성은 항상 안쪽 방향으로 향해야 한다. 20 | 21 | 이번 장에서는 이러한 의존성 규칙을 강제하는 방법을 알아보고 잘못된 방향을 가리키는 의존성을 없게 만들고자 한다. 22 | 23 | ## 접근 제한다 24 | 25 | package-private 제한자는 중요하다. 자바 패키지를 통해 클래스들을 응집적인 '모듈'로 만들어 주기 때문이다. 이러한 모듈 내에 있는 클래스들은 서로 접근 가능하지만, 패키지 바깥에서는 접근할 수 없다. 그럼 모듈의 진입점으로 활용될 클래스들만 골라서 public으로 만들면 된다. 이렇게 하면 의존성이 잘못된 방향을 가리켜서 의존성 규칙을 위반할 위험이 줄어든다. 26 | 27 | package-private 제한자는 몇 개 정도의 클래스로만 이뤄진 작은 모듈에서 가장 효과적이다. 그러나 패키지 내의 클래스가 특정 개수를 넘어가기 시작하면 하나의 패키지에 너무 많은 클래스를 포함하는 것이 혼란스러워지게 된다. 이렇게 되면 코드를 쉽게 찾을 수 있도록 하위 패키지를 만드는 방법을 선호한다. 하지만 이렇게 하면 자바는 하위 패키지를 다른 패키지로 취급하기 때문에 package-private 맴버에 접근할 수 없어 public으로 만들어 노출시켜야 하기 때문에 아키텍처에서 의존성 규칙이 깨질 수 있는 환경이 만들어진다. 28 | 29 | ## 컴파일 후 체크 30 | 31 | 클래스에 public 제한자를 쓰면 아키텍처 상의 의존성 방향이 잘못되더라도 컴파일러는 다른 클래스들이 이 클래스를 사용하도록 허용한다. 이런 경우에는 컴파일러가 전혀 도움이 되지 않기 때문에 의존성 규칙을 위반했는지 확인할 다른 수단을 찾아야 한다. 32 | 33 | 한 가지 방법은 컴파일 후 체크를 도입하는 것이다. 다시 말해, 코드가 컴파일된 후에 런타임에 체크한다는 것이다. 이런 런타임 체크는 지속적인 통합 빌드 환경에서 자동화된 테스트 과정에서 가장 잘 동작한다. 34 | 35 | 이러한 체크를 도와주는 자바용 도구로 ArchUnit이 있다. ArchUnit은 의존성 방향이 기대한 대로 잘 설정돼 있는지 체크할 수 있는 API를 제공한다. 의존성 규칙 위반을 발견하면 예외를 던진다. 36 | 37 | ## 빌드 아티팩트 38 | 39 | 지금까지 코드 상에서 아키텍처 경계를 구분하는 유일한 도구는 패키지였다. 모든 코드가 같은 모놀리식 빌드 아티팩트의 일부였던 셈이다. 40 | 41 | 빌드 아티팩트는 자동화된 빌드 프로세스의 결과물이다. (Maven, Gradle) 42 | 43 | 빌드 도구의 주요한 기능 중 하나는 의존성 해결이다. 어떤 코드베이스를 빌드 아티팩트로 변환하기 위해 빌드 도구가 가장 먼저 할 일은 코드베이스가 의존하고 있는 모든 아티팩트가 사용 가능한지 확인하는 것이다. 만약 사용 불가능한 것이 있다면 아티팩트 리포지토리로부터 가져오려고 시도한다. 이마저도 실패하면 에러와 함께 빌드가 실패한다. 44 | 45 | 이를 활용해 모듈과 아키텍처의 계층 간의 의존성을 강제할 수 있다. 각 모듈 혹은 계층에 대해 전용 코드베이스와 빌드 아티팩트로 분리된 빌드 모듈(JAR 파일)을 만들 수 있다. 각 모듈의 빌드 스크립트에서는 아키텍처에서 허용하는 의존성만 지정한다. 46 | 47 | ![img](./images/10.3.jpeg) 48 | 49 | 핵심은 모듈을 더 세분화할수록, 모듈 간의 의존성을 더 잘 제어할 수 있게 된다는 것이다. 하지만 더 작게 분리할수록 모듈간에 매핑을 더 많이 수행해야 한다. 50 | 51 | 이 밖에도 빌드 모듈로 아키텍처 경계를 구분하는 것은 패키지로 구분하는 방식과 비교했을 때 몇 가지 장점이 있다. 52 | 53 | 첫 번째로, 빌드 도구가 순환 의존성을 싫어한다는 것이다. 순한 의존성은 하나의 모듈에서 일어나는 변경이 잠재적으로 다른 모든 모듈을 변경하게 만들며 단일 책임 원칙을 위배한다. 54 | 55 | 두 번째로, 빌드 모듈 방식에서는 다른 모듈을 고려하지 않고 특정 모듈의 코드를 격리한 채로 변경할 수 있다. 그러므로 여러 개의 빌드 모듈은 각 모듈을 격리한 채로 변경할 수 있게 해준다. 56 | 57 | 마지막으로, 모듈 간 의존성이 빌드 스크립트에 분명하게 선언돼 있기 때문에 새로 의존성을 추가하는 일은 우연이 아닌 의식적인 행동이 된다. 어떤 개발자가 당장은 접근할 수 없는 특정 클래스에 접근해야 할 일이 생기면 빌드 스크립트에 이 의존성을 추가하기에 앞서 정말로 이 의존성이 필요한 것이지 생각할 여지가 생긴다. 58 | 59 | 하지만 이런 장점에는 빌드 스크립트를 유지보수하는 비용을 수반하기 때문에 아키텍처를 여러 개의 빌드 모듈로 나누기 전에 아키텍처가 어느 정도는 안정된 상태여야 한다. 60 | 61 | ## 유지보수 가능한 소프트웨어를 만드는 데 어떻게 도움이 될까? 62 | 63 | 기본적으로 소프트웨어 아키텍처는 아키텍처 요소 간의 의존성을 관리하는 게 전부다. 64 | 65 | 그렇기 때문에 아키텍처를 잘 유지해나가고 싶다면 의존성이 올바른 방향을 가리키고 있는지 지속적으로 확인해야 한다. 66 | 67 | 새로운 코드를 추가하거나 리팩터링할 때 패키지 구조를 항상 염두에 둬야 하고, 가능하다면 package-private 가시성을 이용해 패키지 바깥에서 접근하면 안 되는 클래스에 대한 의존성을 피해야 한다. 68 | 69 | 하나의 빌드 모듈 안에서 아키텍쳐 경계를 강제해야 한다. 70 | 71 | 그리고 아키텍처가 충분히 안정적이라고 느껴지면 아키텍처 요소를 독립적인 빌드 모듈로 추출해야 한다. 그래야 의존성을 분명하게 제어할 수 있기 때문이다. 72 | -------------------------------------------------------------------------------- /chapter10/아키텍처_경계_강제하기.md: -------------------------------------------------------------------------------- 1 | ## 10 아키텍처 경계 강제하기 2 | 경계를 강제해야 하는 이유 3 | - 일정 규모 이상의 프로젝트는 시간이 지나면 아키텍처는 무너지게된다. 이는 점점 테스트하기 어려워지고 새로운 기능을 구현하는 데 더 많은 시간이 든다 4 | 5 | 주요 내용 6 | - 경계를 강제하는 방법 7 | - 아키텍처 붕괴에 맞서 싸우기 위한 조치 8 | 9 | ---- 10 | 11 | ### 경계와 의존성 12 | 경계를 강제한다 -> 의존성이 올바른 방향을 향하도록 강제한다는 것을 의미 13 | ![image](https://user-images.githubusercontent.com/16996054/159099232-0e21d2ba-df6d-4c6b-b4b3-b3d1a0f634be.png) 14 | 15 | 의존성 관계 16 | - 애플리케이션 -> 도메인 17 | - 어댑터 -> 인커밍 포트 -> 서비스 -> 아웃고잉 포트를 -> 어댑터 18 | - 설정계층 -> 어댑터, 서비스 19 | 20 | ----- 21 | 22 | ### 접근 제한자 23 | 자바에서 package-private 제한자는 왜 중요할까? 24 | - 클래스들을 응집적인 **모듈** 로 만들어 준다 25 | - 모듈은 모듈 내에 있는 클래스들은 서로 접근 가능하고 패키지 바깥에서는 접근할 수 없다 26 | 27 | ![image](https://user-images.githubusercontent.com/16996054/159099487-e8d60bea-d5b5-4970-b480-6e53a8780c18.png) 28 | 경계간 외부로 드러난 port 를 이용한다 29 | - persistence 패키지에 있는 클래스들은 외부에서 접근할 필요가 없기 때문에 package-private 으로 만들 수 있다 30 | - SendMoneyService 도 같은 이유로 package-private 31 | 32 | package-private 단점 33 | - 클래스가 특정 개수를 넘어가면 혼란스러워 지고, 이를 해결하기 위해 하위 패키지를 만들게 되면 다른 패키지로 취급되기 때문에 접근이 제한된다 34 | 35 | ---- 36 | 37 | ### 컴파일 후 체크 38 | 코드가 컴파일된 후에 런타임에서 체크하는 방법 39 | 40 | 자바용 도구로는 ArchUnit (의존성 방향이 기대한 대로 잘 설정되 있는지 체크할 수 있는 API 제공) 41 | ![image](https://user-images.githubusercontent.com/16996054/159099814-9adda8ea-4b85-4ce9-8336-5e1face123a8.png) 42 | 43 | 단점 44 | - 실패에 안전하지 않다 (오타, 리팩토링시 함께 유지보수) 45 | (개인의견) 제품의 테스트코드도 벅찬데 의존성을 위한 테스트코드는 짜지 않을것 같다 46 | 47 | ---- 48 | 49 | ### 빌드 아티팩트 50 | 각 모듈의 빌드 스크립트에서 아키텍처에서 허용하는 의존성을 지정하는 방법 51 | ![image](https://user-images.githubusercontent.com/16996054/159099995-a26116ed-6df1-449f-a7a0-e0ecd524fd6b.png) 52 | 53 | 54 | 장점 55 | - 모듈간 의존성을 더 잘 제어할 수 있다 56 | - 순환 의존성을 허용하지 않는다 57 | - 특정 모듈의 코드를 격리한 채로 변경할 수 있다 (모듈간 부리되어 있기 때문) 58 | - 의존성이 스크립트에 명시적으로 선언되어 있다 (의식적인 행동) 59 | 60 | 단점 61 | - 빌드 스크립트의 유지보수 비용 (빌드모듈 나누기 전에 아키텍처가 어느 정도 안정된 상태로 만든다) 62 | - 모듈간 매핑을 더 많이 수행해야 한다 (그만큼 한 모듈에 대한 의존성이 줄어든다는 장점도 있다) 63 | 64 | 용어 65 | - 빌드 아티팩트(artifact) 66 | - (maven) or (gradle) 로 배포되는 결과물 (jar 파일이다) 67 | - 구성요서 (group ID, artifact ID, version)로 artifact 를 구분하게 된다 68 | 69 | 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /chapter11/[11장] 의식적으로 지름길 사용하기.md: -------------------------------------------------------------------------------- 1 | # 11장 의식적으로 지름길 사용하기 2 | ## 1. 왜 지름길은 깨진 창문 같을까 ? 3 | - (1) 품질이 떨어진 코드에서 작업할 때 더 낮은 품질의 코드를 추가하기가 쉽다. 4 | - (2) 코딩 규칙을 많이 어긴 코드에서 작업할 때 또 다른 규칙을 어기기도 쉽다. 5 | - (3) 지름길을 많이 사용한 코드에서 작업할 때 또 다른 지름길을 추가하기도 쉽다. 6 | 7 |
8 | 9 | ## 2. 유스케이스 간 모델 공유하기 10 | 11 | 12 | - 4장에서 유스케이스마다 다른 입출력 모델을 가져야 한다고 말했다. 13 | - SendMoneyCommand 클래스가 변경이 된다면 두 유스케이스 모두 영향을 받는다. 14 | - 해당 케이스는 유스케이스들이 기능적으로 묶여 있을 때 유효하다. ----> 즉, 특정 요구사항을 공유할 때 괜찮다는 의미다. 15 | - 그게 아니라면 이건 지름길을 택한 것이 된다. 16 | 17 |
18 | 19 | ## 3. 도메인 엔티티를 입출력 모델로 사용하기 20 | 21 | 22 | - 인커밍 포트는 도메인 엔티티에 의존성을 가지고 있다. ---> Account 엔티티는 변경할 이유가 생겼다. 23 | - 유스케이스가 점점 복잡해지면 도메인 엔티티에 모두 전파가 될 것이다. 24 | - 따라서 유스케이스 전용 입출력모델을 만드는 것이 좋다. 25 | 26 |
27 | 28 | ## 4. 인커밍 포트 건너뛰기 29 | 30 | 31 | - 인커밍 포트는 의존성 역전에 필수적인 요소는 아니다. 32 | - 따라서 인커밍 포트 없이 애플리케이션 Service 에 직접 접근할 수 있다. 33 | - 하지만 인커밍 포트는 애플리케이션 중심에 접근하는 진입점을 정의한다. 34 | - 전용 인커밍 포트를 유지하면 한눈에 진입점을 식별할 수 있다. 35 | 36 | 37 | - (만약 서비스를 기능에 맞게 모두 만든다면 비용은 줄이고 거의 동일한 효과를 볼 수 있지 않을까 ??) 38 | 39 |
40 | 41 | ## 5. 애플리케이션 서비스 건너뛰기 42 | 43 | 44 | - 애플리케이션을 통째로 건너뛰는 방법도 있다. 45 | - 하지만 이런 방식은 Account 엔티티가 압/출력 모델에 모두 영향을 받게 된다. 46 | -------------------------------------------------------------------------------- /chapter11/pkch93.md: -------------------------------------------------------------------------------- 1 | # 11장. 의식적으로 지름길 사용하기 2 | 3 | - [11장. 의식적으로 지름길 사용하기](#11장-의식적으로-지름길-사용하기) 4 | - [왜 지름길은 깨진 창문과 같을까?](#왜-지름길은-깨진-창문과-같을까) 5 | - [깨끗한 상태로 시작할 책임](#깨끗한-상태로-시작할-책임) 6 | - [핵사고날 아키텍처에서의 지름길](#핵사고날-아키텍처에서의-지름길) 7 | - [Use Case 간에 모델 공유](#use-case-간에-모델-공유) 8 | - [Input / Output 모델로 도메인 객체 사용하기](#input--output-모델로-도메인-객체-사용하기) 9 | - [들어오는 포트 생략](#들어오는-포트-생략) 10 | - [Application Service 생략](#application-service-생략) 11 | 12 | 먼저 지름길이 무조건 나쁜것은 아니다. 때때로는 일정을 맞추기 위해서 의식적으로 지름길을 택하고 `기술부채` 후에 이를 보정하는 방식을 택할 수 있다. 다만, 지름길이라는 것을 모르고 사용한다면 추후에 큰 문제가 될 수 있다. 13 | 14 | ## 왜 지름길은 깨진 창문과 같을까? 15 | 16 | > Broken Windows란 깨진 유리창 이론에서 깨진 유리창에 해당한다. 17 | 깨진 유리창을 방치해두면 그 지점을 중심으로 범죄가 확산된다는 사회 무질서와 관련된 이론이다. 18 | > 19 | > 참고: [https://ko.wikipedia.org/wiki/깨진_유리창_이론](https://ko.wikipedia.org/wiki/%EA%B9%A8%EC%A7%84_%EC%9C%A0%EB%A6%AC%EC%B0%BD_%EC%9D%B4%EB%A1%A0) 20 | 21 | 프로그래밍에서 지름길을 택하는 것은 깨진 유리창과 유사하다. 22 | 23 | - 저품질의 코드베이스에서 작업을 하는 경우 더 저품질의 코드를 작성할 가능성이 높다. 24 | - 코딩 규칙을 많이 위반한 코드베이스에서 작업을 할수록 또 다른 코딩 규칙을 위반할 가능성이 높다. 25 | - 많은 지름길을 사용한 코드베이스에서 작업을 할수록 또 다른 지름길을 사용할 가능성이 높다. 26 | 27 | ## 깨끗한 상태로 시작할 책임 28 | 29 | 코드 작업이 깨진 유리창의 차와 같지는 않지만 코드를 작성하는 프로그래머는 무의식적으로 깨진 유리창 심리의 대상이 될 수 있다. 즉, 지름길을 사용한 코드가 프로젝트 내에 있다면 무의식적으로 지름길 방식으로 코드를 작성할 수 있다는 것이다. 이와 같은 이유로 프로젝트를 시작부터 깔끔하게 유지하는 것이 중요하다. 30 | 31 | 특히 지름길이 많은 코드베이스를 팀의 새로운 개발자가 유지보수한다고 생각해보자. 이 경우는 더더욱 지름길을 사용할 가능성이 높아진다. 32 | 33 | 이를 위해서 Micheal Nygard가 제안한 것과 같이 Architecture Decision Records `ADRs`와 같은 폼으로 추가되는 지름길을 의식적으로 관리할 수 있어야한다. 34 | 35 | Architecture Decision Records: [http://thinkrelevance.com/blog/2011/11/15/documenting-architecture-decisions](http://thinkrelevance.com/blog/2011/11/15/documenting-architecture-decisions) 36 | 37 | ## 핵사고날 아키텍처에서의 지름길 38 | 39 | ### Use Case 간에 모델 공유 40 | 41 | `4장. Implementing a Use Case`에서 다른 Use Case에서는 다른 input, output 모델을 사용해야한다고 했다. 다른 Use Case에서 같은 input, output 모델을 사용하는 경우에는 두 Use Case 간에 결합도를 증가시킨다. 42 | 43 | ![Share_Use_Case_Model](https://user-images.githubusercontent.com/30178507/160421107-51d0c66d-a431-46ec-a90e-86b5b4b0f4f1.png) 44 | 45 | 위 그림에서 SendMoneyUseCase와 RevokeActivityUseCase는 SendMoneyCommand input 모델을 공유하고 있다. 이는 만약 SendMoneyCommand에 변경이 있는 경우에 SendMoneyUseCase와 RevokeActivityUseCase 둘 다에 영향을 미친다. output 모델을 공유하는 경우도 마찬가지이다. 46 | 47 | 예외적으로 Use Case 간에 input, output 모델을 공유해도 되는 경우가 있을 수 있다. 이는 기능적인 범위가 Use Case 간에 일치하는 경우에는 가능하다. 단, 현재는 범위가 일치하더라도 추후에 분리될 가능성이 있다면 이는 input, output 모델을 공유하는 것은 결국 지름길이 된다. 48 | 49 | 만약 처음에 동일한 기능으로 잡혀있다면 같은 input, output 모델을 사용하여 개발하고 추후에 분리되야하는 상황에서 분리하여 작업하는 것도 좋다고 생각한다. 무조건적으로 분리하는 것보다도... 50 | 51 | > 모든 기능은 정책적으로 얼마든지 변경이 가능하기 때문에 결국은 모든 기능마다 분리해서 작업하는 것이 맞지 않을까 싶다. 52 | > 53 | > 그나마 CRUD의 작업만 하는 어드민 작업에서는 공유해도 무방하지 않을까 싶다. 54 | 55 | ### Input / Output 모델로 도메인 객체 사용하기 56 | 57 | input, output 모델로 도메인 객체를 사용하는 것은 도메인 객체가 변경될 이유를 추가하는 것과 같다. 즉, 비즈니스적인 요구사항의 변화 뿐만 아니라 내부, 외부 응답 모델의 변화로도 도메인 객체를 변화시킨다. 58 | 59 | ![Command_Domain_Object](https://user-images.githubusercontent.com/30178507/160421103-b1959ca7-8eb1-4ba4-b1f3-b1a98b3aba06.png) 60 | 61 | 만약 SendMoneyUseCase에서 로직을 처리하는데 Account 객체에서 정의된 필드 외에 다른 필드가 추가적으로 필요하다고 가정한다. 이 경우에는 Account 도메인 객체를 input 모델로 사용하고 있기 때문에 결국 Account의 도메인 로직에는 필요가 없더라도 Account에 필드를 추가해야한다. 62 | 63 | ## 들어오는 포트 생략 64 | 65 | 들어오는 포트의 생략은 도메인 로직의 진입점을 명백히 하지 못한다. 66 | 67 | ![skip_port](https://user-images.githubusercontent.com/30178507/160421097-de2f8a43-1028-4963-b397-8b2f95c00a4a.png) 68 | 69 | 들어오는 포트를 생략하면 incoming adapter와 application 레이어 사이에 추상화 계층을 하나 생략하는 것과 같다. 이는 도메인 로직의 진입점을 생략하는 것이므로 애플리케이션 내부를 더욱 잘 알아야한다는 문제가 있다. 70 | 71 | 애플리케이션 유지보수 관점에서 애플리케이션 내부에 대해 잊어먹을수도 있고 팀에 새로운 개발자가 왔을때 코드 파악에 어려움을 줄 수 있으므로 들어오는 포트를 구현하는 것이 바람직하다. 72 | 73 | ## Application Service 생략 74 | 75 | Service 대신에 Adapter를 Use Case의 구현체로 사용하는 경우 다음과 같은 형태가 될 수 있다. 76 | 77 | ![skip_service](https://user-images.githubusercontent.com/30178507/160421092-c93bbefe-7580-4d9c-a380-c7a33fdea614.png) 78 | 79 | 간단한 CRUD Use Case에 대해서 위와 같이 구현하는 경향이 있다. 즉, 복잡한 도메인 로직 없이 CRUD만 Persistence Adapter에 요청하는 형식이다. 80 | 81 | 하지만 이는 incoming adapter와 outgoing adapter에서 모델을 같이 공유해야한다. 이는 input 모델로써 도메인 모델을 사용하게됨을 의미한다. 82 | 83 | 여기에 더해서 만약 간단한 CRUD에서 더 복잡한 도메인 로직이 추가되는 경우에 persistence adapter에 도메인 로직을 구현하는 형태로 발전할 가능성이 있다. 이는 도메인 로직이 persistence 레이어와 분산이 되므로 향후 애플리케이션의 유지보수를 힘들게 만든다. 84 | -------------------------------------------------------------------------------- /chapter12/[Eunmi]12.아키텍처스타일정하기.md: -------------------------------------------------------------------------------- 1 | - 언제 실제로 육각형 아키텍처 스타일을 사용해야 할까? 2 | - 언제 육각형 아키텍처 스타일 대신 전통적인 계층형 아키텍처 스타일을 고수 해야 할까? 3 | 4 | 이 두가지가 가장 궁극적인 질문 이다. 5 | 6 | # 도메인은 왕이다 7 | 8 | 육각형 아키텍처 스타일의 주요 특징은 자유롭게 도메인 코드를 개발할 수 있다는 점이다. 9 | 10 | > *외부의 영향을 받지 않고 도메인 코드를 자유롭게 발전시킬 수 있다는 것은 육각형 아키텍처 스타일이 내세우는 가장 중요한 가치다.* 11 | > 12 | 13 | 이것이 육각형 아키텍처 스타일이 도메인 주도 설계 방식과 정말 잘 어울리는 이유다. 14 | 15 | 아키텍처 스타일을 사용할지 말지를 결정할 첫 번째 지표로서, 도메인 코드가 애플리케이션에서 가장 중요한 것이다. 16 | 17 | # 경험이 왕이다 18 | 19 | 아키텍처 스타일에 대해서 괜찮은 결정을 내리는 유일한 방법은 다른 아키텍처 스타일을 경험해 보는 것이다. 개념에 익숙해지고 스타일에 익숙해져 편하게 느껴지는 스타일을 개발해 보아라. 20 | 21 | 그러면 이 경험이 다음 아키텍처 결정을 이끌어 줄 것이다. 22 | 23 | # 그때그때 다르다 24 | 25 | 어떤 아키텍처 스타일을 골라야 하는가에 대한 저자의 대답은 어떤 소프트웨어를 만드느냐에 따라서 다르고, 도메인 코드의 역할에 따라서도 다르다. 팀의 경험에 따라서도 다르고 최종적으로 내린 결정이 마음에 드느냐에 따라서도 다르다. -------------------------------------------------------------------------------- /chapter12/mskim.md: -------------------------------------------------------------------------------- 1 | # 12 아키텍처 스타일 결정하기 2 | 3 | 지금까지 육각형 아키텍처를 사용해서 웹 어플리케이션을 만드는 방법을 살펴봤다. 그렇다면 언제 육각형 아키텍처 스타일을 사용해야 할까? 4 | 5 | ## 도메인이 왕이다 6 | 7 | > 육각형 아키텍처 스타일을 사용할지 첫번째 지표 : 도메인 코드가 애플리케이션에서 가장 중요한 것인가? 8 | 9 | - 육각형 아키텍처 스타일은 결국 영속성 관심사나 외부 시스템에 대한 의존성 등의 변화로부터 자유롭게 도메인 코드를 개발 할 수 있다는 것 10 | - 외부의 영향을 받지 않고 도메인 코드를 자유롭게 발전시킬 수 있다는 것은 육각형 아키텍처 스타일이 내세우는 가장 중요한 가치 11 | - 이것이 육각형 아키텍처 스타일이 도메인 주도 설계 방식과 정말 잘 어울리는 이유 12 | - DDD에서는 도메인이 개발을 주도하고 도메인 외 다른 기술들을 생각할 필요가 없게 되면 도메인에 대해 가장 잘 고려할 수 있음 13 | 14 | ## 경험이 여왕이다 15 | 16 | - 습관은 인간이 선택을 무의식적으로 할 수 있도록 도와준다. 좋을 수도 나쁠 수도... 17 | - 따라서 아키텍처를 결정할 때 괜찮은 결정을 내리기 위해서는 다른 아키텍처를 경험해 보는 것이다. 18 | - 작은 모듈에 먼저 시도해보고 이 책에 있는 아이디어들을 적용하면서 자신만의 아이디어들을 추가해보자 19 | - 이 경험이 더 좋은 아키텍처를 결정할 수 있게 할 수 있다 20 | 21 | ## 그때그때 다르다 22 | 23 | - 결국 아키텍처 스타일은 그때그때 다르다 24 | - 어떤 소프트웨어를 만드느냐, 도메인 코드의 역할에 따라, 팀의 경험에 따라, 최종적으로 내린 결정이 마음에 드느냐에 따라서도 다르다 25 | 26 | -------------------------------------------------------------------------------- /chapter2/images/1.2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/726b041246229b733566547278b45fd044534dfe/chapter2/images/1.2.png -------------------------------------------------------------------------------- /chapter2/images/2.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/726b041246229b733566547278b45fd044534dfe/chapter2/images/2.1.png -------------------------------------------------------------------------------- /chapter2/images/2.2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/726b041246229b733566547278b45fd044534dfe/chapter2/images/2.2.png -------------------------------------------------------------------------------- /chapter2/images/2.3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/726b041246229b733566547278b45fd044534dfe/chapter2/images/2.3.png -------------------------------------------------------------------------------- /chapter2/images/2.4.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/726b041246229b733566547278b45fd044534dfe/chapter2/images/2.4.jpeg -------------------------------------------------------------------------------- /chapter2/kooku0..md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 02 의존성 역전하기 3 | --- 4 | 5 | 이번 장에서는 계층형 아키텍처의 대안에 대해 이야기 해보자. 먼저 단일 책임 원칙(Single Responsibility Principle, SRP)과 의존성 역전 원칙(Dependency Inversion Principle, DIP)에 대해 이야기하는 것으로 시작하자. 6 | 7 | ## 단일 책임 원칙 8 | 9 | 이 원칙의 일반적인 해석은 다음과 같다. 10 | 11 | > 하나의 컴폰넌트는 오로지 한 가지 일만 해야하고, 그것을 올바르게 수행해야 한다. 12 | 13 | 이는 단일 책임 원칙의 실제 의도는 아니다. 14 | 15 | 단일 책임 원칙의 실제 정의는 다음과 같다. 16 | 17 | > 컴포넌트를 변경하는 이유는 오직 하나뿐이어야 한다. 18 | 19 | 만약 컴포넌트를 변경할 이유가 한 가지라면 우리가 **어떤 다른 이유로** 소프트웨어를 변경하더라도 이 컴포넌트에 대해서는 전혀 신경 쓸 필요가 없다. 소프트웨어가 변경되더라도 여전히 우리가 기대한 대로 동작할 것이기 때문이다. 20 | 21 | 안타깝게도 변경할 이유라는 것은 컴포넌트 간의 의존성을 통해 너무도 쉽게 전파된다. 22 | 23 | 2.1 28 | 29 | ``` 30 | 어떤 컴포넌트의 의존성 각각은 이 컴포넌트를 변경하는 이유 하나씩에 해당한다. 점선 화살표처럼 전이 의존성(transitive dependency) 이라고 하더라도 말이다. 31 | 32 | > 전의 의존성: 프로그램이 참조하고 있는 컴포넌트로부터 전이된 의존성 33 | 34 | ``` 35 | 36 | 컴포넌트 A는 여러 컴포넌트에 의존하는 반면 E는 의존하는 것이 없다. 37 | 38 | 컴포넌트 E를 변경할 유일한 이유는 새로운 요구사항에 의해 E의 기능을 바꿔야 할 때 뿐이다. A의 경우 어떤 컴포넌트가 바뀌든 같이 바뀌어야 한다. 39 | 40 | ## 의존성 역전 원칙 41 | 42 | 계층형 아키텍처에서 계층 간 의존성은 항상 다음 계층인 아래 방향을 가리킨다. 단일 책임 원칙을 고수준에서 적용할 때 상위 계층들이 하위 계층들에 비해 변경할 이유가 더 많다는 것을 알 수 있다. 43 | 44 | 그러므로 영속성 계층을 변경할 때마다 잠재적으로 도메인 계층도 변경해야 한다. 45 | 46 | 이 의존성을 어떻게 제거할 수 있을까? 47 | 48 | 의존성 역전 원칙이 답을 알려준다. 49 | 50 | > 코드상의 어떤 의존성이든 그 방향을 바꿀 수(역전시킬 수) 있다. 51 | 52 | 사실 의존성의 양쪽 코드를 모두 제어할 수 있을 때만 의존성을 역전시킬 수 있다. 만약 서드파티 라이브러리에 의존성이 있다면 해당 라이브러리를 제어할 수 없기 때문에 이 의존성을 역전시킬 수 없다. 53 | 54 | ![img](./images/1.2.png) 55 | 56 | 도메인 계층에 영속성 계층의 엔티티와 리포지토리와 상호작용하는 서비스가 하나 있다. 57 | 58 | 엔티티는 도메인 객체를 표현하고 도메인 코드는 이 엔티티들의 상태를 변경하는 일을 중심으로 하기 때문에 먼저 엔티티를 도메인 계층으로 올린다. 59 | 60 | 이제 영속성 계층의 리포지토리가 도메인 계층에 있는 엔티티에 의존하기 때문에 두 계층 사이에 순한 의존성(circular dependency)이 생긴다. 이 부분이 바로 DIP를 적용하는 부분이다. 도메인 계층에 리포지토리에 대한 인터페이스를 만들고, 실제 리포지토리는 영속성 계층에서 구현하게 하는 것이다. 61 | 62 | ![img](./images/2.2.png) 63 | 64 | ``` 65 | 66 | 도메인 계층에 인터페이스를 도입함으로써 의존성을 역전시킬 수 있고, 그 덕분에 영속성 계층이 도메인 계층에 의존하게 된다. 67 | 68 | ``` 69 | 70 | 이로써 영속성 코드에 있는 의존성으로부터 도메인 로직을 해방시켰다. 71 | 72 | ## 클린 아키텍처 73 | 74 | 로버트 C. 마틴은 '클린 아키텍처'에서는 설계가 비즈니스 규칙의 테스트를 용이하게 하고, 비즈니스 규칙은 프레임워크, 데이터베이스, UI 기술, 그 밖의 외부 애플리케이션이나 인터페이스로부터 독립적일 수 있다고 이야기 했다. 75 | 76 | 이는 도메인 코드가 밖으로 향하는 어떤 의존성도 없어야 함을 의미했다. 대신 의존성 역전 원칙의 도움으로 모든 의존성이 도메인 코드를 향하고 있다. 77 | 78 | ![img](./images/2.3.png) 79 | 80 | ``` 81 | 클린 아키텍처에서 모든 의존성은 도메인 로직을 향해 안쪽 방향으로 향한다. 82 | ``` 83 | 84 | 이 아키텍처에서 가장 중요한 규칙은 의존성 규칙으로, 계층 간의 모든 의존성이 안쪽으로 향해야 한다는 것이다. 85 | 86 | 코어에는 주변 유스케이스에서 접근하는 도메인 엔티티들이 있다. 유스케이스는 단일 책임을 갖기 위해 조금 더 세분화돼 있다. 이전에 이야기했던 **넓은 서비스** 문제를 피할 수 있다. 87 | 88 | 도메인 코드에서는 어떤 영속성 프레임워크나 UI 프레임워크가 사용되는지 알 수 없기 때문에 특정 프레임워크에 특화된 코드를 가질 수 없고 비즈니스 규칙에 집중할 수 있다. 그래서 도메인 코드를 자유롭게 모델링 할 수 있다. 89 | 영속성이나 UI에 특화된 문제를 신경 쓰지 않아도 된다면 이렇게 하기가 굉장히 수월해진다. 90 | 91 | 그러나 클린 아키텍처에는 대가가 따른다. 도메인 계층이 영속성이나 UI같은 외부 계층과 철저하게 분리돼야 하므로 애플리케이션의 엔티티에 대한 모델을 각 계층에서 유지보수해야 한다. 92 | 93 | 하지만 도메인 코드를 프레임워크에 특화된 문제로부터 해방시키고 결합이 제거된다. 94 | 95 | 클린 아키텍처는 다소 추상적이기 때문에 조금 더 깊게 들어가서 클린 아키텍처의 원칙들을 조금 더 구체적으로 만들어주는 '육각형 아키텍처'에 대해 살펴보자. 96 | 97 | ## 육각형 아키텍처(헥사고날 아키텍처) 98 | 99 | '육각형 아키텍처'라는 용어는 알리스테어 콕번이 만든 용어다. 100 | 101 | ![img](./images/2.4.jpeg) 102 | 103 | ``` 104 | 육각형 아키텍처는 애플리케이션 코어가 각 어댑터와 상호작용하기 위해 특정 포트를 제공하기 때문에 '포트와 어댑터'(ports-and-adapters) 아키텍처라고도 불린다. 105 | ``` 106 | 107 | 육각형 안에는 도메인 엔티티와 이와 상호작용하는 유스케이스가 있다. 육각형에서 외부로 향하는 의존성이 없기 때문에 마틴이 클린 아키텍처에서 제시한 의존성 규칙이 그대로 적용된다는 점을 주목하자. 대신 모든 의존성은 코어를 향한다. 108 | 109 | 왼쪽에 있는 어댑터들은 (애플리케이션 코어를 호출하기 때문에) 애플리케이션을 주도하는 어댑터들이다. 반면 오른쪽에 있는 어댑터들은 (애플리케이션 코어에 의해 호출되기 때문에) 애플리케이션에 의해 주도되는 어댑터들이다. 110 | 111 | 애플리케이션 코어와 어댑터들 간의 통신이 가능하려면 애플리케이션 코어가 각각의 포트를 제공해야 한다. 주도하는 어댑터(driving adapter)에게는 그러한 포트가 코어에 있는 유스케이스 클래스들에 의해 구현되고 호출되는 인터페이스가 될 것이고, 주도되는 어댑터(driven adapter)에게는 그러한 포트가 어댑터에 의해 구현되고 코어에 의해 호출되는 인터페이스가 될 것이다. 112 | 113 | 클린 아키텍처처럼 육각형 아키텍처도 계층으로 구성할 수 있다. 가장 바깥쪽에 있는 계층은 애플리케이션과 다른 시스템 간의 번역을 담당하는 어댑터로 구성돼 있다. 다음으로 포트와 유스케이스 구현체를 결합해서 애플리케이션 계층을 구성할 수 있는데, 이 두 가지가 애플리케이션의 인터페이스를 정의하기 때문이다. 마지막 계층에는 도메인 엔티티가 위치한다. 114 | 115 | ## 유지보수 가능한 소프트웨어를 만드는 데 어떻게 도움이 될까? 116 | 117 | 클린 아키텍처, 육각형 아키텍처 모두 의존성을 역전시켜 도메인 코드가 다른 바깥쪽 코드에 의존하지 않게 함으로써 영속성과 UI에 특화된 모든 문제로부터 도메인 로직의 결합을 제거하고 코드를 변경할 이유의 수를 줄일 수 있다. 그리고 변경할 이유가 적을수록 유지보수성은 좋아진다. 118 | 119 | 또한 도메인 코드는 비즈니스 문제에 딱 맞도록 자유롭게 모델링될 수 있고, 영속성 코드와 UI 코드도 영속성 문제와 UI 문제에 맞게 자유롭게 모델링될 수 있다. 120 | -------------------------------------------------------------------------------- /chapter2/김민석.md: -------------------------------------------------------------------------------- 1 | # 02 의존성 역전하기 2 | 3 | 2장에서는 1장의 계층형 아키텍처의 문제점에 대한 대안에 대해 얘기한다. 4 | 5 | 6 | ## 단일 책임 원칙 7 | 8 | Single Responsibility Principle(SRP) 9 | 10 | `하나의 컴포넌트는 오로의 한가지 일만 해야 하고, 그것을 올바르게 수행해야 한다.` 11 | 12 | 라고 알고있지만 사실 실제 정의는 13 | 14 | `컴포넌트를 변경하는 이유는 오직 하나뿐이어야 한다.` 15 | 16 | http://www.butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod 17 | 18 | 아키텍처에서 가지는 의미 : 다른 이유로 변경된다면 이 컴포넌트는 안 변한다. 19 | 20 | 그런데... SRP를 따르다보니 변경할 이유가 컴포넌트 간의 의존성을 통해 너무 쉽게 전파된다. 21 | 22 | ![image](https://user-images.githubusercontent.com/6725753/153800897-a0ba823a-3ce9-4e6e-a833-393ce2a9bc50.png) 23 | 24 | E에 비해 A는 의존성 때문에 변경할 확률이 높다. 25 | 26 | 시간이 갈수록 바꿀건 많아지는데 SRP 때문에 변경이 어렵고 비용이 올라간다. 27 | 그리고, 변경할 이유가 많아지면 다른 컴포넌트가 실패하는 원인이 될 수도 있다. 28 | 29 | `의존이 많이 걸리는 컴포넌트의 변경은 위험하다. 의존의 방향이 매우 중요하다!` 30 | 31 | 32 | ## 부수 효과에 관한 이야기 33 | 34 | 클라이언트가 핵심적인 컴포넌트 변경에 따른 위험을 두려워해서 더 이상하고 비용이 많이 드는 방식을 주문한 이야기. 변경에 대한 부수효과가 이렇게나 무섭습니다. 우리는 좋은 아키텍처를 통해 좋은 소프트웨어를 만들어봅시다. 35 | 36 | ## 의존성 역전 원칙 37 | 38 | ![image](https://user-images.githubusercontent.com/6725753/153814764-56e8f808-a537-4331-97a8-fa6f70a769e1.png) 39 | 40 | 계층형 아키텍처인 경우에 의존성 방향에 따라 상위 계층이 하위 계층에 비해 변경 확률이 높다. 41 | 따라서, 영속성 계층 변경 시 도메인 계층도 변경된다. 도메인 코드를 바꾸고 싶지 않기 때문에 의존성을 제거하고 싶다. => 의존성 역전 원칙 사용 42 | 43 | ` DIP : 코드상의 어떤 의존성이든 그 방향을 바꿀 수(역전시킬 수) 있다.` 44 | 45 | 1. 엔티티를 도메인 계층으로 옮긴다 => 순한 의존성 발생 46 | 2. 도메인 계층에 리포지터리 인터페이스 만들고 리포지터리 구현체를 영속성 계층에 만든다(DIP) 47 | 48 | ![image](https://user-images.githubusercontent.com/6725753/153814851-f9a4655d-aa6f-4b5f-9f6a-16abf1a8136b.png) 49 | 50 | 의존성으로부터 도메인 로직 해방! 51 | 52 | DIP는 뒤에 나올 두 가지 아키텍처 스타일(클린 / 헥사고날)의 핵심기능. 53 | 54 | ## 클린 아키텍처 55 | 56 | ![image](https://user-images.githubusercontent.com/6725753/153989315-a9f268a8-07bc-47a7-b254-6dc168a0a4e7.png) 57 | 58 | - 비즈니스 규칙이 독립적(프레임워크, 데이터베이스, UI, 다른 앱 등등 으로부터) 59 | - 비즈니스 규칙 테스트 용이 60 | - 도메인 코드가 바깥으로 향하는 의존성이 없음(DIP를 통해 안으로) 61 | - 도메인 엔티티가 코어에 존재. SRP로 인해 엔티티는 세분화 => 넓은 서비스 문제 해결 62 | - 도메인 주변으로 앱의 다른 모든 컴포넌트(디비/UI 등)가 존재 63 | - 바깥쪽 계층은 서드파티에 어댑터 제공 64 | - DDD 적용 가능 65 | - 단점 66 | - 도메인 엔티티가 독립되어야 하기 때문에 67 | - ORM 같은 경우에 영속성 계층에서 도메인 엔티티를 따로 가지고 있고 계층간에 변환해서 사용해야 함 68 | - 하지만 당연해 보임. 예를 들어 JPA에서 관리하는 엔티티에 기본생성자는 도메인과 상관없지만 프레임워크에는 필요함. 69 | 70 | 클린 아키텍처의 모호함을 더 구체적으로 푼 육각형 아키텍처를 알아보자 71 | 72 | ## 육각형 아키텍처(헥사고날 아키텍처) 73 | 74 | ![image](https://user-images.githubusercontent.com/6725753/153996198-e8d1631d-eda8-4fb0-bda5-00e602a8d631.png) 75 | 76 | - 육각형 안에는 도메인 엔티티와 이와 상호작용하는 유스케이스 존재 77 | - 육각형에서 외부로 향하는 의존성 없음. 모든 의존성은 코어를 향함 78 | - 육각형 바깥에는 앱과 상호작용하는 다양한 어댑터 존재 79 | - 웹브라우저와 상호작용하는 웹 어댑터 / 데이터베이스와 상호작용하는 어댑터 / 외부시스템과 상호작용하는 어댑터 등 80 | - 왼쪽은 코어를 호출하는 어댑터(앱을 주도하는) 81 | - 오른쪽은 코어가 호출하는 어댑터(앱에 주도되는) 82 | - 코어와 어댑터간 통신은 포트를 통함 83 | - 포트 84 | - 주도하는 어댑터(왼쪽/인풋)에게는 코어에 있는 유스케이스에 의해 구현되고 어댑터로부터 호출되는 인터페이스 85 | - For driving adaptors, such a port might be an interface that is implemented by one of the use case classes in the core and called by adaptor. 86 | - 주도되는 어댑터(오른쪽/아웃풋)에게는 어댑터에 의해 구현되고 코어에 의해 호출되는 인터페이스 87 | - 포트와 어댑터 아키텍처로 알려져 있음 88 | - 계층 89 | - 바깥 : 어댑터. 앱과 다른 시스템간의 번역 담당 90 | - 중간 : 포트와 유스케이스 구현체. 애플리케이션 계층. 인터페이스 정의 91 | - 안 : 도메인 엔티티 92 | 93 | ## 유지보수 가능한 소프트웨어를 만드는 데 어떻게 도움이 될까? 94 | 95 | - DIP로 인해 도메인 코드가 다른 어떤 컴포넌트에도 의존하지 않게 함으로써 코드를 변경할 이유의 수를 줄인다. 96 | - 도메인 코드는 비즈니스 문제에만 집중하고 영속성 코드와 UI 코드도 각자의 문제이 집중할 수 있다. 97 | 98 | 99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /chapter3/jiaekim.md: -------------------------------------------------------------------------------- 1 | # 3장. 코드 구성하기 2 | 3 | # 패키지 구조 4 | 5 | ## 1. 계층으로 구성하기 6 | 7 | ```java 8 | buckpal 9 | |-- domain 10 | | |-- Account 11 | | |-- Activity 12 | | |-- AccountRepositoryy 13 | | |-- AccountService 14 | |-- persistence 15 | | |-- AccountRepositoryImpl 16 | |-- web 17 | |-- AccountController 18 | ``` 19 | 20 | 웹 계층(web), 도메인 계층(domain), 영속성 계층(persistence)로 구분했다. 21 | 22 | ### 1.1 계층으로 구성한 패키지 구조가 최적의 구조가 아닌 이유 23 | 24 | **이유1.** 어플리케이션의 기능 조각(functional slice)이나 **특성(feature)을 구분 짓는 패키지 경계가 없다.** 25 | 26 | - 추가적인 구조가 없다면, 서로 연관되지 않은 기능들끼리 예상하지 못한 부수효과를 일으킬 수 있는 클래스들의 묶음으로 변모할 수 있다. 27 | 28 | **이유2.** 어플리케이션이 **어떤 유스케이스들을 제공하는지 파악할 수 없다.** 29 | 30 | - 서비스 내의 어떤 메서드가 특정 기능에 대한 책임을 수행하는지 찾아야 한다. 31 | 32 | **이유3. 패키지 구조를 통해 아키텍처를 파악할 수 없다.** 33 | 34 | - 어떤 기능이 웹 어댑터에서 호출되는지, 영속성 어댑터가 도메인 계층에 어떤 기능을 제공하는지 한눈에 볼 수 없다. 35 | 36 | ## 2. 기능으로 구성하기 37 | 38 | ```java 39 | buckpal 40 | |-- account 41 | |-- Account 42 | |-- AccountController 43 | |-- AccountRepository 44 | |-- AccountRepositoryImpl 45 | |-- SendMoneyService 46 | ``` 47 | 48 | account 패키지로 묶고 계층 패키지를 없앴다. 49 | 50 | - 장점 51 | - package-private 접근 수준으로 각 기능 사이의 불필요한 의존성을 방지할 수 있다. 52 | - 단점 53 | - 가시성을 떨어뜰인다. 54 | - package-private 접근 수준을 이용해 도메인 코드가 실수로 영속성 코드에 의존하는 것을 막을 수 없다. 55 | 56 | ## 3. 아키텍처적으로 표현력 있는 패키지 구조 57 | 58 | ```java 59 | buckpal 60 | |-- account 61 | |-- adapter 62 | | |-- in 63 | | | |-- web 64 | | | |-- AccountController 65 | | |-- out 66 | | | |-- persistence 67 | | | |-- AccountPersistenceAdapter 68 | | | |-- SpringDataAccountRepository 69 | |-- domain 70 | | |-- Account 71 | | |-- Activity 72 | |-- application 73 | |-- SendMoneyService 74 | |-- port 75 | |-- in 76 | | |-- SendMoneyUseCase 77 | |-- out 78 | | |-- LoadAccountPort 79 | | |-- UpdateAccountStatePort 80 | ``` 81 | 82 | ### 3.1 핵사고날 아키텍처 패키지 구조 83 | 84 | Account와 관련된 유스케이스는 모두 account 패키지 안에 있다. 85 | 86 | - domain 87 | - 도메인 모델 (Account) 88 | - application 89 | - 도메인 모델을 둘러싼 서비스 계층 (SendMoneyService) 90 | - 인커밍 포트 인터페이스 (SendMoneyUseCase) 91 | - 아웃고잉 포트 인터페이스 (LoadAccountPort, UpdateAccountStatePort) 92 | - adapter 93 | - 어플리케이션 계층의 인커밍 포트를 호출하는 인커밍 어댑터 (Controller) 94 | - 어플리케이션 계층의 아웃고잉 포트에 대한 구현을 제공하는 아웃고잉 어댑터 (PersistenceAdapter, Repository) 95 | 96 | ### 3.2 헥사고날 아키텍처 구조의 장점 97 | 98 | 장점1. 이러한 패키지 구조는 모델-코드 갭(아키텍처-코드 갭)을 효과적으로 다룰 수 있다. 99 | 100 | 101 | > 💡 [모델-코드 갭(model-code gap)](https://www.ben-morris.com/most-architecture-diagrams-are-useless/#:~:text=George%20Fairbanks%20identified%20what%20he,always%20be%20mapped%20into%20code.) 102 | > 아키텍처 모델에는 항상 코드에 매핑할 수 없는 추상적인 개념, 기술 선택 및 설계 결정이 혼합되어 있다. 최종 결과는 모델이 정한 구성 요소의 배열과 반드시 일치하지 않는 소스 코드가 될 수 있다. 103 | 104 | 장점2. 패키지간 접근을 제어할 수 있다. 105 | 106 | - package-private인 adapter 클래스 107 | - 모든 클래스는 application 패키지 내의 포트 인터페이스를 통해 바깥에 호출되기 때문에 adapter는 모두 package-private 접근 수준으로 둬도 된다. 108 | - 어플리케이션 계층에서 어댑터로 향하는 우발적 의존성은 있을 수 없다. 109 | - public이어야 하는 application, domain의 일부 클래스 110 | - application의 port(in, out) 111 | - `SendMoneyUseCase`, `LoadAccountPort`, `UpdateAccountStatePort` 112 | - 도메인 클래스 113 | - `Account`, `Activity` 114 | - package-private이어도 되는 서비스 클래스 115 | - 인커밍 포트 인터페이스 뒤에 숨겨지는 서비스는 public일 필요가 없다. 116 | - `GetAccountBalanceService` 117 | 118 | ## 4. 의존성 주입의 역할 119 | 120 | - 클린 아키텍처의 본질적인 요건 121 | 122 | `어플리케이션이 인커밍/아웃고잉 어댑터에 의존성을 갖지 않아야 한다.` 123 | 124 | - 의존성 역전 원칙 이용 125 | - 어플리케이션 계층에 인터페이스(port)를 만들고 어댑터에 해당 인터페이스를 구현한 클래스를 둔다. 126 | - 모든 계층에 의존성을 가진 중립적인 컴포넌트를 하나 두고, 이 컴포넌트가 아키텍처를 구성하는 대부분의 클래스를 초기화하는 역할을 한다. 127 | - 웹 컨트롤러가 서비스에 의해 구현된 인커밍 포트를 호출한다. 서비스는 어댑터에 의해 구현된 아웃고잉 포트를 호출한다. 128 | 129 | ![image](https://user-images.githubusercontent.com/37948906/154954236-0972c09e-d6c7-41d1-ad79-eedb73822d85.png) 130 | 131 | - AccountController 132 | - SendMoneyUseCase 인터페이스가 필요하므로 의존성 주입을 통해 SendMoneyService 클래스의 인스턴스를 주입 133 | - SendMoneyService 134 | - LoadAccount 인터페이스로 가장한 AccountPersistenceAdapter 클래스의 인스턴스 주입 135 | 136 | 137 | ## 5. 유지보수 가능한 소프트웨어를 만드는 데 어떻게 도움이 될까? 138 | 139 | - 코드에서 아키텍처의 특정 요소를 찾으려면 이제 아키텍처 다이어그램의 박스 이름을 따라 패키지 구조를 탐색하면 된다. 140 | - 이를 통해 의사소통, 개발, 유지보수 모두가 조금 더 수월해진다. -------------------------------------------------------------------------------- /chapter3/seyun.md: -------------------------------------------------------------------------------- 1 | # 3. 코드 구성하기 2 | 3 | ## 계층으로 구성하기 4 | 5 | ```jsx 6 | buckapl 7 | |--- domain 8 | | |----- Account 9 | | |----- Activity 10 | | |----- AccountRepository 11 | | |----- AccountService 12 | |--- persistence 13 | | |----- AccountRepositoryImpl 14 | |--- web 15 | | |----- AccountController 16 | ``` 17 | 18 | ### 문제점 19 | 20 | 1. 계층으로 코드를 구성하면 기능적인 측면들이 섞이기 쉽다. 21 | 2. 애플리케이션의 기능 조각(functional slice)이나 특성(feature)을 구분 짓는 패키지 경계가 업다. 22 | - 서로 연관되지 않은 기능들끼리 예상하지 못한 부수효과를 일으킬 수 있는 클래스들의 엉망진창 묶음으로 변모할 가능성이 크다. 23 | 3. 애플리케이션이 어떤 유스케이스들을 제공하는지 파악할 수 없다. 24 | 4. 패키지 구조를 통해서는 우리가 목표로 하는 아키텍처를 파악할 수 없다. 25 | 26 | ## 기능으로 구성하기 27 | 28 | ```jsx 29 | buckapl 30 | |--- account 31 | | |----- Account 32 | | |----- Activity 33 | | |----- AccountRepository 34 | | |----- SendMoneyService 35 | | |----- AccountRepositoryImpl 36 | | |----- AccountController 37 | ``` 38 | 39 | ### 장점 40 | 41 | 1. 패키지 경계를 package-private 접근 수준과 결합하면 각 기능 사이의 불필요한 의존성을 방지할 수 있다. 42 | 43 | ### 문제점 44 | 45 | 1. 기능을 기준으로 코드를 구성하면 기반 아키텍처가 명확하게 보이지 않는다. 46 | 47 | ## 아키텍처적으로 표현력 있는 패키지 구조 48 | 49 | ```jsx 50 | buckapl 51 | |--- account 52 | | |----- adapter 53 | | | |----- in 54 | | | | |---- web 55 | | | | | |---- AccountController 56 | | | |----- out 57 | | | | |---- persistence 58 | | | | | |---- AccountPersistenceAdapter 59 | | | | | |---- SpringDataAccountRepository 60 | | |---- domain 61 | | | |----- Account 62 | | | |----- Activity 63 | | |---- application 64 | | | |----- SendMoneyService 65 | | | |----- port 66 | | | | |---- in 67 | | | | | |---- SendMoneyUseCase 68 | | | | |---- out 69 | | | | | |---- LoadAccountPort 70 | | | | | |---- UpdateAccountStatePort 71 | ``` 72 | 73 | ### 장점 74 | 75 | 1. 각 아키텍처 요소들에 정해진 위치가 있어 직관적이다. 76 | 2. 어댑터 코드를 자체 패키지로 이동시키면 필요할 경우 하나의 어댑터를 다른 구현으로 쉽게 교체할 수 있다. 77 | 3. DDD 개념을 직접적으로 대응할 수 있다. 78 | - 상위 레벨 패키지는 다른 바운디드 컨텍스트와 통신할 전용 진입점과 출구(포트)를 포함하는 바운디드 컨텍스트에 해당한다. 79 | 80 | ### 의문점 81 | 82 | - 패키지가 많다는 것은 모든 것을 public으로 만들어 패키지 간의 접근을 허용해야 하는거 아닌가? 83 | - application 패키지 내에 있는 포트 인터페이스를 통하지 않고서 바깥으로 호출되지 않기 때문에 package-private 접근 수준으로 둬도 된다. 84 | 85 | 86 | ## 의존성 주입의 역할 87 | 88 | 어댑터는 그저 애플리케이션 계층에 위치한 서비스를 호출할 뿐이다. 영속성 어댑터와 같이 아웃고잉 어댑터에 대해서는 제어 흐름의 반대 방향으로 의존성을 돌리기 위해 의존성 역전 원칙을 이용해야 한다. 모든 계층에 의존성을 가진 중립적인 컴포넌트를 하나 도입하면 의존성 주입을 활용할 수 있다. 중립적인 컴포넌트는 아키텍처를 구성하는 대부분의 클래스를 초기화하는 역할을 한다. 89 | 90 | ### 예시 91 | 92 | ![image](https://user-images.githubusercontent.com/53366407/151645788-d03b79c4-72cc-43cb-ae06-11ef2c5f54f8.png) 93 | 94 | 웹 컨트롤러가 서비스에 의해 구현된 인커밍 포트를 호출한다. 서비스는 어댑터에 의해 구현된 아웃고잉 포트를 호출한다. 95 | 96 | ## 출처 97 | - [만들면서 배우는 클린 아키텍처 - 자바 코드로 구현하는 클린 웹 애플리케이션](https://www.aladin.co.kr/shop/wproduct.aspx?ItemId=283437942) 98 | -------------------------------------------------------------------------------- /chapter4/[4장] 유스케이스 구현하기_김광훈.md: -------------------------------------------------------------------------------- 1 | # 유스케이스 구현하기 2 | ## 1. 유스케이스 둘러보기 3 | #### 1.1 Flow 4 | 5 | 6 | - (1) 입력을 받는다. 7 | - (2) 비즈니스 규칙을 검증한다. 8 | - (3) 모델 상태를 조작한다. 9 | - (4) 출력을 반환한다. 10 | 11 | #### 1.2 역할 12 | - 유스케이스 코드는 도메인 로직에만 신경써야한다. 13 | - 입력 유효성 검증은 책임에 벗어난다. 14 | - 단, 비즈니스 규칙을 검증한 책임은 존재한다. 15 | 16 |
17 | 18 | ## 2. 입력 유효성 검증 19 | 20 | 21 | - 유스케이스 입력 모델의 생성자에서 입력 유효성을 검증하는 것이 좋다. 22 | - 애플리케이션 계층에서 입력 유효성을 검증해야 한다. 23 | - 그렇지 않으면 애플리케이션 코어 바깥쪽으로부터 유효하지 않은 입력 값을 받게 되어 모델의 상태를 해친다. 24 | 25 |
26 | 27 | ## 3. 생성자의 힘 28 | - 빌더 패턴을 무조건 사용하는 것이 좋은 것인가 ?? 29 | - 저자는 빌더 패턴 없이 생성자를 호출하는 것이 좋다는 점을 어필하고 있다. 30 | - 컴파일의 도움을 받을 수 있음 31 | - IDE 에서 파라미터명 보여줌 32 | 33 | 34 | 35 | - 그렇다면 빌더패턴은 언제 사용하는 것이 좋을까 ?? 36 | - 모든 필드를 다 받는다면 Builder 패턴의 이점은 없다고 생각한다. 37 | - 클래스 내에 전체 필드 개수 보다 적은 생성자가 있는 경우에만 Builder 패턴을 구현하는 것이 좋다고 생각한다. 38 | 39 |
40 | 41 | ## 4. 유스케이스마다 다른 입력 모델 42 | - 결론부터 말하자면 독자는 유스케이스마다 모델을 분리하는 것을 권장한다. 43 | - 예를들어 MoneyCommand 객체를 SendMoneyCommand / GetMoneyCommand 등으로 분리하라고 한다. 44 | - 이렇게 사용하면 null 을 받을 필요 없고, 유스케이스를 훨씬 더 명확하게 만든다고 한다. 45 | 46 |
47 | 48 | ## 5. 비즈니스 규칙 검증하가 49 | 50 | 51 | - 비즈니스 규칙은 유스케이스의 맥락 속에서 의미적인 유효성 검증하는 일 52 | - 비즈니스 규칙은 도메인 엔티티에 안에 넣는 것이 좋다. 53 | 54 |
55 | 56 | ## 6. 풍부한 도메인 모델 vs 빈약한 도메인 모델 57 | #### (1) 풍부한 도메인 모델 58 | - 애플리케이션의 코어에 있는 Entity 에서 많은 도메인 로직이 구현 59 | - 즉, 대부분의 비즈니스 규칙들은 Entity 에 위치하게 된다. 60 | - 따라서 유스케이스에는 사용자의 의도만을 표현하게 된다. 61 | 62 | #### (2) 빈약한 도메인 모델 63 | - 일반적으로 getter / setter 정도만 표현한다. 64 | - 즉, 유스케이스에 비즈니스 로직이 구현되게 된다. 65 | 66 |
67 | 68 | ## 7. 유스케이스마다 다른 모델 출력 69 | - 입력과 비슷하게 출력도 각 유스케이스에 맞게 구체적일수록 좋다. 70 | - 공유 모델은 장기적으로 보았을 때 점점 비대해지게 돼 있다. 71 | 72 |
73 | 74 | ## 8. 읽기 전용 유스케이스는 어떨까 ? 75 | 76 | 77 | - 애플리케이션 코어 관점에서 간단한 데이터 쿼리이다. 78 | - 프로덕트 맥락에서는 유스케이스로 간주되지는 않기 때문에 실제 유스케이스와 구분이 되어야한다. 79 | - 이러한 읽기 전용 유스케이스는 인커밍 전용 포트를 만들고 Query Service 에 구현하는 것이다. -------------------------------------------------------------------------------- /chapter4/jiaekim.md: -------------------------------------------------------------------------------- 1 | # 4장. 유스케이스 구현하기 2 | 3 | `육각형 아키텍처 스타일에서 유스케이스 구현하기` 4 | 5 | - 어플리케이션, 웹, 영속성 계층이 현재 아키텍처에서 아주 느슨하게 결합 6 | - 육각형 아키텍처는 도메인 중심의 아키텍처에 적합하므로 도메인 엔티티를 만들고 엔티티를 중심으로 유스케이스 구현 7 | 8 | ## 1. 도메인 모델 구현하기 9 | 10 | 한 계좌에서 다른 계좌로 송금하는 유스케이스 11 | 12 | 입금과 출금을 할 수 있는 Account 엔티티와 출금 계좌에서 돈을 출금해서 입금 계좌로 돈을 입금 13 | 14 | ```java 15 | package buckpal.account.domain; 16 | 17 | @AllArgsConstructor(access = AccessLevel.PRIVATE) 18 | public class Account { 19 | 20 | @Getter private final AccountId id; 21 | 22 | @Getter private final Money baselineBalance; 23 | 24 | @Getter private final ActivityWindow activityWindow; 25 | 26 | public static Account withoutId( 27 | Money baselineBalance, 28 | ActivityWindow activityWindow) { 29 | return new Account(null, baselineBalance, activityWindow); 30 | } 31 | 32 | public static Account withId( 33 | AccountId accountId, 34 | Money baselineBalance, 35 | ActivityWindow activityWindow) { 36 | return new Account(accountId, baselineBalance, activityWindow); 37 | } 38 | 39 | public Optional getId(){ 40 | return Optional.ofNullable(this.id); 41 | } 42 | 43 | public Money calculateBalance() { 44 | return Money.add( 45 | this.baselineBalance, 46 | this.activityWindow.calculateBalance(this.id)); 47 | } 48 | 49 | public boolean withdraw(Money money, AccountId targetAccountId) { 50 | 51 | if (!mayWithdraw(money)) { 52 | return false; 53 | } 54 | 55 | Activity withdrawal = new Activity( 56 | this.id, 57 | this.id, 58 | targetAccountId, 59 | LocalDateTime.now(), 60 | money); 61 | this.activityWindow.addActivity(withdrawal); 62 | return true; 63 | } 64 | 65 | private boolean mayWithdraw(Money money) { 66 | return Money.add( 67 | this.calculateBalance(), 68 | money.negate()) 69 | .isPositiveOrZero(); 70 | } 71 | 72 | public boolean deposit(Money money, AccountId sourceAccountId) { 73 | Activity deposit = new Activity( 74 | this.id, 75 | sourceAccountId, 76 | this.id, 77 | LocalDateTime.now(), 78 | money); 79 | this.activityWindow.addActivity(deposit); 80 | return true; 81 | } 82 | 83 | @Value 84 | public static class AccountId { 85 | private Long value; 86 | } 87 | 88 | } 89 | ``` 90 | 91 | - Account 엔티티 92 | - 실제 계좌의 현재 스냅샷을 제공 93 | - ActivityWindow 94 | - 한 계좌에 대한 모든 활동(activity)들을 항상 메모리에 한꺼번에 올리는 것은 현명하지 않으므로 지난 며칠 혹은 몇 주간의 범위 활동만 보유 95 | - baselineBalance 96 | - 첫번째 활동 전의 잔고 97 | - 현재 총 잔고는 기준 잔고(baseBalance)에 활동 창의 모든 활동들의 잔고를 합한 값 98 | - withdraw (입금), deposit(출금) 99 | - 새로운 활동을 활동창에 추가 100 | - 출금 전에는 잔고를 초과하는 금액을 출금할 수 없도록 비즈니스 규칙 검사 101 | 102 | 103 | ## 유스케이스 둘러보기 104 | 105 | ```java 106 | 01. 입력을 받는다. 107 | 02. 비즈니스 규칙을 검증한다. 108 | 03. 모델 상태를 변경한다. 109 | 04. 출력을 반환한다. 110 | ``` 111 | 112 | - 입력을 받는다. 113 | - `01. 입력을 받는다` 에서는 `입력 유효성 검증` 을 하지 않는다. 유스케이스 코드에서는 입력 유효성을 검증하지 않고, 도메인 로직에만 신경써야 함 114 | - 따라서 입력 유효성 검증은 다른 곳에서 처리함 115 | - 모델 상태를 변경한다. 116 | - 도메인 객체의 상태를 바꾸고 영속성 어댑터를 통해 구현된 포트로 이 상태를 전달해서 저장 117 | - 출력을 반환한다 118 | - 아웃 고잉 어댑터에서 온 출력값을 유스케이스를 호출한 어댑터로 반환할 출력 객체로 변환 119 | 120 | ```java 121 | package buckpal.account.application.service; 122 | 123 | @RequiredArgsConstructor 124 | @Transactional 125 | public class SendMoneyService implements SendMoneyUseCase { 126 | 127 | private final LoadAccountPort loadAccountPort; 128 | private final AccountLock accountLock; 129 | private final UpdateAccountStatePort updateAccountStatePort; 130 | 131 | @Override 132 | public boolean sendMoney(SendMoneyCommand command) { 133 | // TODO: 비즈니스 규칙 검증 134 | // TODO: 모델 상태 조작 135 | // TODO: 출력 값 반환 136 | } 137 | } 138 | ``` 139 | 140 | - 서비스 141 | - 인커밍 포트 인터페이스인 SendMoneyUseCase 구현 (돈 송금하기) 142 | - 아웃고잉 포트 인터페이스인 LoadAccountPort 호출 (계좌 불러오기) 143 | - 데이터베이스 상태 업데이트를 위해 UpdateAccountStatePort 호출 144 | 145 | ![image](https://user-images.githubusercontent.com/37948906/154954560-c5a6b8d6-3ea2-43c0-a895-eda1fbe537a7.png) 146 | 147 | ## 입력 유효성 검증 148 | 149 | - 어댑터에서 유스케이스에 입력을 전달하기 전에 입력 유효성을 전달한다면? 모든 어댑터에서 유효성 검증을 구현해야 함. 150 | - 따라서 어플리케이션 계층에서 유효성 검증을 해야 함. but 유스케이스 클래스에서 입력 유효성을 검증하지는 않음 151 | 152 | ### 생성자 내에서의 유효성 검증 153 | 154 | - 생성자 내에서 유효성 검증을 한다면? (인커밍 포트 패키지에 위치한 SendMoneyCommand) 155 | 156 | ```java 157 | @Getter 158 | public class SendMoneyCommand { 159 | 160 | private final AccountId sourceAccountId; 161 | private final AccountId targetAccountId; 162 | private final Money money; 163 | 164 | public SendMoneyCommand( 165 | AccountId sourceAccountId, 166 | AccountId targetAccountId, 167 | Money money) { 168 | this.sourceAccountId = sourceAccountId; 169 | this.targetAccountId = targetAccountId; 170 | this.money = money; 171 | requireNonNull(sourceAccountId); 172 | requireNonNull(targetAccountId); 173 | requireNonNull(money); 174 | requireGraterThan(money, 0); 175 | } 176 | } 177 | ``` 178 | 179 | 자바에 Bean Validation API를 활용하면 필드 애너테이션으로 간단하게 사용할 수 있다. 180 | 181 | ```java 182 | @Value 183 | @EqualsAndHashCode(callSuper = false) 184 | public 185 | class SendMoneyCommand extends SelfValidating { 186 | 187 | @NotNull 188 | private final AccountId sourceAccountId; 189 | 190 | @NotNull 191 | private final AccountId targetAccountId; 192 | 193 | @NotNull 194 | private final Money money; 195 | 196 | public SendMoneyCommand( 197 | AccountId sourceAccountId, 198 | AccountId targetAccountId, 199 | Money money) { 200 | this.sourceAccountId = sourceAccountId; 201 | this.targetAccountId = targetAccountId; 202 | this.money = money; 203 | this.validateSelf(); 204 | } 205 | } 206 | ``` 207 | 208 | ```java 209 | package buckpal.common; 210 | 211 | public abstract class SelfValidating { 212 | 213 | private Validator validator; 214 | 215 | public SelfValidating() { 216 | ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); 217 | validator = factory.getValidator(); 218 | } 219 | 220 | protected void validateSelf() { 221 | Set> violations = validator.validate((T) this); 222 | if (!violations.isEmpty()) { 223 | throw new ConstraintViolationException(violations); 224 | } 225 | } 226 | } 227 | ``` 228 | 229 | ### 생성자에서 유효성 검증을 했을 때의 장점 230 | 231 | - 유효하지 않은 상태의 객체를 만드는 것이 불가능하다 232 | - 클래스가 불변이기 때문에 생성자의 인자 리스트에 클래스의 각 속성에 해당하는 파라미터를 포함 233 | - 생성자가 파라미터의 유효성 검증을 하고 있기 때문에 유효하지 않은 객체를 만드는 것은 불가능함 234 | 235 | ### 만약 빌더를 사용한다면? 236 | 237 | ```java 238 | new SendMoneyCommandBuilder() 239 | .sourceAccountId(new AccountId(41L)) 240 | .targetAccountId(new AccountId(42L)) 241 | // ... 다른 여러 필드를 초기화 242 | .build(); 243 | ``` 244 | 245 | 유효성 검증 로직은 생성자에 그대로 있기 대문에 빌더가 유효하지 않은 상태의 객체를 생성하지 못하도록 막을 수 있다. 246 | 247 | 그런데 만약 SendMoneyCommandBuilder에 새로운 필드를 추가해야 한다면? 248 | 249 | 빌더를 호출하는 코드에 새로운 필드를 추가하는 것을 잊고 만다면, 컴파일러에서는 유효하지 않은 불변 객체를 만들려는 시도에 대해 경고해주지는 못하지만 **단위테스트 혹은 런타임에서 유효성 검증 로직이 동작해서 누락된 파라미터에 대한 에러를 던질 것이다.** 250 | 251 | **But 빌더 말고 생성자를 직접 사용했다면 컴파일 에러에 따라 나머지 코드에 즉각 변경사항을 반영**할 수 있었을 것이다. (요즘은 IDE에서 파라미터명 힌트도 주기 때문에 생성자에 많은 인자가 들어가도 어떤 필드가 누락되었는지 잘 알 수 있다.) 252 | 253 | 254 | 255 | 256 | ## 유스케이스마다 다른 입력 모델 257 | 258 | - `계좌 등록하기`와 `계좌 정보 업데이트하기` 두 기능의 입력 모델에 ID의 유무 차이가 있다면 유효성 검사는 어떻게 해야할까? 259 | - 각 유스케이스 전용 입력 모델은 유스케이스를 훨씬 정확하게 만들고 다른 유스케이스와의 결합도를 제거해서 불필요한 부수효과가 발생하지 않게 한다. 260 | 261 | ## 비즈니스 규칙 검증하기 262 | 263 | - 비즈니스 규칙 검증 264 | - 도메인 모델의 현재 상태에 접근한다. 265 | - 유스케이스의 맥락 속에서 의미적인(sementical) 유효성을 검증 266 | - ex) 출금 계좌는 초과 출금되어서는 안된다 267 | - 입력 유효성 검증 268 | - 구문상의(syntactical) 유효성을 검증 269 | - ex) 송금되는 금액은 0보다 커야 한다. 270 | 271 | ```java 272 | public class Account { 273 | // ... 274 | 275 | public boolean withdraw(Money money, AccountId targetAccountId) { 276 | if (!mayWithdraw(money)){ 277 | return false; 278 | } 279 | // ... 280 | } 281 | } 282 | ``` 283 | 284 | 만약 도메인 엔티티에서 비즈니스 규칙을 검증하기가 여의치 않다면 유스케이스 코드에서 도메인 엔티티를 사용하기 전에 해도 된다. 285 | 286 | ```java 287 | @RequiredArgConstructor 288 | @Transactional 289 | public class SendMoneyService implements SendMoneyUseCase { 290 | // ... 291 | 292 | @Override 293 | public boolean sendMoney(SendMoneyCommand command){ 294 | requireAccountExists(command.getSourceAccountId()); 295 | requireAccountExists(command.getTargetAccountId()); 296 | } 297 | } 298 | ``` 299 | 300 | 유효성 검증하는 코드를 호출하고, 유효성 검증이 실패할 경우 유효성 검증 전용 예외를 던진다. 301 | 302 | ## 풍부한 도메인 모델 vs 빈약한 도메인 모델 303 | 304 | - 풍부한 도메인 모델(rich domain model) 305 | - 엔티티에서 가능한 많은 도메인 로직이 구현 306 | - 엔티티들은 상태를 변경하는 메서드를 제공하고, 비즈니스 규칙에 맞는 유효한 변경만을 허용 307 | - 많은 비즈니스 규칙이 유스케이스 구현체 대신 엔티티에 위치 308 | - 빈약한 도메인 모델(anemic domain model) 309 | - 엔티티에 상태를 표현하는 필드와 이 값을 읽고 바꾸기 위한 getter, setter 메서드만 포함하고 어떤 도메인 로직도 가지고 있지 않음 310 | - 도메인 로직은 유스케이스 클래스에만 구현 311 | - 비즈니스 규칙을 검증하고, 엔티티의 상태를 바꾸고, 데이터 베이스 저장을 담당하는 아웃고잉 포트에 엔티티를 전달할 책임 역시 유스케이스 클래스에 있음 312 | 313 | ## 유스케이스마다 다른 출력 모델 314 | 315 | - 입력과 비슷하게 출력도 가능하면 각 유스케이스에 맞게 구체적일수록 좋다 316 | - 출력은 호출자에게 꼭 필요한 데이터만 들고 있어야 한다 317 | - 유스케이스들 간에 같은 출력 모델을 공유하면 유스케이스들도 강하게 결합됨 318 | - 같은 이유로 도메인 엔티티를 출력 모델로 사용하고 싶은 유혹도 견뎌야 한다. 319 | 320 | ## 읽기 전용 유스케이스 321 | 322 | - 읽기전용 유스케이스를 구현하는 방법 323 | 324 | ```java 325 | package buckpal.application.service; 326 | 327 | @RequiredArgsConstructor 328 | class GetAccountBalanceService implements GetAccountBalanceQuery { 329 | private final LoadAccountPort loadAccountPort; 330 | 331 | @Override 332 | public Money getAccountBalance(AccountId, accountId){ 333 | return loadAccountPort.loadAccount(accoutId, LocalDateTime.now()).calculateBalance(); 334 | } 335 | } 336 | ``` 337 | 338 | - 쿼리 서비스 구현 339 | - GetAccountBalanceQuery 인커밍 포트 구현 340 | - 데이터베이스로부터 실제 데이터를 로드하기 위해 LoadAccountPort라는 아웃고잉 포트를 호출 341 | - 읽기 전용 쿼리는 쓰기가 가능한 유스케이스(또는 ‘커멘드’)와 코드 상에서 명확하게 구분 342 | 343 | ⇒ SQS(Command-Query Separation) 344 | ⇒ CQRS(Command-Query Responsibility Segregation) 345 | 346 | - GetAccountBalanceService 서비스에서는 아웃고잉 포트로 쿼리를 전달하는 것 외에 다른 일을 하지 않는다. 여러 계층에 걸쳐 같은 모델을 사용하면 지름길을 써서 클라이언트가 아웃고잉 포트를 직접 호출하게 할 수도 있음 347 | 348 | > CQRS란? 흔히 말하는 CRUD(Create, Read, Update, Delete)에서 CUD(Command)와 R(Query)를 구분하자는 것. 349 | > **CQRS의 장점** 350 | > 1. Read와 CUD 각각에 더 최적화된 Database 구성을 통해서 성능을 더 향상시킬 수 있다. 351 | > 2. Read와 CUD에서 필요한 데이터 형식이 다를 수 있고, 특히 Read는 aggregation(집계 함수) 등의 부가적인 attribute들이 Entity에 필요하게 될 수 있다. R과 CUD를 분리함으로써 R로 인해 Entity의 구조가 변경되는 것을 막을 수 있다. 352 | > 3. R과 CUD를 분리함으로써 과도하게 복잡한 모델을 덜 복합하게 만듦으로서 시스템 복잡도를 줄일 수 있다. 353 | > 출처: [Log.bluayer](https://bluayer.com/37) 354 | 355 | 356 | ## 유지보수 가능한 소프트웨어를 만드는데 어떻게 도움이 될까? 357 | 358 | - 도메인 로직을 우리가 원하는 대로 구현할 수 있도록 허용하지만, 입출력 모델을 독립적으로 모델링한다면 원치 않는 부수효과를 피할 수 있다. 359 | - 유스케이스 간에 모델을 공유하는 것보다 별도 모델을 만들고, 엔티티를 매핑하는 등의 추가 작업을 해주어야 한다. 360 | - 유스케이스별로 모델을 만들면 유스케이스를 명확하게 이해할 수 있고 장기적으로 유지보수하기도 더 쉽다 361 | - 꼼꼼한 입력 유효성 검증, 유스케이스 별 입출력 모델은 지속가능한 코드를 만드는데 큰 도움이 된다. -------------------------------------------------------------------------------- /chapter5/[5장] 웹 어댑터 구현하기_김광훈.md: -------------------------------------------------------------------------------- 1 | # 웹 어댑터 구현하기 2 | ## 1. 의존성 역전 3 | 4 | 5 | - 웹 어댑터는 Incoming 어댑터이다. 6 | - 외부로부터 요청 받음 ---> 애플리케이션 코어 호출 ---> 무슨 일을 해야할지 알려줌 7 | - 애플리케이션 계층을 웹 어댑터가 통신할 수 있는 `포트`를 제공 8 | - 즉, 서비스는 포트 구현 & 웹 어댑터 포트 호출 9 | 10 |
11 | 12 | 13 | 14 | - 제어 흐름이 왼쪽 ---> 오른쪽 15 | - 웹 어댑터가 유스케이스를 직접 호출할 수 있지만 간접 계층을 넣음 16 | - 포트(간접 계층)은 외부와 통신할 수 있는 곳에 대한 명세서 (어떤 통신이 일어나는지 명확하게 알 수 있음) 17 | 18 |
19 | 20 | 21 | 22 | 23 | - 그림의 포트는 Outgoing 포트이다. 24 | - 이 포트는 웹 어댑터에서 구현하고 애플리케이션 코어에서 호출해야 한다. (p 55) 25 | - 정확한 의미는 정확하게 모르곘다. 26 | 27 | 28 | 29 | - 뇌피셜로 위 코드처럼 웹 어댑터에서 SendMoneyCommand 를 구현하고 UseCase 케이스를 호출할때 파라미터로 넘겨주는 것을 의미하는 건가 싶다. 30 | 31 |
32 | 33 | ## 2. 웹 어댑터의 책임 34 | - 웹 어댑터의 입력 모델을 유스케이스의 입력 모델로 변환할 수 있다는 것을 검증해야함 !!! 35 | - HTTP 와 관련된 모든 것은 애플리케이션 계층으로 침투하면 안된다. !!! 36 | - 애플리케이션 코어에서는 바깥 계층에서 HTTP 를 쓰든 뭐를 쓰든 몰라야 한다. 37 | - 이러한 계층 간의 경계는 도메인과 어플리케이션 계층부터 개발하기 시작하면 자연스럽게 생긴다. (???) 38 | 39 |
40 | 41 | ## 3. 컨트롤러 나누기 42 | - 컨트롤러는 너무 적은 것 보다는 많은게 낫다. 43 | - 각 컨틀로러가 가능한 한 좁고 다른 컨트롤러와 가능한 한 적게 공유한느 웹 어댑터 조각을 구현해야한다. 44 | - `그렇다면 궁금: 만약 CSV, JSON, XML 형식으로 제공하는 기능이 있다고 할 때, 하나의 컨트롤러로 할 것 인가 ?? 나눌 것인가 ??` 45 | -------------------------------------------------------------------------------- /chapter5/김기현.md: -------------------------------------------------------------------------------- 1 | # 05 웹 어댑터 구현하기 2 | ### 의존성 역전 3 | 웹 어댑터는 '주도하는' 혹은 '인커밍' 어댑터이며, 외부로부터 요청을 받아 애플리케이션을 호출하고 무슨 일을 해야할지 알려준다 4 | 애플리케이션 계층은 웹 어댑터가 동신할 수 있는 특정 포트를 제공한다 (interface) 5 | ![image](https://user-images.githubusercontent.com/16996054/156940604-f708bcb8-00dd-494d-a0fa-1e32f7d6455d.png) 6 | 7 | **왜 어댑터와 서비스 사이에 간접 계층(포트)을 넣어야 할까?** 8 | - 외부와 어떤 통신이 일어나고 있는지 정확히 알 수 있다 (유지보수 엔지니어에게 소중한 정보) 9 | - 인커밍 포트를 생략하고 서비스를 직접 호출하는 지름길은? (11장에서 이야기한다) 10 | 11 | **반드시 포트가 필요한 Case** 12 | 웹 소켓을 통한 실시간 데이터 통신 13 | - 애플리케이션 서비스가 웹 어댑터에 능등적으로 알림을 주기위해 서비스는 어댑터로 데이터를 보내야한다 14 | - 포트가 없다면 서비스는 어댑터를 알게되고 의존성이 발생한다 15 | - 포트는 어댑터에 대한 의존성을 해결한다 16 | 17 | PS. 웹 어댑터를 인커밍 어댑터로 정의하였지만, 인커밍,아웃고잉 어댑터를 언급하고 있음 18 | 19 | ---- 20 | 21 | ### 웹 어댑터의 책임 22 | - HTTP 요청을 자바 객체로 매핑 23 | - 권한 검사 24 | - 입력 유효성 검증 25 | - 입력을 유스케이스의 입력 모델로 매핑 26 | - 유스케이스 호출 27 | - 유스케이스의 출력을 HTTP로 매핑 28 | - HTTP 응답을 반환 29 | 30 | 웹어댑터의 유효성 검증이란 유스케이스 유효성 검증과 똑같이 구현하는 것이 아니다. 31 | **웹 어댑터의 입력 모델을 유스케이스의 입력 모델로 변환할 수 있다는 것을 검증**해야 한다 32 | 33 | 웹어댑터와 애플리케이션 계층간 경계는 도메인과 애플리케이션 계층부터 개발하기 시작하면 자연스럽게 생긴다. 34 | 이는 특정 어댑터를 생각할 필요없기 때문에 경계를 흐리게 만드는 상황에 빠지지 않을 수 있다 35 | 36 | ---- 37 | 38 | ### 컨트롤러 나누기 39 | **모델을 공유하지 않은 여러 작은 클래스를 만드는 것을 두려워 하지 마라** 40 | 각 컨트롤러가 가능한 좁고, 다른 컨트롤러와 가능한 한 적게 공유하는 웹 어댑터 조각을 구현해야 한다 41 | - 가독성이 좋아진다 42 | - 테스트 코드가 찾기 쉬어진다 43 | - 전용 모델 클래스들은 private 선언할 수 있기 때문에 실수로 다른 곳에서 재사용되지 않는다 (?) 44 | - 동시작업이 쉬어진다 (두 명의 개발자가 서로 다른 파일의 코드를 짜고 있기 떄문에) 45 | - 공수가 더 많이 들겠지만 결국에는 유지보수성이 좋아진다 46 | -------------------------------------------------------------------------------- /chapter6/[Eunmi]영속성-어댑터-구현하기.md: -------------------------------------------------------------------------------- 1 | 영속성 계층에 대한 의존성을 역전시키기 위해 영속성 계층을 애플리케이션 계층의 플러그인으로 만드는 방법을 살펴보는 챕터이다. 2 | 3 | # 의존성 역전 4 | 5 | 6 | 7 | # 영속성 어댑터의 책임 8 | 9 | 1. 입력을 받는다. → 입력모델은 포트가 지정한 도메인 엔티티나 특정 데이터베이스 연산 적용 객체 10 | 2. 입력을 데이터베이스 포맷으로 매핑한다. → JPA경우 JPA엔티티 객체로 11 | 3. 입력을 데이터베이스로 보낸다. 12 | 4. 데이터베이스 출력을 애플리케이션 포맷으로 맵핑한다. 13 | 5. 출력을 반환한다. 14 | 15 | 핵심 - 영속성 어댑터의 입력 모델이 영속성 어댑터 내부에 있는 것이 아니라 애플리케이션 코어에 있기 때문에 영속성 어댑터 내부를 변경하는 것이 코어에 영향을 미치지 않는다 16 | 17 | # 포트 인터페이스 나누기 18 | 19 | 20 | 21 | 🔝 특정 엔티티가 필요로 하는 모든 데이터베이스 연산을 하나의 리포지토리 인터페이스에 넣어 두는게 일반적인 이 방법은 하나의 넓은 포트 인터페이스에 의존성을 갖게한다. 22 | 23 | 해결 방법은 인터페이스 분리 원칙 (ISP) 24 | 25 | 26 | 27 | 🔝 ISP를 적용하면 불필요한 의존성을 젝하고 기존 의존성을 눈에 더 잘띄게 만들 수 있다. 28 | 29 | # 영속성 어댑터 나누기 30 | 31 | 32 | 33 | 🔝  영속성 연산이 필요한 도메인 클래스 하나당 하나의 영속성 어댑터를 구현하는 방식 34 | 35 | 영속성 어댑터들은 각 영속성 기능을 이용하는 도메인 경계를 따라 자동으로 나누어 진다. 36 | 37 | 도메인 코드는 영속성 포트에 의해 정의된 명세를 어떤 클래스가 충족시키는지에 관심이 없다. 38 | 39 | 40 | 41 | 이 접근 방식은 또한 여러개의 바운디드 컨텍스트의 영속성 요구사항을 분리하기 위한 좋은 토대 42 | 43 | # 스프링 데이터 JPA 예제 44 | 45 | 46 | 47 | 여기서 Account와 Activity 도메인 모델, AccountJpaEntity와 ActivityJpaEntity 데이터 베이스 모델 간에 양방향 매핑이 존재 48 | 49 | 그냥 Account와 Activity 클래스를 그대로 데이터베이스에 엔티티로 저장하게 된다면 영속성 측면과 타협없이 풍부한 도메인 모델을 생성할 수 없게 되기 때문 50 | 51 | # 데이터베이스 트랜잭션은 어떻게 해야 할까? 52 | 53 | 트랜잭션 경계는 어디에 위치? 54 | 55 | 트랜잭션은 하나의 특정한 유스케이스에 대해서 일어나는 모든 쓰기 작업에 걸쳐있어야 한다. 56 | 57 | 자바와 스프링에서는 @Transactional 애너테이션을 애플리케이션 서비스 클래스에 부여서 스프링이 모든 public 메서드를 트랜잭션을 감싸게 하는 것이다. 58 | 59 | ```java 60 | package buckpal.application.service; 61 | 62 | @Transactional 63 | public class SendMoneSrvice implements SendMoneyUseCase { 64 | ... 65 | } 66 | ``` 67 | 68 | 만약 서비스가 @transactional 에너테이션으로 오염되지 않고 깔끔하게 유지되길 원한 다면 AspectJ 같은 도구를 이용해 AOP으로 트랜잭션 경계를 코드에 위빙할 수 있다. 69 | 70 | public 이외에 메서드에 트랜잭션을 AspectJ를 사용해서 처리할 수 있다. 71 | 72 | ```java 73 | aspect TransactionManagementAspect { 74 | private TransactionManager transactionManager; 75 | 76 | pointcut transactionalOp() : ... 77 | 78 | Object around() : transactionalOp() { 79 | TransactionAttribute ta = ... 80 | TransactionStatus ts = transactionManager.getTransaction(ta); 81 | try { 82 | Object ret = proceed(); 83 | transactionManager.commit(ts); 84 | return ret; 85 | } catch (Throwable ex) { 86 | if(ta.rollbackOn(ex)) { 87 | transactionManager.rollback(ts); 88 | } else { 89 | transactionManager.commit(ts); 90 | } 91 | throw ex; 92 | } 93 | } 94 | 95 | ... 96 | } 97 | 98 | https://livebook.manning.com/book/aspectj-in-action-second-edition/chapter-14/22 99 | ``` 100 | 101 | # 유지보수 가능한 소프트웨어를 만드는 데 어떻게 도움이 될까? 102 | 103 | - 풍부한 도메인 모델 104 | - 포트마다 다른 방식으로 구현할 수 있는 유연함 105 | - 포트의 명세만 지키면 영속성 계층 전체를 교체할 수 있음 -------------------------------------------------------------------------------- /chapter6/mskim.md: -------------------------------------------------------------------------------- 1 | # 06 영속성 어댑터 구현하기 2 | 3 | 데이터베이스 주도 설계를 피하기 위해 영속성 계층을 앱 계층의 플러그인으로 만들어보자. 4 | 5 | ## 의존성 역전 6 | 7 | ![image](https://user-images.githubusercontent.com/6725753/157010412-76f77116-e272-4ecc-a26f-04f4ea052cdd.png) 8 | 9 | - 앱은 영속성 기능 사용하기 위해 포트 호출. 이 포트는 영속성 어댑터에 의해 구현.(DIP) 10 | - 육각형 아키텍처에서 영속성 어댑터는 앱에서 호출하기 때문에 아웃고잉 어댑터 11 | - 포트는 앱과 영속성 사이의 간접적 계층 12 | - 영속성 문제에 신경쓰지 않고 도메인 코드 개발 13 | - 영속성 계층에 코드 의존성을 없앤다 14 | - 영속성 코드를 변경하더라도 코어 코드에 영향이 없다 15 | - DIP로 인해 정적인 상황에서는 의존성이 역전되었지만 동적인 타임에는 여전히 앱이 영속성 코드에 의존하고 있다. 하지만 인터페이스 계약을 만족하는 한 영속성 코드 수정은 문제가 없다. 16 | 17 | ## 영속성 어댑터의 책임 18 | 19 | 영속성 어댑터가 일반적으로 하는 일. 20 | 21 | 1. 입력을 받는다 22 | - 입력 모델은 인터페이스의 도메인 엔티티나 특정 데이터베이스 연산 전용 객체 23 | 2. 입력을 데이터베이스 포맷으로 매핑한다 24 | - JPA인 경우 JPA 엔티티 객체로 매핑 25 | - 맥락에 따라 매핑이 필요하지 않는 경우도 있음 -> 8장 26 | - JPA가 아닌 어떤 기술도 상관없음 27 | - > 핵심은 영속성 어댑터의 입력 모델이 어댑터가 아닌 코어에 존재. 이로인해 어댑터 변경이 코어에 영향을 주지 않는다 28 | 3. 입력을 데이터베이스로 보낸다 29 | 4. 데이터베이스 출력을 애플리케이션 포맷으로 매핑한다 30 | - `출력 모델도 코어에 위치` 31 | 5. 출력을 반환한다 32 | 33 | ## 포트 인터페이스 나누기 34 | 35 | 그렇다면 영속성을 담당하는 포트 인터페이스를 어떻게 나눠야 할까? 36 | 37 | ![image](https://user-images.githubusercontent.com/6725753/157163384-5df55945-db1e-4859-85f1-ffeb17735123.png) 38 | 39 | - 위 그림처럼 하나에 리포지터리 인터페이스에 담아 놓는게 일반적이다. 40 | - 하지만 이럴경우 '넓은' 포트 인터페이스 문제점을 갖게 된다(SRP 위배) 41 | - 이로 인해 불필요한 의존이 생기고 42 | - 테스트를 어렵게 한다 43 | - 위 그림에서 RegisterAccountService를 테스트 한다면 AccountRepository 모킹을 해야할 때 어떤 메서드를 모킹해야 하는지 일일이 찾아봐야한다. 44 | - 또한, 다음에 이 테스트에 작업하는 사람은 인터페이스 전체가 모킹 되었다고 오해를 할 수도 있다. 45 | - ISP(Interface Segregation Principle 인터페이스 분리 원칙)을 적용해야한다. 46 | 47 | ![image](https://user-images.githubusercontent.com/6725753/157164231-df353c57-52c7-435b-84e0-b6364d6a26c0.png) 48 | 49 | - 포트의 이름이 구체적이고 역할을 잘 표현한다 50 | - 테스트 시 모킹의 대상이 좁아지고 명확해진다 51 | - 좁은 포트는 코딩을 플러그 앤 플레이가 가능하게 한다 52 | 53 | ## 영속 어댑터 나누기 54 | 55 | 지금까지 어댑터 하나로 해결했으나 이제 나눠보자. 56 | 57 | ![image](https://user-images.githubusercontent.com/6725753/157164823-8331bbf5-4108-48ff-85f7-39063a5575d9.png) 58 | 59 | - 영속성 연산이 필요한 도메인 클래스(애그리거트) 당 하나의 어댑터 구현 60 | - 도메인 경계를 따라 자연스럽게 나뉘어진다 61 | - 물론 어댑터를 필요에 따라 훨씬 더 쪼갤 수도 있다. 예) JPA 외에 SQL을 바로 쓰기 위한 포트를 구현하는 경우 등 62 | - 이는 여러 개의 바운디드 컨텍스트의 영속성 요구사항을 분리하기 위한 좋은 토대 63 | ![image](https://user-images.githubusercontent.com/6725753/157165183-87647a3c-b631-4a00-8a11-6ac481b66c64.png) 64 | - 각 바운디드 컨텍스트는 영속성 어댑터를 따로 가지고 있고 65 | - 바운디드 컨텍스트는 서로 분리되어 의존성이 없다 66 | - 만약 서로 접근할 필요가 있다면 영속성 어댑터에 바로 접근하지 않고 인커밍 포트를 통해서 접근하게 된다 67 | 68 | ## 스프링 데이터 JPA 예제 69 | 70 | ![image](https://user-images.githubusercontent.com/6725753/157206294-836266cc-902a-4910-bf01-9bff27bb0f85.png) 71 | 72 | - 불변성 73 | - 유효한 상태의 Account 엔티티만 생성하도록 팩토리 메서드 제공 74 | 75 | ![image](https://user-images.githubusercontent.com/6725753/157207939-c3f03a67-1320-4c57-af0d-6b60ef215399.png) 76 | - 영속성 어댑터를 위한 Account 엔티티 따로 정의 77 | 78 | ![image](https://user-images.githubusercontent.com/6725753/157208048-3ea1992e-d824-4f02-88ad-15c2a08cb7c4.png) 79 | - 영속성 어댑터에서 사용할 Activity 엔티티 정의 80 | - JPA의 @ManyToOne 이나 @OneToMany 는 부수효과에 비해 아직 크게 필요하지 않다 판단되어 사용안함 81 | - JPA는 좋은 도구이나 그에 비해 많은 문제가 있을 수 있다 82 | 83 | ![image](https://user-images.githubusercontent.com/6725753/157208177-87d6cd48-915f-43a9-91df-97ae203ef8bf.png) 84 | - ActivityRepository는 스프링에 의해서 자동으로 구현체가 생성된다 85 | 86 | 87 | ![image](https://user-images.githubusercontent.com/6725753/157208365-5ed04dfc-1e55-4770-9365-214f0e5f95bc.png) 88 | ![image](https://user-images.githubusercontent.com/6725753/157209754-e4720cce-3f0d-4bcd-b256-f0ea82731623.png) 89 | - 실제 영속성 어댑터 90 | - LoadAccountPort / UpdateAccountStatePort 두 개의 포트를 구현한다 91 | - Account를 디비에서 불러오고 92 | - 베이스 날짜 이후 Activity르 가져오고 93 | - 베이스 잔고를 구하기 위해 베이스 날짜 전까지의 입금 / 출금 활동을 디비에서 가져와서 94 | - Account Entity로 변경시 베이스 잔고를 계산한다 95 | 96 | ![image](https://user-images.githubusercontent.com/6725753/157210568-cc849ca6-3b52-4ac1-9b09-48e5e48f0894.png) 97 | 98 | - 도메인 엔티티와 영속성 엔티티 간에 쌍으로 존재한다. 굳이 이래야 하나? 99 | - 쌍으로 엔티티를 만들지 않고 '매핑하지 않기' 전략이 유효할 수도 있다 100 | - 하지만 이런 경우 JPA를 위해서 도메인 모델을 타협할 수 밖에 없다(기본 생성자라든가...) 101 | - 즉, 영속성 측면에 타협하지 않을 때 좀 더 풍부한 도메인 모델응 생성할 수 있다. 102 | 103 | ## 데이터베이스 트랜잭션은 어떻게 해야 할까? 104 | 105 | 트랜잭션의 시작은 어디에 위치해야 할까? 106 | 107 | - 트랜잭션은 특정 유스케이스에 대해서 모든 쓰기 작업에 걸쳐 있어야한다. 하나라도 실패하면 다 같이 롤백되어야 하기 때문 108 | - 어댑터 같은 경우 어떤 유스케이스에 포함되는지 알지 못하기때문에 탈락 109 | - > 트랜잭션의 시작은 서비스 110 | 111 | ![image](https://user-images.githubusercontent.com/6725753/157166051-835142df-b4c9-46e3-b6b7-e9dcb229f0e3.png) 112 | - 스프링에서 가장 쉬운 방법은 위와 같이 서비스 클래스에 붙여서 모든 public 메서드를 트랜잭션으로 감싸는 것이다 113 | - @Transaction 으로 오염시키기 싫다면 위빙을 통하여 해결 할 수도 있다 114 | 115 | ## 유지보수 가능한 소프트웨어를 만드는 데 어떻게 도움이 될까? 116 | 117 | - 도메인 코드에 플로그인처럼 동작하는 영속성 어댑터는 도메인이 영속성에 분리되어 풍부한 도메인 모델이 가능하다 118 | - 좁은 포트 인터페이스는 포트마다 다른 방식으로 구현 가능하기 때문에 유연하다 119 | - 포트의 인터페이스만 지킨다면 영속성 기술 선택에 자유로워진다. -------------------------------------------------------------------------------- /chapter6/pics/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/726b041246229b733566547278b45fd044534dfe/chapter6/pics/1.png -------------------------------------------------------------------------------- /chapter6/pics/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/726b041246229b733566547278b45fd044534dfe/chapter6/pics/2.png -------------------------------------------------------------------------------- /chapter6/pics/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/726b041246229b733566547278b45fd044534dfe/chapter6/pics/3.png -------------------------------------------------------------------------------- /chapter6/pics/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/726b041246229b733566547278b45fd044534dfe/chapter6/pics/4.png -------------------------------------------------------------------------------- /chapter6/pics/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/726b041246229b733566547278b45fd044534dfe/chapter6/pics/5.png -------------------------------------------------------------------------------- /chapter6/pics/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/726b041246229b733566547278b45fd044534dfe/chapter6/pics/6.png -------------------------------------------------------------------------------- /chapter7/[Eunmi]테스트-하기.md: -------------------------------------------------------------------------------- 1 | # Chapter 7. 아키텍처 요소 테스트하기 2 | 3 | ## 테스트 피라미드 4 | 5 | 6 | 7 | - 단위 테스트 : 피라미드의 토대, 하나의 클래스를 인스턴스화하고 해당 클래스의 인터페이스를 통해 기능등을 테스트 8 | - 통합 테스트 : 연결된 여러 유닛을 인스턴스화하고 시작점이 되는 클래스의 인터페이스로 데이터를 보낸 후 유닛들의 네트워크가 기대한 대로 잘 동작하는지 검증 9 | - 시스템 테스트 : 애플리케이션을 구성하는 모든 객체 네트워크를 가동시켜 특정 유스케이스가 전 계층에서 잘 동작하는지 검증 10 | 11 | ## 단위 테스트로 도메인 엔티티 테스트하기 12 | 13 | 17 | 18 | - Account의 상태 - 과거의 특정 시점의 계좌 잔고(baselineBalance) + 입출금 내역(activity) 19 | - withdarw() 메서드 작동 20 | 21 | 22 | 23 | 24 | 25 | ## 단위 테스트로 유스케이스 테스트하기 26 | 27 | 31 | 32 | - 출금 계좌의 잔고가 다른 트랜잭션에 의해 변경되지 않도록 lock 33 | - 출금 계좌에서 돈이 출금되고 나면 똑같이 입금 계좌에 락을 걸고 돈을 입금 34 | - 두 계좌에서 모두 락을 해제한다. 35 | 36 | 37 | 38 | 이 테스트는 서비스가 모킹된 의존 대상 (ex AccountLock)의 특정 메서드(ex lockAccount())와 상호작용 했는지 여부를 검증한다. 이건 테스트가 코드의 행동 변경 뿐만 아니라 코드의 구조 변경에도 취약해진다. 39 | 40 | ```java 41 | public interface AccountLock { 42 | void lockAccount(Account.AccountId accountId); 43 | void releaseAccount(Account.AccountId accountId); 44 | } 45 | ``` 46 | 47 | 모든 동작을 검증하는 대신 중요한 핵심만 골라 집중해서 테스트 하는 것이 좋다 48 | 49 | ## 통합 테스트로 웹 어댑터 테스트하기 50 | 51 | 55 | 56 | - JSON 문자열 등의 형태로 HTTP를 통해 입력을 받고 57 | - 입력에 대한 유효성 검증을 하고 58 | - 유스케이스에서 사용할 수 있는 포맷으로 맵핑하고 59 | - 유스케이스에 전달 60 | - 결과를 JSON으로 매핑하고 61 | - HTTP응답을 통해 클라이언트에 반환 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | ## 통합 테스트로 영속성 어댑터 테스트하기 70 | 71 | 75 | 76 | - 어댑터의 로직 77 | - 데이터베이스 맵핑 78 | 79 | 80 | 81 | 82 | 83 | 데이터 베이스를 모킹하지 않는다! 84 | 85 | 왜냐하면 모킹한 데이터베이스로 테스트 할 경우 실제 데이터베이스와 연동했을 때 SQL 구문의 오류나 데이터베이스 테이브과 자바 객체 간의 매핑 에러 등으로 문제가 생길 확률이 높고 86 | 87 | 인메모리 데이터베이스를 사용하는 경우 테이터베이스마다 고유한 SQL문법이 있어서 생기는 문제 등 프로덕션 환경에서 문제가 생길 가능성이 높다. 88 | 89 | ## 시스템 테스트로 주요 경로 테스트하기 90 | 91 | 95 | 96 | - 애플리케이션에 HTTP 요청 보내고 97 | - 계좌의 잔고를 확인하는 것 98 | 99 | 100 | 101 | 테스트 가독성을 높이기 위해 지저분한 로직들은 헬퍼 메서드 안으로 감췄다👇 102 | 103 | 이 헬퍼 메서드들은 여러가지 상태를 검증할때 사용할 수 있는 도메인 특화 언어(DSL)를 형성한다. 104 | 105 | 106 | 107 | ## 얼마만큼의 테스트가 충분할까? 108 | 109 | 얼마나 마음 편하게 소프트웨어를 배포할 수 있는냐를 테스트의 성공 기준으로 삼으면 된다 110 | 111 | 처음 몇번의 배포에는 믿음의 도약이 필요하지만 프로덕션의 **버그를 수정하고 이로부터 배우는 것**을 우선 순위로 삼으면 제대로 가고 있는 것이다. 112 | 113 | 다음은 육각현 아키텍처에서 사용하는 테스트를 정의하는 전략이다. 114 | 115 | - 도메인 엔티티를 구현할 때는 단위 테스트로 커버하자 116 | - 유스케이스를 구현할 때는 단위 테스트로 커버하자 117 | - 어댑터를 구현할 때는 통합 테스트로 커버하자 118 | - 사용자가 취할 수 있는 중요 애플리케이션 경로는 시스템 테스트로 커버하자 119 | 120 | ## 유지보수 가능한 소프트웨어를 만드는 데 어떻게 도움이 될까? 121 | 122 | **육각형 아키텍처**는 도메인 로직과 바깥으로 향한 어댑터를 깔끔하게 분리하다. 덕분에 핵심 도메인 로직은 단위 테스트로, 어댑터는 통합 테스트로 처리하는 명확한 테스트 전략을 정의할 수 있다. 123 | 124 | 테스트는 아키텍처의 문제에 대해 경고하고 유지보수 가능한 코드를 만들기 위한 올바른 길로 인도하는 카나리아의 역할도 한다고 할 수 있다. -------------------------------------------------------------------------------- /chapter7/pics/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/726b041246229b733566547278b45fd044534dfe/chapter7/pics/1.jpg -------------------------------------------------------------------------------- /chapter7/pics/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/726b041246229b733566547278b45fd044534dfe/chapter7/pics/2.jpg -------------------------------------------------------------------------------- /chapter7/pics/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/726b041246229b733566547278b45fd044534dfe/chapter7/pics/3.jpg -------------------------------------------------------------------------------- /chapter7/pics/4-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/726b041246229b733566547278b45fd044534dfe/chapter7/pics/4-2.png -------------------------------------------------------------------------------- /chapter7/pics/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/726b041246229b733566547278b45fd044534dfe/chapter7/pics/4.jpg -------------------------------------------------------------------------------- /chapter7/pics/5-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/726b041246229b733566547278b45fd044534dfe/chapter7/pics/5-2.png -------------------------------------------------------------------------------- /chapter7/pics/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/726b041246229b733566547278b45fd044534dfe/chapter7/pics/5.jpg -------------------------------------------------------------------------------- /chapter7/pics/6-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/726b041246229b733566547278b45fd044534dfe/chapter7/pics/6-2.png -------------------------------------------------------------------------------- /chapter7/pics/6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/726b041246229b733566547278b45fd044534dfe/chapter7/pics/6.jpg -------------------------------------------------------------------------------- /chapter7/pkch93.md: -------------------------------------------------------------------------------- 1 | # 7장. 아키텍처요소 테스트하기 2 | 3 | ## The Test Pyramid 4 | 5 | ![](https://user-images.githubusercontent.com/30178507/158181032-672eabb8-a3fb-4874-96d2-f9dc8751e7ec.png) 6 | 7 | > 테스트 피라미드는 2009년 Mike Cohn의 책 `Succeeding with Agile`에서 언급됐다. 8 | 9 | 테스트가 여러 단위 및 교차 단위 경계 아키텍처 경계 또는 시스템 경계를 결합하면 빌드 비용이 더 많이 들고 실행 속도가 느려지며 취약해지는 경향이 있다. 피라미드는 이러한 테스트가 더 비싸질수록 테스트가 더 많은 범위를 커버하는 것을 목표로 해야함을 알려준다. 그렇지 않으면 새로운 기능 대신 테스트를 구축하는 데 너무 많은 시간을 할애하는 경향이 있다. 10 | 11 | 기본적으로 테스트 작성 비용이 저렴하고 유지관리가 쉬운 단위 테스트가 프로젝트에서 가장 많은 테스트를 구성한다. 따라서 테스트 피라미드의 가장 기초가 된다. 일반적으로 단일 클래스를 인스턴스화하고 인터페이스를 통해 기능을 테스트한다. 12 | 13 | 통합 테스트는 테스트 피라미드의 다음 계층을 형성한다. 여러 컴포넌트 테스트를 조합하여 로직을 구성하는 클래스를 인스턴스화하고 엔트리 클래스의 인터페이스를 통해 컴포넌트의 로직을 원하는대로 조합하는지 확인한다. 14 | 15 | 시스템 테스트는 애플리케이션을 구성하는 전체 구성을 가동하여 애플리케이션의 모든 계층에서 Use Case가 제대로 동작하는지 확인한다. 16 | 17 | ## 단위 테스트 18 | 19 | 핵사고날 아키텍처 구성요소 중 단위테스트가 적절한 요소들은 도메인 객체와 유스케이스이다. 20 | 21 | ### 도메인 객체 테스트 22 | 23 | 도메인 객체는 가장 만들기 쉽고 이해하기도 쉬우며 빠르게 실행되는 테스트를 만들 수 있다. 다른 클래스에 거의 의존하지 않으므로 단위 테스트 이외에 다른 종류의 테스트는 불필요하다. 24 | 25 | ### 유스케이스 테스트 26 | 27 | 도메인 객체 다음의 아키텍처 요소는 유스케이스이다. 유스케이스도 단위 테스트 대상이다. 유스케이스에서는 보통 Out-Port를 사용하여 타 시스템 또는 DB에서 데이터를 가져오고 도메인 객체를 사용하여 로직을 구성한다. 따라서 유스케이스를 단위테스트로 작성하려면 Mock이 필수적이다. Port를 Mocking 하여 특정 메서드들이 잘 호출이 되었는지와 이들 메서드들을 조합하는 유스케이스의 동작을 테스트할 수 있다. 28 | 29 | 다만, Mock을 사용하게 되면 테스트가 코드의 행동 변경과 리펙터링에 취약해진다. 따라서 중요한 상호작용이 있는 경우에만 테스트를 하거나 아예 유스케이스도 통합테스트로 구성을 하는 것이 맞지 않을까 생각이 든다. 30 | 31 | ## 통합 테스트 32 | 33 | 통합테스트가 필요한 요소는 포트의 구현체들인 웹 어뎁터 또는 영속성 어뎁터가 된다. 34 | 35 | ### 웹 어댑터 테스트 36 | 37 | 웹 어댑터 테스트는 HTTP를 통해 입력을 받고 입력 유효성 검증, 유스케이스가 사용할 수 있는 포멧으로 변경, 다시 HTTP로 응답이라는 일련의 과정이 테스트 되어야한다. 38 | 39 | 이를 위해서 Spring Boot에서는 `@WebMvcTest`를 사용할 수 있다. 실제 유스케이스는 Mocking 하므로 내부로직을 모두 태울수는 없지만 일련의 HTTP 통신이 잘 이뤄지고 유스케이스가 정의한 입력 포멧을 제대로 전달하는 지를 테스트할 수 있다. 40 | 41 | > Rest Docs 라이브러리를 활용한다면 웹 어댑터 테스트를 통해서 먼저 스팩을 정의하고 API 문서를 작성하는데에도 활용할 수 있어보인다. 42 | 43 | ### 영속성 어댑터 테스트 44 | 45 | 영속성 어댑터도 데이터 영속화를 위한 컨텍스트를 띄우는 과정이 필요하므로 통합테스트가 필요하다. 46 | 47 | 만약 Spring Boot와 Spring Data의 일부 프로젝트에서는 `@DataJpaTest`, `@DataRedisTest`, `@DataMongoTest`등을 사용하여 도메인 객체가 제대로 데이터베이스에 영속화되는지, 조회가 되어 도메인 객체로 잘 변환이 되는지 등을 테스트할 수 있다. 48 | 49 | > 만약 Spring Data에서 Test 자동 설정을 지원하지 않는 프로덕트가 있다면 `ex. AWS DynamoDB 등` `@SpringBootTest`를 통해서 테스트할 수 밖에 없을것 같다. 50 | 51 | ## 시스템 테스트 52 | 53 | 시스템 테스트는 애플리케이션을 구성하는 전체 컨텍스트를 띄워 API 요청을 통해 잘 동작하는지를 검증한다. 따라서 전체 컨텍스트를 띄워야하므로 `@SpringBootTest`를 사용하여 테스트를 진행한다. 54 | 55 | 출력 포트도 컨텍스트를 띄워야하기 때문에 필요한 데이터베이스는 in-memory 데이터베이스를 활용하거나 실제 환경과 비슷하게 가져가고 싶다면 Docker 컨테이너를 띄워 테스트용으로 사용할 수 있다. 56 | 57 | 출력 포트가 타 시스템을 요청하는 경우에는 [MockServer](https://mock-server.com/)를 활용하여 요청 어댑터가 HTTP 통신을 제대로 수행하는지를 검증할수도 있을 것이다. 58 | -------------------------------------------------------------------------------- /chapter8/08_경계_간_매핑하기.md: -------------------------------------------------------------------------------- 1 | ## 08. 경계 간 매핑하기 2 | 3 | **계층간 매핑에 대한 논쟁** 4 | 매핑에 찬성하는 개발자 5 | - 두 계층 간에 매핑하지 않으면 양 계층에서 같은 모델을 사용해야하는데 두 계층이 강하게 결합된다 6 | 7 | 매핑에 반대하는 개발자 8 | - 계층간 매핑을 하게되면 보일러플레이트 코드가 너무 많아진다. 9 | 10 | ---- 11 | 12 | **매핑하지 않기 전략** 13 | ![image](https://user-images.githubusercontent.com/16996054/158038978-6df3433b-7000-4076-a7fe-29b8c1641308.png) 14 | 15 | 장점 16 | - 모든 계층이 같은 모델 (Account) 를 사용하니 계층 간 매핑을 전혀 할 필요없다 17 | 18 | 문제점 19 | - 웹계층과 영속성계층의 요구사항에 도메인이 변경되어야 한다 (SRP 위반) 20 | - JSON 으로 직렬화하기 위한 Annotation 21 | - ORM 을 위한 Annotation 22 | - 애플리케이션이 아닌 다른 계층에서 필요한 Custom field 23 | - (개인의견) OSIV 문제 24 | 25 | 언제 유용할까? 26 | - 모든 계층이 정확히 같은 구조, 같은 정보를 필요로 할때 27 | 28 | ---- 29 | **양방향(Two-Way) 매핑 전략** 30 | ![image](https://user-images.githubusercontent.com/16996054/158038986-7c3471d9-e295-46ef-97a9-497fcc7f37fd.png) 31 | 32 | 장점 33 | - 웹이나 영속성 관심사로 오염되지 않은 깨끗한 도메인 모델 (SRP 만족) 34 | - 간단하면서도 매핑 책임이 명확 35 | 36 | 문제점 37 | - 보일러플레이트 코드 38 | - 도메인 모델이 계층 경계를 넘어서 통신하는데 사용된다 (포트가 도메인 객체를 입력파라미터와 반환값으로 사용한다) 39 | 40 | ---- 41 | **완전 매핑 전략** 42 | ![image](https://user-images.githubusercontent.com/16996054/158038992-3a9acefa-276b-42fa-8080-7035ba3c44a8.png) 43 | 각 작업에 특화된 모델을 사용한다 (command 또는 request 와 비슷한 단어로 표현한다) 44 | 45 | 장점 46 | - command 객체는 애플리케이션의 인터페이스를 해석할 여지없이 명확하게 만들어준다? 47 | - 각 유스케이스는 전용필드와 유효성 검증로직을 가진 전용 커맨드를 가진다 48 | - (개인의견 - 웹모델이 검증해야 하는 부분을 command 객체와 나눈것이 장점으로 생각됩니다) 49 | 50 | 문제점 51 | - 더 많은 코드 52 | 53 | 언제 유용할까? 54 | - 인커밍 어댑터와 어플리케이션 계층사이에서 상태 변경 유스케이스의 경계를 명확하게 할때 유용하다 55 | - 애플리케이션과 영속성 계층 사이에서는 매핑 오버헤드 때문에 사용하지 않는것이 좋다 56 | 57 | ---- 58 | **단방향 매핑전략** 59 | ![image](https://user-images.githubusercontent.com/16996054/158038999-46a4d1a8-bfef-4c29-9060-82544794bc1b.png) 60 | 모든 계층의 모델들이 같은 인터페이스를 구현한다 61 | 62 | 장점 63 | - 매픽 책임이 명확하다 (한 계층이 다른 계층으로부터 객체를 받으면 해당 계층에서 매핑한다) 64 | 65 | 문제점 66 | - 매핑이 계층을 넘나들며 퍼져있기 때문에 개념적으로 어렵다 67 | 68 | 언제 유용할까? 69 | - 계층 간 모델이 비슷할 때 효과적이다 70 | 71 | ---- 72 | 73 | **언제 어떤 매핑 전략을 사용할 것인가?** 74 | - 각 매핑 전략이 저마다 장단점을 가지고 있기 때문에 한 전략을 전역 규칙으로 정의하려는 충동을 이겨내야한다 75 | - 언제 어떤 전략을 사용할지 팀 가이드라인을 정하라 76 | 77 | (개인의견 - 생산성 측면에서 하나의 매핑전략을 고수하는 것이 나쁘지 않은 생각이라고 생각합니다. 각 상황에 따른 매핑전략 적용에 대한 가이드라인이 명확하더라도 많은 시행착오가 있지 않을까 생각이 드네요) 78 | -------------------------------------------------------------------------------- /chapter8/images/8.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/726b041246229b733566547278b45fd044534dfe/chapter8/images/8.1.png -------------------------------------------------------------------------------- /chapter8/images/8.2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/726b041246229b733566547278b45fd044534dfe/chapter8/images/8.2.png -------------------------------------------------------------------------------- /chapter8/images/8.3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/726b041246229b733566547278b45fd044534dfe/chapter8/images/8.3.png -------------------------------------------------------------------------------- /chapter8/images/8.4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Meet-Coder-Study/Get-Your-Hands-Dirty-on-Clean-Architecture/726b041246229b733566547278b45fd044534dfe/chapter8/images/8.4.png -------------------------------------------------------------------------------- /chapter8/kooku0.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 08 경계간 매핑하기 3 | --- 4 | 5 | 각 계층의 모델을 매핑하는 것에 다루어보자. 매퍼 구현을 피하기 위해 두 계층에서 같은 모델을 사용하는 것에 대해 논의해본 적이 있을 것이다. 6 | 7 | 아마 논쟁은 이런 식으로 진행됐을 것이다. 8 | 9 | ##### 매핑에 찬성하는 개발자: 10 | 11 | 두 계층 간에 매핑을 하지 않으면 양 계층에서 같은 모델을 사용해야 하는데 이렇게 하면 두 계층이 강하게 결합됩니다. 12 | 13 | ##### 매핑에 반대하는 개발자: 14 | 15 | 하지만 두 계층 간에 매핑을 하게 되면 보일러플레이트 코드를 너무 많이 만들게 돼요. 많은 유스케이스들이 오직 CRUD만 수행하고 계층에 걸쳐 같은 모델을 사용하기 때문에 계층 사이의 매핑은 과합니다. 16 | 17 | 몇 가지 매핑 전략을 장단점과 함께 알아보자. 18 | 19 | ## '매핑하지 않기' 전략 20 | 21 | ![img](./images/8.1.png) 22 | 23 | > 포트 인터페이스가 도메인 모델을 입출력 모델로 사용하면 두 계층 간의 매핑을 할 필요가 없다. 24 | 25 | 웹 계층에서는 웹 컨트롤러가 SendMoneyUseCase 인터페이스를 호출해서 유스케이스를 실행한다. 이 인터페이스는 Account 객체를 인자로 가진다. 즉 웹 계층과 애플리케이션 계층 모두 Account 클래스에 접근해야 한다는 것(두 계층이 같은 모델을 사용)을 의미한다. 26 | 27 | 도메인과 애플리케이션 계층은 웹이나 영속성 관련된 특수한 요구사항에 관심이 없음에도 불구하고 Account 도메인 클래스는 모든 요구사항을 다뤄야 한다. 이럴 때 웹 계층과 영속성 계층은 모델에 대해 특별한 요구사항이 있을 수 있다. (어노테이션 같은) 28 | 29 | Account 클래스는 웹, 애플리케이션, 영속성 계층과 관련된 이유로 인해 변경돼야 하기 때문에 **단일 책임 원칙**을 위반한다. 30 | 31 | 기술적인 요구사항이 아니더라도, 각 계층이 Account 클래스에 특정 커스텀 필드를 두도록 요구할 수 있다. 그 결과, 오로지 한 계층에서만 필요한 필드들을 포함하는 파편화된 도메인 모델로 이어질 수 있다. 32 | 33 | 그렇다면 '매핑하지 않기'를 절대로 쓰며녀 안될까? 지저분하게 느껴질 수는 있지만 '매핑하지 않기' 전략이 딱 들어맞을 때가 있다. 34 | 35 | 간단한 CRUD 유스케이스를 생각해보자. 같은 필드를 가진 웹 모델을 도메인 모델로, 혹은 도메인 모델을 영속성 모델로 매핑할 필요가 있을까? 그럴 필요는 없다. 36 | 37 | 모든 계층이 정확히 같은 구조의, 정확히 같은 정보를 필요로 한다면 '매핑하지 않기' 전략은 완벽한 선택지다. 38 | 39 | ## '양방향' 매핑 전략 40 | 41 | 각 계층이 전용 모델을 가진 매핑 전략을 '양방향(Two-Way)' 매핑 전략이라고 한다. 42 | 43 | ![img](./images/8.2.png) 44 | 45 | > 각 어댑터가 전용 모델을 가지고 있어서 해당 모델을 도메인 모델로, 도메인 모델을 해당 모델로 매핑할 책임을 가지고 있다. 46 | 47 | 각 계층은 도메인 모델과는 완전히 다른 구조의 전용 모델을 가지고 있다. 48 | 49 | 웹 계층에서는 웹 모델을 인커밍 포트에서 필요한 도메인 모델로 매핑하고, 인커밍 포트에 의해 반환된 도메인 객체를 다시 웹 모델로 매핑한다. 50 | 51 | 영속성 계층은 아웃고잉 포트가 사용하는 도메인 모델과 영속성 모델 간의 매핑과 유사한 매핑을 담당한다. 52 | 53 | 두 계층 모두 양방향으로 매핑하기 때문에 '양방향' 매핑이라고 부른다. 54 | 55 | 각 계층이 전용 모델을 가지고 있는 덕분에 각 계층이 전용 모델을 변경하도라도 다른 계층에는 영향이 없다. 그래서 웹 모델은 데이터에 최적으로 표현할 수 있는 구조를 가질 수 있고, 도메인 모델은 유스케이스를 제일 잘 구현할 수 있는 구조, 영속성 모델은 데이터베이스에 객체를 저장하기 위해 ORM에서 필요로 하는 구조를 가질 수 있다. 56 | 57 | 이 매핑 전략은 웹이나 영속성 관심사로 오염되지 않은 깨끗한 도메인 모델로 이어진다. 즉 **단일 책임 원칙**을 만족하는 것이다. 58 | 59 | '양방향' 매핑의 또 다른 장점은 개념적으로는 '매핑하지 않기' 전략 다음으로 간단한 전략이라는 것이다. 매핑 책임이 명확히다. 즉, 바깥쪽 계층/어댑터는 안쪽 계층의 모델로 매핑하고, 다시 반대 방향으로 매핑한다. 안쪽 계층은 해당 계층의 모델만 알면 되고 매핑대신 도메인 로직에 집중할 수 있다. 60 | 61 | 단점도 있다. 62 | 63 | 먼저, 너무 많은 보일러플레이트 코드가 생긴다. 64 | 65 | 또 다른 단점은 도메인 모델이 계층 경계를 넘어서 통신하는 데 사용되고 있다는 것이다. 인커밍 포트와 아웃고잉 포트는 도메인 객체를 입력 파라미터와 반환값으로 사용한다. 도메인 모델은 도메인 모델의 필요에 의해서만 변경되는 것이 이상적이지만 바깥쪽 계층의 요구에 따른 변경에 취약해지는 것이다. 66 | 67 | ## '완전' 매핑 전략' 68 | 69 | ![img](./images/8.3.png) 70 | 71 | > 각 연산이 전용 모델을 필요로 하기 때문에 웹 어댑터와 애플리케이션 계층 각각이 자신의 전용 모델을 각 연산을 실행하는 데 필요한 모델로 매핑한다. 72 | 73 | 이 매핑 전략에서는 **각 연산마다** 별도의 입출력 모델을 사용한다. 계층 경계를 넘어 통신할 때 도메인 모델을 사용하는 대신 그림 8.3의 SendMoneyUseCase 포트의 입력 모델로 동작하는 SendMoneyCommand처럼 각 작업에 특화된 모델을 사용한다. 74 | 75 | 웹 계층은 입력을 애플리케이션 계층의 커맨드 객체로 매핑할 책임을 가지고 있다. 이러한 커맨드 객체는 애플리케이션 계층의 인터페이스를 해석할 여지 없이 명확하게 만들어 준다. 각 유스케이스는 전용 필드와 유효성 검증 로직을 가진 전용 커맨드를 가진다. 76 | 77 | 그리고 나서 애플리케이션 계층은 커맨드 객체를 유스케이스에 따라 도메인 모델을 변경하기 위해 필요한 무언가로 매핑할 책임을 가진다. 78 | 79 | 당연하겠지만 한 계층은 한 계층을 다른 여러 개의 커맨드로 매핑하는 데는 하나의 웹 모델과 도메인 모델 간의 매핑보다 더 많은 코드가 필요하다. 하지만 이렇게 매핑하면 여러 유스케이스의 요구사항을 함께 다뤄야 하는 매핑에 비해 구현하고 유지보수하기가 훨씬 쉽다. 80 | 81 | 또한 어떤 경우에는 연산의 입력 모델에 대해서만 이 매핑을 사용하고, 도메인 객체를 그대로 출력 모델을 사용하는 것도 좋다. 82 | 83 | 이처럼 매핑 전략은 여러 가지를 섞어쓸 수 있고, 섞어 써야만 한다. 어떤 매핑 전략도 모든 계층에 걸쳐 전역 규칙일 필요가 없다. 84 | 85 | ## '단방향' 매핑 전략 86 | 87 | ![img](./images/8.4.png) 88 | 89 | > 동일한 '상태' 인터페이스를 구현하는 도메인 모델과 어댑터 모델을 이용하면 각 계층은 다른 계층으로부터 온 객체를 단방향으로 매핑하기만 하면 된다. 90 | 91 | 이 전략에서는 모든 계층의 모델들이 같은 인터페이스를 구현한다. 이 인터페이스는 관련있는 특성에 대한 getter 메서드를 제공해서 도메인 모델의 상태를 캡슐화한다. 92 | 93 | 도메인 모델 자체는 풍부한 행동을 구현할 수 있고, 애플리케이션 계층 내의 서비스에서 이러한 행동에 접근할 수 있다. 도메인 객체를 바깥 계층으로 전달하고 싶으면 매핑 없이 할 수 있다. 왜냐하면 도메인 객체가 인커밍/아웃고잉 포트가 기대하는 대로 상태 인터페이스를 구현하고 있기 때문이다. 94 | 95 | 그러고 나면 바깥 계층에서는 상태 인터페이스를 이용할지, 전용 모델로 매핑해야 할지 결정할 수 있다. 행동을 변경하는 것이 상태 인터페이스에 의해 노출돼 있지 않기 때문에 실수로 도메인 객체의 상태를 변경하는 일은 발생하지 않는다. 96 | 97 | 바깥 계층에서 애플리케이션 계층으로 전달하는 객체들도 이 상태 인터페이스를 구현하고 있다. 98 | 99 | 이 전략에서 매핑 책임은 명호가하다. 만약 한 계층이 다른 계층으로부터 객체를 받으면 해당 계층에서 이용할 수 있도록 다른 무언가로 매핑하는 것이다. 그러므로 각 계층은 한 방향으로만 매핑한다. 100 | 101 | 하지만 매핑이 계층을 넘나들며 퍼져 있기 때문에 이 전략은 다른 전략에 비해 개념적으로 어렵다. 102 | 103 | 이 전략은 계층 간의 모델이 비슷할 때 가장 효과적이다. 예를 들어, 읽기 전용 연산의 경우 상태 인터페이스가 필요한 모든 정보를 제공하기 때문에 웹 계층에서 전용 모델로 매핑할 필요가 전혀 없다. 104 | 105 | ## 언제 어떤 매핑 전략을 사용할 것인가? 106 | 107 | '그때그때 다르다' 108 | 109 | 각 매핑 전략이 저마다 장단점을 갖고 있기 때문에 여러 패턴을 섞어써야 한다. 패턴을 섞어쓰면 어수선해 보일 수 있지만 특정 작업에 최선의 패턴이 아님에도 그저 깔끔하게 느껴진다는 이유로 선택하면 무책임하다. 110 | 111 | 소프트웨어는 시간이 지나며 변화를 거듭하기 때문에, 어제는 최선이었던 전략이 오늘은 최선이 아닐 수 있다. 고정된 매핑 전략으로 계속 유지하기 보다는 빠르게 코드를 짤 수 있는 간단한 전략으로 시작해서 계층 간 결합을 떼어내는 데 도움이 되는 복잡한 전략으로 갈아타는 것도 괜찮은 방법이다. 112 | 113 | 언제 어떤 전략을 사용할지 결정하려면 팀 내에서 합의할 수 있는 가이드라인을 정해둬야 한다. 이 가이드라인은 어떤 상황에서 어떤 매핑 전략을 가장 먼저 택해야 하는가에 답할 수 있어야 한다. 또한 **왜** 해당 전략을 최우선으로 택해야 하는지도 설명할 수 있어야 한다. 그래야 그러한 근거들이 시간이 흐른 후에도 여전히 유효한지 평가할 수 있기 때문이다. 114 | 115 | 가이드라인을 성공적으로 적용하려면 개발자들의 머릿속에 이 가이드라인이 잘 담겨 있어야 한다. 그러므로 가이드라인은 팀 차원에서 지속적으로 논의하고 수정해야한다. 116 | 117 | ## 유지보수 가능한 소프트웨어를 만드는 데 어떻게 도움이 될까? 118 | 119 | 인커밍 포트와 아웃고잉 포트는 서로 다른 계층이 어떻게 통신해야 하는지를 정의한다. 여기에는 계층 사이에 매핑을 수행할지 여부와 어떤 매핑 전략을 선택할지가 포함된다. 120 | 121 | 각 유스케이스에 대해 좁은 포트를 사용하면 유스케이스마다 다른 매핑 전략을 사용할 수 있고, 다른 유스케이스에 영향을 미치지 않으면서 코드를 개선할 수 있기 때문에 특정 상황, 특정 시점에 최선의 전략을 선택할 수 있다. 122 | -------------------------------------------------------------------------------- /chapter9/jiaekim.md: -------------------------------------------------------------------------------- 1 | # 3장. 코드 구성하기 2 | 3 | # 패키지 구조 4 | 5 | ## 1. 계층으로 구성하기 6 | 7 | ```java 8 | buckpal 9 | |-- domain 10 | | |-- Account 11 | | |-- Activity 12 | | |-- AccountRepositoryy 13 | | |-- AccountService 14 | |-- persistence 15 | | |-- AccountRepositoryImpl 16 | |-- web 17 | |-- AccountController 18 | ``` 19 | 20 | 웹 계층(web), 도메인 계층(domain), 영속성 계층(persistence)로 구분했다. 21 | 22 | ### 1.1 계층으로 구성한 패키지 구조가 최적의 구조가 아닌 이유 23 | 24 | **이유1.** 어플리케이션의 기능 조각(functional slice)이나 **특성(feature)을 구분 짓는 패키지 경계가 없다.** 25 | 26 | - 추가적인 구조가 없다면, 서로 연관되지 않은 기능들끼리 예상하지 못한 부수효과를 일으킬 수 있는 클래스들의 묶음으로 변모할 수 있다. 27 | 28 | **이유2.** 어플리케이션이 **어떤 유스케이스들을 제공하는지 파악할 수 없다.** 29 | 30 | - 서비스 내의 어떤 메서드가 특정 기능에 대한 책임을 수행하는지 찾아야 한다. 31 | 32 | **이유3. 패키지 구조를 통해 아키텍처를 파악할 수 없다.** 33 | 34 | - 어떤 기능이 웹 어댑터에서 호출되는지, 영속성 어댑터가 도메인 계층에 어떤 기능을 제공하는지 한눈에 볼 수 없다. 35 | 36 | ## 2. 기능으로 구성하기 37 | 38 | ```java 39 | buckpal 40 | |-- account 41 | |-- Account 42 | |-- AccountController 43 | |-- AccountRepository 44 | |-- AccountRepositoryImpl 45 | |-- SendMoneyService 46 | ``` 47 | 48 | account 패키지로 묶고 계층 패키지를 없앴다. 49 | 50 | - 장점 51 | - package-private 접근 수준으로 각 기능 사이의 불필요한 의존성을 방지할 수 있다. 52 | - 단점 53 | - 가시성을 떨어뜰인다. 54 | - package-private 접근 수준을 이용해 도메인 코드가 실수로 영속성 코드에 의존하는 것을 막을 수 없다. 55 | 56 | ## 3. 아키텍처적으로 표현력 있는 패키지 구조 57 | 58 | ```java 59 | buckpal 60 | |-- account 61 | |-- adapter 62 | | |-- in 63 | | | |-- web 64 | | | |-- AccountController 65 | | |-- out 66 | | | |-- persistence 67 | | | |-- AccountPersistenceAdapter 68 | | | |-- SpringDataAccountRepository 69 | |-- domain 70 | | |-- Account 71 | | |-- Activity 72 | |-- application 73 | |-- SendMoneyService 74 | |-- port 75 | |-- in 76 | | |-- SendMoneyUseCase 77 | |-- out 78 | | |-- LoadAccountPort 79 | | |-- UpdateAccountStatePort 80 | ``` 81 | 82 | ### 3.1 핵사고날 아키텍처 패키지 구조 83 | 84 | Account와 관련된 유스케이스는 모두 account 패키지 안에 있다. 85 | 86 | - domain 87 | - 도메인 모델 (Account) 88 | - application 89 | - 도메인 모델을 둘러싼 서비스 계층 (SendMoneyService) 90 | - 인커밍 포트 인터페이스 (SendMoneyUseCase) 91 | - 아웃고잉 포트 인터페이스 (LoadAccountPort, UpdateAccountStatePort) 92 | - adapter 93 | - 어플리케이션 계층의 인커밍 포트를 호출하는 인커밍 어댑터 (Controller) 94 | - 어플리케이션 계층의 아웃고잉 포트에 대한 구현을 제공하는 아웃고잉 어댑터 (PersistenceAdapter, Repository) 95 | 96 | ### 3.2 헥사고날 아키텍처 구조의 장점 97 | 98 | 장점1. 이러한 패키지 구조는 모델-코드 갭(아키텍처-코드 갭)을 효과적으로 다룰 수 있다. 99 | 100 | 101 | > 💡 [모델-코드 갭(model-code gap)](https://www.ben-morris.com/most-architecture-diagrams-are-useless/#:~:text=George%20Fairbanks%20identified%20what%20he,always%20be%20mapped%20into%20code.) 102 | > 아키텍처 모델에는 항상 코드에 매핑할 수 없는 추상적인 개념, 기술 선택 및 설계 결정이 혼합되어 있다. 최종 결과는 모델이 정한 구성 요소의 배열과 반드시 일치하지 않는 소스 코드가 될 수 있다. 103 | 104 | 장점2. 패키지간 접근을 제어할 수 있다. 105 | 106 | - package-private인 adapter 클래스 107 | - 모든 클래스는 application 패키지 내의 포트 인터페이스를 통해 바깥에 호출되기 때문에 adapter는 모두 package-private 접근 수준으로 둬도 된다. 108 | - 어플리케이션 계층에서 어댑터로 향하는 우발적 의존성은 있을 수 없다. 109 | - public이어야 하는 application, domain의 일부 클래스 110 | - application의 port(in, out) 111 | - `SendMoneyUseCase`, `LoadAccountPort`, `UpdateAccountStatePort` 112 | - 도메인 클래스 113 | - `Account`, `Activity` 114 | - package-private이어도 되는 서비스 클래스 115 | - 인커밍 포트 인터페이스 뒤에 숨겨지는 서비스는 public일 필요가 없다. 116 | - `GetAccountBalanceService` 117 | 118 | ## 4. 의존성 주입의 역할 119 | 120 | - 클린 아키텍처의 본질적인 요건 121 | 122 | `어플리케이션이 인커밍/아웃고잉 어댑터에 의존성을 갖지 않아야 한다.` 123 | 124 | - 의존성 역전 원칙 이용 125 | - 어플리케이션 계층에 인터페이스(port)를 만들고 어댑터에 해당 인터페이스를 구현한 클래스를 둔다. 126 | - 모든 계층에 의존성을 가진 중립적인 컴포넌트를 하나 두고, 이 컴포넌트가 아키텍처를 구성하는 대부분의 클래스를 초기화하는 역할을 한다. 127 | - 웹 컨트롤러가 서비스에 의해 구현된 인커밍 포트를 호출한다. 서비스는 어댑터에 의해 구현된 아웃고잉 포트를 호출한다. 128 | 129 | ![image](https://user-images.githubusercontent.com/37948906/154954236-0972c09e-d6c7-41d1-ad79-eedb73822d85.png) 130 | 131 | - AccountController 132 | - SendMoneyUseCase 인터페이스가 필요하므로 의존성 주입을 통해 SendMoneyService 클래스의 인스턴스를 주입 133 | - SendMoneyService 134 | - LoadAccount 인터페이스로 가장한 AccountPersistenceAdapter 클래스의 인스턴스 주입 135 | 136 | 137 | ## 5. 유지보수 가능한 소프트웨어를 만드는 데 어떻게 도움이 될까? 138 | 139 | - 코드에서 아키텍처의 특정 요소를 찾으려면 이제 아키텍처 다이어그램의 박스 이름을 따라 패키지 구조를 탐색하면 된다. 140 | - 이를 통해 의사소통, 개발, 유지보수 모두가 조금 더 수월해진다. -------------------------------------------------------------------------------- /chapter9/seyun.md: -------------------------------------------------------------------------------- 1 | # 9. 애플리케이션 조립하기 2 | 3 | ## 왜 조립까지 신경 써야 할까? 4 | 5 | - 코드의 의존성이 올바른 방향을 가리키게 하기 위해서다. 6 | - 모든 의존성은 안쪽으로, 애플리케이션의 도메인 코드 방향으로 향해야 도메인 코드가 바깥 계층의 변경으로부터 안전하다는 점을 기억하자. 7 | 8 | ## 우리의 객체 인스턴스를 생성할 책임은 누구에게 있을까? 그리고 어떻게 의존성 규칙을 어기지 않으면서 그렇게 할 수 있을까? 9 | 10 | - 아키텍처에 대해 중립적이고 인스턴스 생성을 위해 모든 클래스에 대한 의존성을 가지는 설정 컴포넌트(configuration component)가 있어야 한다는 것이다. 중립적인 설정 컴포넌트는 인스턴스 생성을 위해 모든 클래스에 접근할 수 있다. 11 | 12 | ## 설정 컴포넌트의 역할 13 | 14 | - 웹 어댑터 인스턴스 생성 15 | - HTTP 요청이 실제로 웹 어댑터로 전달되도록 보장 16 | - 유스케이스 인스턴스 생성 17 | - 웹 어댑터에 유스케이스 인스턴스 제공 18 | - 영속성 어댑터 인스턴스 생성 19 | - 유스케이스에 영속성 어댑터 인스턴스 제공 20 | - 영속성 어댑터가 실제로 데이터베이스에 접근할 수 있도록 보장 21 | 22 | ## 평범한 코드로 조립하기 23 | 24 | ```java 25 | package com.book.cleanarchitecture.buckpal; 26 | 27 | import com.book.cleanarchitecture.buckpal.account.adapter.in.web.SendMoneyController; 28 | import com.book.cleanarchitecture.buckpal.account.adapter.out.persistence.AccountPersistenceAdapter; 29 | import com.book.cleanarchitecture.buckpal.account.application.port.in.SendMoneyUseCase; 30 | import com.book.cleanarchitecture.buckpal.account.application.service.SendMoneyService; 31 | 32 | public class Application { 33 | public static void main(String[] args) { 34 | AccountRepository accountRepository = new AccountRepository(); 35 | ActivityRepository activityRepository = new ActivityRepository(); 36 | 37 | AccountPersistenceAdapter accountPersistenceAdapter = new AccountPersistenceAdapter(accountRepository, activityRepository); 38 | 39 | SendMoneyUseCase sendMoneyUseCase = new SendMoneyService( 40 | accountPersistenceAdapter, 41 | accountPersistenceAdapter 42 | ); 43 | 44 | SendMoneyController sendMoneyController = new SendMoneyController(sendMoneyUseCase); 45 | 46 | startProcessingWebRequests(sendMoneyController); 47 | } 48 | } 49 | ``` 50 | 51 | 1. 웹 컨트롤러, 유스케이스, 영속성 어댑터가 단 하나씩만 있는 애플리케이션 52 | 2. 각 클래스가 속한 패키지 외부에서 인스턴스를 생성하기 때문에 이 클래스들은 전부 public이여야 한다. 53 | 3. 다행히도 package-private 의존성을 유지하면서 이처럼 지저분한 작업을 대신해줄 수 있는 주입 프레임워크가 있는데 그게 바로 spring이다. 54 | 55 | ## 스프링의 클래스패스 스캐닝으로 조립하기 56 | 57 | - 스프링 프레임워크를 이용해서 애플리케이션을 조립한 결과물을 애플리케이션 컨텍스트라고 한다. 애플리케이션 컨텍스트는 애플리케이션을 구성하는 모든 객체(자바 용어로는 빈(bean))을 포함한다. 58 | - 스프링은 클래스패스 스캐닝으로 클래스패스에서 접근 가능한 모든 클래스를 확인해서 `@Component` 애너테이션이 붙은 클래스를 찾는다. 59 | 60 | - 스프링이 인식할 수 있는 커스텀 애노테이션도 생성 가능하다. 61 | 62 | ```java 63 | package com.book.cleanarchitecture.buckpal.shared; 64 | 65 | import org.springframework.core.annotation.AliasFor; 66 | import org.springframework.stereotype.Component; 67 | 68 | import java.lang.annotation.*; 69 | 70 | @Target({ElementType.TYPE}) 71 | @Retention(RetentionPolicy.RUNTIME) 72 | @Documented 73 | @Component 74 | public @interface UseCase { 75 | 76 | @AliasFor(annotation = Component.class) 77 | String value() default ""; 78 | } 79 | ``` 80 | 81 | - 메타 애너테이션으로 `@Component` 를 포함하고 있어서 스프링이 클래스패스 스캐닝을 할 때 인스턴스를 생성할 수 있게 한다. 82 | - 단점 83 | 1. 클래스에 프레임워크에 특화된 애너테이션을 붙어야 한다는 점에서 침투적이다. 84 | 2. 마법 같은 일이 일어날 수 있다 85 | 86 | 87 | ## 스프링의 자바 컨피그로 조립하기 88 | 89 | - `@Configuration` 을 통해 스캐닝을 가지고 찾아야 하는 설정 클래스임을 표시한다. 90 | - 이건 모든 빈을 찾아오는건 아니기 때문에, 마법이 일어날 일은 적다. 91 | - 설정 클래스와 같은 패키지에 넣어놓지 않는 경우에 public으로 해야 한다. 92 | - 패키지를 모듈 경계로 사용하고 각 패키지 안에 전용 클래스를 만들 수 있지만, 하위 패키지를 사용할 순 없다. 93 | 94 | ## 출처 95 | 96 | - [만들면서 배우는 클린 아키텍처 - 자바 코드로 구현하는 클린 웹 애플리케이션](https://www.aladin.co.kr/shop/wproduct.aspx?ItemId=283437942) 97 | --------------------------------------------------------------------------------