추천을 받은 책이다. 회사 분들과의 어마어마한 실력 차이를 줄이는 데에 도움이 됐으면 좋겠다. 소중한 기회를 얻게 된 것에 감사하며 열심히 성장할 것이다🧑🏻💻
layered architecture의 구성: web -> domain -> persistence
It promotes Database-Driven Design
중요한 것은 domain인데 디자인은 database를 기반으로 하게 됨
orm쓰면 domain의 변화가 database의 변화를 야기함
컨트롤러에서 바로 엔티티, 즉 persistence layer를 호출해서 로직을 쌓는 경우도 생기는데, 이런 식으로
persistence layer에 로직을 넣는 생기면 유지보수도 어렵고 테스트하기도 어려운 코드가 만들어진다
뚱뚱한 service layer가 만들어지는 경우도 있는데, 그러다보면 어떤 서비스에 어떤 use case가 있는지도 확인하기 어려워진다
뚱뚱한 service는 협업하기 어려운 코드가 되기도 한다
SRP를 다시 정의하면 A Component should have only one reason to change이다
layer A가 B를 부르고, B가 C를 부른다면, C의 변화는 B를, B의 변화는 A에 영향을 줄 수 밖에 없을텐데, 이런 의존성을 어떻게 개선할 수 있을까?
해답은 DIP이다. 인터페이스를 정의해두고 구현체를 외부에서 연동해주는 식으로 의존성을 역전시킬 수 있다.
로버트 마틴은 이를 활용하여 클린 아키텍쳐(aka hexagonal architecture)를 주장했다.
Web, UI, DB, Device와 같은 External Interfaces가 제일 밖,
Gateway, Presenters, Controller가 그 안,
UseCase가 그 안
Entity가 가장 안에 존재하는데, 이런식으로 Core가 안에 들어있고 안으로 갈수록 의존성이 없는 코드가 된다
코드를 layered 하게 작성하면 이런 형태가 된다
buckpal
-- domain
---- account
---- activity
---- accountRepository
---- accountService
--persistence
----accountRepositoryImpl
--web
----accountController
이렇게 하면 layered architecture의 단점을 개선할 수 없고 incoming and outgoing port가 보이지 않는다
좀 더 개선해서 feature별로 작성하는 방법도 있다
buckpal
-- account
---- account
---- accountController
---- accountRepository
---- accountRepositoryImpl
---- sendMoneyService
이렇게 하면 layered architecture의 의존성을 조금 개선할 수 있을 지는 몰라도 여전히 incoming and outgoing port는 드러나지 않는다
clean architecture에서는 이렇게 사용한다
buckpal
-- account
---- adapter
------ in
-------- web
---------- accountController
------ out
-------- persistence
---------- accountPersistenceAdapter
---------- springDataAccountRepository
---- domain
------ account
------ activity
---- application
------ sendMoneyService
------ port
-------- in
---------- sendMoneyUseCase
-------- out
---------- loadAccountPort
---------- updateAccountStatePort
어댑터가 incoming usecase를 부르고, usecase를 service가 구현하고 service가 outgoing port를 부르는데, outgoing port를 구현한 adapter에서 persistence layer동작을 한다
The web controller calls an incoming port, which is implemented by a service. The service calls an outgoing port, which is implemented by an adapter
usecase의 역할
1. takes input
2. validates business rules
3. manipulates the model state
4. returns output
business rule을 validate할 때도 룰이 있는데, core에 dependency가 없는 코드를 검증하는 것이다.
core에 dependency가 있는 거는 안쪽에서 검증
usecase가 다르면 input, output model 또한 다른거를 사용해야 함
An incoming adapter talks to the application layer through dedicated incoming ports, which are interfaces implemented by the application services
incoming port로 요청을 보내고 incoming port는 service가 구현함
outgoing port로 요청이 리턴되는데 web adapter가 이를 구현함
web adapter의 책임
1. maps http requests to java objects
2. performs authorization checks
3. validates input
4. maps input to the input model of the use case
5. calls the use case
6. maps the output of the use case back to http
7. returns an http response
web adapter도 뚱뚱하지 않게 유지해주는 것이 좋고 그렇게 하려면 usecase를 잘게 쪼개야 한다
The services from the core use ports to access the persistence adapter
outgoing port가 있고 persistence adapter가 이를 구현
persistence adapter의 책임
1. takes input
2. maps input into a database format
3. sends input to the database
4. maps database output into an application format
5. returns output
이전에서 말한 것과 같은 논리로 port를 잘게 쪼개고 adapter 또한 잘게 쪼개는 것이 좋다
유닛 테스트 - usecase를 검증, mockito같은거로 목 객체 만들어서 쓰기도 함
통합 테스트 - webadapter를 검증, 여전히 mock을 쓰기는 하지만 controller 응답이나 testcontainer를 써서 db와 같은 어댑터 영역을 테스트
시스템 테스트 - 사용자가 도달할 수 있는 main path를 검증, 다 띄워서 한다고 보면 됨
테스트할 때에는 수치보다는 중요한 path를 테스트하는게 더 의미있음
layer 간에 mapper를 사용해야 하는가?
no mapping strategy - 매퍼 사용 안함. 도메인이 input output model로 공유됨, 다른 layer의 변화가 domain의 변화를 야기하기도 하고 domain을 해석하기 위해 다른 layer에 어노테이션을 붙이는 작업이 필요할 수 있음 -> SRP 위반. 처음에는 괜찮아보일 수 있지만 장기적으로는 좋지 않은 전략
two-way mapping strategy - web adapter랑 db adapter에서 모델을 반들어서 변환해서 사용함. 구조상 분리가 된다는 점에서 조금 더 깔끔함. 하지만 boilerplate code가 늘어나고 도메인 로직이 여러 layer에서 사용되는 건 마찬가지임
full mapping strategy - adapter영역말고 port나 usecase단에서도 매핑을 시켜줌. 구조상 더 분리가 잘 되는 구조이지만 boilerplate코드가 증가. 좀 과한 감이 있기도 함
one-way mapping strategy - interface로 쓸 수 있는 모델을 서비스 레이어쪽에 하나 만들어두고 이거를 web, db adapter가 사용하도록 하는 방식. 의존성이 클린 아키텍쳐가 의도한대로 코어쪽으로 가기 때문에 좋은 구조이고 ddd창시자인 Eric Evans의 견해랑도 맞는 구조이지만 잘 사용하기에는 난이도가 있는편
정답은 없고 상황에 따라 잘 판단해서 쓰는 게 모범답안
만들었으면 조립해야 되고, 조립에도 방법이 여러 가지 존재
클린 아키텍쳐의 핵심 중 하나는 의존성을 역전시키는거고 의존성을 역전시켜주기 위해서는 어디선가 configuration으로 의존성을 지정해줘야 하는데, 그렇게 하는 방법이 여러가지 존재
configuration component의 역할
1. create web adapter instances
2. ensure that HTTP requests are actually routed to the web adapters
3. create use case instances
4. provide web adapters with use case instances
5. create persistence adapter instances
6. provide use cases with persistence adapter instances
7. ensure that the persistence adapters can actually access the database
역할이 좀 많긴 하지만, 어디선가는 필요한 작업. 따라서 이 파트에서만 SRP를 깨고 다른 파드들에서는 SRP를 지키는 방향을 선택
좋은 책이고 재미있게 읽었습니다. 번역서가 잘 번역되었으나 일부 오역이나 누락된 부분이 있어 원서를 같이 찾아봤었네요. 추천합니다.