본문 바로가기
독서

만들면서 배우는 클린 아키텍처

by 탁구치는 개발자 2023. 9. 26.

p.3

우리는 상태가 아니라 행동을 중심으로 모델링한다. 어떤 애플리케이션이든 상태가 중요한 요소이긴 하지만 행동이 상태를 바꾸는 주체이기 때문에 행동이 비즈니스를 이끌어준다.

 

p.7

테스트 설정이 복잡해지는 것은 테스트를 전혀 작성하지 않는 방향으로 가는 첫걸음이다. 왜냐하면 복잡한 설정을 할 시간이 없기 때문이다.

 

p.10

코드에 넓은 서비스가 있다면 서로 다른 기능을 동시에 작업하기가 더욱 어렵다. 서로 다른 유스케이스에 대한 작업을 하게 되면 같은 서비스를 동시에 편집하는 상황이 발생하고, 이는 병합 충돌과 잠재적으로 이전 코드로 되돌려야 하는 문제를 야기하기 때문이다.

 

p.17

도메인 계층에 인터페이스를 도입함으로써 의존성을 역전시킬 수 있고, 그 덕분에 영속성 계층이 도메인 계층에 의존하게 된다. 도메인 코드가 바깥으로 향하는 어떤 의존성도 없어야 함을 의미한다. 대신 의존성 역전 원칙의 도움으로 모든 의존성이 도메인 코드를 향하고 있다.

 

p.19

클린 아키텍처에는 대가가 따른다. 가령 영속성 계층에서 ORM 프레임워크를 사용한다고 해보자. 일반적으로 ORM 프레임워크는 데이터베이스 구조 및 객체 필드와 데이터베이스 칼럼의 매핑을 서술한 메타데이터를 담고 있는 엔티티 클래스를 필요로 한다. 도메인 계층은 영속성 계층을 모르기 때문에 도메인 계층에서 사용한 엔티티 클래스를 영속성 계층에서 함께 사용할 수 없고 두 계층에서 각각 엔티티를 만들어야 한다. 즉, 도메인 계층와 영속성 계층이 데이터를 주고받을 때, 두 엔티티를 서로 변환해야 한다는 뜻이다. 이는 도메인 계층과 다른 계층들 사이에서도 마찬가지다.

 

p.24

계층으로 구성하기 계층으로 패키지를 구성하면 애플리케이션의 기능 조각이나 특성을 구분 짓는 패키지 경계가 없어진다. web 패키지에 UserController를 추가 domain 패키지에 UserService, UserRepository, User를 추가 persistence 패키지에 UserRepositoryImple 추가

 

p.25

기능으로 구성하기 기능에 의한 패키징 방식은 사실 계층에 의한 패키징 방식보다 아키텍처의 가시성을 훨씬 더 떨어뜨린다. 그럼 한눈에 파악하기 쉬운 아키텍처는 어떻게 만들 수 있을까? 육각형 아키텍처를 사용 표현력 있는 패키지 구조는 적어도 코드와 아키텍처 간의 갭을 줄일 수 있게 해준다.

 

p.43

빌더 뒤에 숨겨진 대신 생성자를 직접 사용했다면 새로운 필드를 추가하거나 필드를 삭제할 때마다 컴파일 에러를 따라 나머지 코드에 변경사항을 반영할 수 있었을 것이다. 파라미터들을 헷갈리지 않도록 IDE가 파라미터명 힌트를 보여준다.

 

p.55

application.port.in 패키지 어댑터와 유스케이스 사이에 또 다른 간접 계층을 넣어야 할까? 애플리케이션 코어가 외부 세계와 통신할 수 있는 곳에 대한 명세가 포트이기 때문이다. 포트를 적절한 곳에 위치시키면 외부와 어떤 통신이 일어나고 있는지 정확히 알 수 있고, 이는 레거시 코드를 다루는 유아지보수 엔지니어에게는 무척 소중한 정보다.

 

p.57

컨트롤러를 몇 개 만들어야 할까? 너무 적은 것보다는 너무 많은 게 낫다.

 

p.59

보통 테스트 코드는 더 추상적이라서 프로덕션 코드에 비해 파악하기가 어려울 때가 많다. 따라서 특정 프로덕션 코드에 해당하는 테스트 코드를 찾기 쉽게 만들어야 하는데, 클래스가 작을수록 더 찾기가 쉽다.

 

p.62

여러 작은 클래스들을 만드는 것을 두려워해서는 안 된다. 작은 클래스들은 더 파악하기 쉽고, 더 테스트하기 쉬우며, 동시 작업을 지원한다. 이렇게 세분화된 컨트롤러를 만드는 것은 처음에는 조금 더 공수가 들겠지만 유지보수하는 동안에는 분명히 빛을 발할 것이다.

 

p.65 영속성 어댑터의 책임 AccountPersistenceAdapter

  • 입력을 받는다.
  • 입력을 데이터베이스 포맷으로 매핑한다.
  • 입력을 데이터베이스로 보낸다.
  • 데이터베이스 출력을 애플리케이션 포맷으로 매핑한다.
  • 출력은 반환한다.

p.66

출력 모델이 영속성 어댑터가 아니라 애플리케이션 코어에 위치하는 것이 중요하다.

 

p.67

인터페이스 분리 원칙은 이 문제의 답을 제시한다. 이 원칙은 클라이언트가 오로지 자신이 필요로 하는 메서드만 되도록 넓은 인터페이스를 특화된 인터페이스로 분리해야 한다고 설명한다.

 

p.70

바운디드 컨텍스트는 영속성 어댑터를 하나씩 가지고 있다. 바운디드 컨텍스트라는 표현은 경계를 암시한다. account 맥락의 서비스가 billing 맥락의 영속성 어댑터에 접근하지 않고, 반대로 billing의 서비스도 account의 영속성 어댑터에 접근하지 않는다는 의미다. 어떤 맥락이 다른 맥락에 있는 무엇인가를 필요로 한다면 전용 인커밍 포트를 통해 접근해야 한다.

 

p.79

도메인 코드에 플러그인처럼 동작하는 영속성 어댑터를 만들면 도메인 코드가 영속성과 관련된 것들로부터 분리되어 풍부한 도메인 모델을 만들 수 있다. 좁은 포트 인터페이스를 사용하면 포트마다 다른 방식으로 구현할 수 있는 유연함이 생긴다. 심지어 포트 뒤에서 애플리케이션이 모르게 다른 영속성 기술을 사용할 수도 있다. 포트의 명세만 지켜진다면 영속성 계층 전체를 교체할 수도 있다.

 

p.82

테스트하는 클래스가 다른 클래스에 의존한다면 의존되는 클래스들은 인스턴스화하지 않고 테스트하는 동안 필요한 작업들을 흉내 내는 목(mock)으로 대체한다.

 

p.86

모든 동작을 검증하는 대신 중요한 핵심만 골라 집중해서 테스트하는 것이 좋다. 만약 모든 동작을 검증하려고 하면 클래스가 조금이라도 바뀔 때마다 테스트를 변경해야 한다. 이는 테스트의 가치를 떨어뜨리는 일이다.

 

p.88

MockMvc 객체를 이용해 모킹했기 때문에 실제로 HTTP 프로토콜을 통해 테스트한 것은 아니다. 프레임워크가 HTTP 프로토콜에 맞게 모든 것을 적절히 잘 변환한다고 믿는 것이다.

 

p.91

영속성 어댑터 테스트는 실제 데이터베이스를 대상으로 진행해야 한다. Testcontainers 같은 라이브러리는 필요한 데이터베이스를 도커 컨테이너에 띄울 수 있기 때문에 이런 측면에서 아주 유용하다.

 

p.95

라인 커버리지는 테스트 성공을 측정하는 데 있어서는 잘못된 지표다. 코드의 중요한 부분이 전혀 커버되지 않을 수 있기 때문에 100%를 제외한 어떤 목표도 완전히 무의미하다. 그리고 심지어 100%라 하더라도 버그가 잘 잡혔는지 확신할 수 없다. 나는 얼마나 마음 편하게 소프트웨어를 배포할 수 있느냐를 테스트의 성공 기준으로 삼으면 된다고 생각한다. 테스트를 실행한 후에 소프트웨어를 배포해도 될 만큼 테스트를 신뢰한다면 그것으로 된 것이다. 더 자주 배포할수록 테스트를 더 신뢰할 수 있다. 각각의 프로덕션 버그에 대해서 "테스트가 이 버그를 왜 잡지 못했을까?"를 생각하고 이에 대한 답변을 기록하고, 이 케이스를 커버할 수 있는 테스트를 추가해야 한다.

 

p.96

새로운 필드를 추가할 때마다 테스트를 고치는 데 한 시간을 써야 한다면 뭔가 잘못된 것이다. 아마도 테스트가 코드의 구조적 변경에 너무 취약할 것이므로 어떻게 개선할지 살펴봐야 한다. 리팩터링할 때마다 테스트 코드도 변경해야 한다면 테스트는 테스트로서의 가치를 잃는다.

육각형 아키텍처는 도메인 로직과 바깥으로 향한 어댑터를 깔끔하게 분리한다. 덕분에 핵심 도메인 로직은 단위 테스트로, 어댑터는 통합 테스트로 처리하는 명확한 테스트 전략을 정의할 수 있다.

 

p.108

한 클래스가 필요로 하는 모든 객체를 생성자로 전달할 수 있다면 실제 객체 대신 목으로 전달할 수 있고, 이렇게 되면 격리된 단위 테스트를 생성하기가 쉬워진다.

 

p.121

package-private(혹은 default) 제한자는 중요하다. 자바 패키지를 통해 클래스들을 응집적인 '모듈'로 만들어 준다. 이러한 모듈 내에 있는 클래스들은 서로 접근 가능하지만, 패키지 바깥에서는 접근할 수 없다. 그럼 모듈의 진입점으로 활용될 클래스들만 골라서 public으로 만들면 된다. 이렇게 하면 의존성이 잘못된 방향을 가리켜서 의존성 규칙을 위반할 위험이 줄어든다.

 

p.123

의존성 규칙을 위반했는지 확인하는 방법으로 컴파일 후 체크를 도입 ArchUnit 도구 활용

 

p.130

새로운 코드를 추가하거나 리팩터링할 때 패키지 구조를 항상 염두에 둬야 하고, 가능한다면 package-private 가시성을 이용해 패키지 바깥에서 접근하면 안 되는 클래스에 대한 의존성을 피해야 한다.

 

p.134

깨진 유리창의 법칙이 코드에도 적용 품질이 떨어진 코드에서 작업할 때 더 낮은 품질의 코드를 추가하기가 쉽다. 코딩 규칙을 많이 어긴 코드에서 작업할 때 또 다른 규칙을 어기기도 쉽다. 지름길을 많이 사용한 코드에서 작업할 때 또 다른 지름길을 추가하기도 쉽다.

 

p.143

헥사고날 아키텍처 스타일을 사용할지 말지를 결정할 첫 번째 지표로서, 만약 도메인 코드가 애플리케이션에서 가장 중요한 것이 아니라면 이 아키텍처 스타일은 필요하지 않을 것이다.

 

p.144

아키텍처 스타일에 대해서 괜찮은 결정을 내리는 유일한 방법은 다른 아키텍처 스타일을 경험해 보는 것이다. 육각형 아키텍처에 대한 확신이 없다면 지금 만들고 있는 애플리케이션의 작은 모듈에 먼저 시도해 보라. 그러면 이 경험이 다음 아키텍처 결정을 이끌어 줄 것이다. 어떤 아키텍처 스타일을 골라야 하는가에 대한 내 대답은 전문 컨설턴트의 "그때그때 달라요."와 같다. 어떤 소프트웨어를 만드느냐에 따라서도 다르고, 도메인 코드의 역할에 따라서도 다르다. 팀의 경험에 따라서도 다르다. 그리고 최종적으로 내린 결정이 마음에 드느냐에 따라서도 다르다.

 

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

누구도 나를 파괴할 수 없다  (1) 2023.10.06
당신과 나 사이  (1) 2023.10.02
빠르게 실패하기  (0) 2023.09.30
김미경의 마흔수업  (1) 2023.09.26
마음챙김  (1) 2023.09.10
우리는 모두 죽는다는 것을 기억하라  (0) 2023.09.10
2029 기계가 멈추는 날  (1) 2023.08.31
언젠가 잘리고, 회사는 망하고, 우리는 죽는다!  (0) 2023.08.31