Posts [만들면서 배우는 클린 아키텍처] Chapter8 - 경계 간 매핑하기
Post
Cancel

[만들면서 배우는 클린 아키텍처] Chapter8 - 경계 간 매핑하기

image

‘만들면서 배우는 클린 아키텍처’ 기술 서적을 읽고 학습 내용을 정리하기 위한 목적의 TIL 포스팅입니다🙆‍♂️ 예제코드는 깃허브 레포지토리를 참고해주세요.

8장 - 경계 간 매핑하기

  • 각 계층의 매핑을 모델을 매핑하는것에 대한 찬성파와 반대파가 있다.
    • 찬성파: 매핑 안할 경우 두 계층에서 같은 모델을 사용할텐데 두 계층이 강하게 결합된다.
    • 반대파: 보일러 플레이트코드가 너무 많아진다.

‘매핑하지 않기’ 전략

image 출처: https://jandari91.tistory.com/58

  • 웹 계층과 애플리케이션 계층 모두 Account 클래스에 접근해야 한다는것(두 계층이 같은 모델을 사용)을 의미한다.

장점

  • 간단한 CRUD 유스케이스에는 실용적일수 있다.
    • 같은 필드를 가진 웹 모델을 도메인 모델로, 혹은 도메인 모델을 영속성 모델로 매핑할 필요는 없다.
    • 도메인 모델에 추가한 JSON 이나 ORM 애너테이션 한두개를 바꿔야하더라도 큰 상관없다.

단점

  • 도메인 모델에 웹 계층, 영속성 계층과 관련된 애너테이션이 필요할수있고(JSON, JPA) 웹, 애플리케이션, 영속성 계층과 관련된 이유로 변경돼야 하므로 단일 책임 원칙을 위반하게 된다.
  • 오로지 한 계층에서만 필요로 하는 필드들을 포함하는 파편화된 도메인 모델로 이어질 수 있다.

정리

  • 모든 계층이 정확히 같은 구조의 정확히 같은 정보를 필요로 한다면 완벽한 선택지다.
  • 그러나 애플리케이션 계층이나 도메인 계층에서 웹과 영속성 문제를 다루게되면(애너테이션을 제외하더라도) 곧바로 다른 전략을 취해야한다.
  • 하지만 나중에 언제든 바꿀수 있다. 저자의 경험에 의하면 많은 유스케이스들이 간단한 CRUD 수준으로 시작했다가 시간이 지남에 따라 값비싼 매핑 전략이 필요한, 풍부한 행동과 유효성 검증을 가진 제대로된 비즈니스 유스케이스로 바뀌어갔기 때문이다. 또는 영원히 CRUD 유스케이스로 남을수도 있겠지만, 이 경우엔 다른 매핑 전략에 시간을 들이지 않았기에 이것 역시 반가운 일이다.

‘양방향’ 매핑 전략

image 출처: https://jandari91.tistory.com/58

  • 각 계층은 도메인 모델과는 완전히 다른 구조의 전용 모델을 가진다.(웹 모델, 영속성 모델)
    • 웹 계층에선 웹 모델을 인커밍 포트에서 필요한 도메인 모델로 매핑하고, 인커밍 포트에 의해 반환된 도메인 객체를 다시 웹 모델로 매핑한다.
    • 영속성 계층은 아웃고잉 포트가 사용되는 도메인 모델과 영속성 모델 간의 매핑과 유사한 매핑을 담당한다.
    • 두 계층 모두 양 방향으로 매핑하기에 양방향 매핑이라 부른다.
    • ex. 웹 계층에서 전용호 request 모델을 도메인 모델로 변환해서 유스케이스를 호출하고 도메인 모델을 반환값으로 리턴받아 별도 response 모델을 반환하는 구조

장점

  • 각 계층이 전용 모델을 변경하더라도 다른 계층에는 영향이 없다. 그래서 웹 모델은 데이터 최적으로 표현할 수 있는 구조를 가질수 있고, 도메인 모델은 유스케이스를 제일 잘 구현할 수 있는 구조를 가질 수 있다. 그리고 영속성 모델은 데이터베이스에 객체를 저장하기 위해 ORM에서 필요로 하는 구조를 가질 수 있다.
  • 이 매핑 전략은 웹이나 영속성 관심사로 오염되지 않은 깨끗한 도메인 모델로 이어지게 되고(JSON 이나 ORM 매핑 애너테이션 없이) 단일 책임 원칙을 만족하게 된다.
  • ‘양방향’ 매핑의 또 다른 장점은 ‘매핑하지 않기’ 전략 다음으로 간단한 전략이고 매핑 책임이 명확하다.
    • 즉, 바깥쪽 계층/어댑터는 안쪽 계층 모델로 매핑하고, 다시 반대 방향으로 매핑한다. 안쪽 계층은 해당 계층의 모델만 알면되고 매핑 대신 도메인 로직에 집중할 수 있다.

단점

  • 두 모델간 매핑구현으로 인해 꽤 시간이 들고, 너무 많은 보일러플레이트 코드가 생긴다.
  • 도메인 모델은 도메인 모델의 필요에 의해서만 변경되는것이 이상적이지만 바깥쪽 계층의 요구에 취약해진다.
    • 도메인 모델이 계층 경계를 넘어 통신하는데 사용되고 있기 때문이다. 인커핑 포트와 아웃고잉 포트는 도메인 객체를 입력 파라미터와 반환값으로 사용한다.

정리

  • 이 전략도 은총알은 아니다. 철칙처럼 여겨선 안된다.

‘완전’ 매핑 전략

image 출처: https://jandari91.tistory.com/58

  • 각 연산마다 별도 입출력 모델을 사용한다.
  • 계층 경계를 넘어 통신할때 도메인 모델을 사용하는 대신 별도 유스케이스 작업에 특화된 모델(SendMoneyCommand)을 사용한다.
    • 커맨드(command), 요청(request) 혹은 이와 비슷한 단어로 표현한다.
  • 웹 계층은 입력을 애플리케이션 계층의 커맨드 객체로 매핑할 책임을 가지게 된다.
  • 각 유스케이스는 전용 필드와 유효성 검증 로직을 가진 전용 커맨드를 가진다.
    • 값을 비워둘수있는 필드를 허용해선 안된다. 허용할 경우 현재 유스케이스에서 필요없는 유효성 검증이 수행될수도 있기 때문이다.
  • 애플리케이션 계층은 커맨드 객체를 유스케이스에 따라 도메인 모델을 변경하기 위해 필요한 무엇인가로 매핑할 책임을 가진다.

장점

  • 여러 유스케이스의 요구사항을 함께 다뤄야 하는 매핑에 비해 구현하고 유지보수하기 훨씬 쉽다.

정리

  • 전역 패턴으로 추천하진 않는다.
  • 웹 계층(인커밍 어댑터)과 애플리케이션 계층 사이에서 상태 변경 유스케이스의 경계를 명확하게 할 때 가장 효과적이다.
  • 애플리케이션 계층과 영속성 계층 사이에선 매핑 오버헤드 때문에 사용하지 않는 것이 좋다.
  • 연산의 입력 모델에 대해서만 이 매핑을 사용하고, 도메인 객체를 그대로 출력 모델로 사용하는것도 좋다. SendMonyUseCase 가 업데이트된 잔고를 가진채로 Account 객체를 그대로 반환하는것처럼 말이다.
  • 이처럼 매핑 전략은 여러가지를 섞어쓸수 있고, 섞어써야만 한다. 어떤 매핑 전략도 모든 계층에 걸쳐 전역 규칙일 필요가 없다.

‘단방향’ 매핑 전략

image 출처: https://jandari91.tistory.com/58

  • 모든 계층의 모델들이 같은 인터페이스를 구현한다.
  • 이 인터페이스는 관련 있는 특성(attribute)에 대한 getter 메서드를 제공해서 도메인 모델의 상태를 캡슐화한다.
  • 도메인 객체를 바같 계층으로 전달하고 싶으면 매핑 없이 할 수 있다. 왜냐하면 도메인 객체가 인커밍/아웃고잉 포트가 기대하는대로 상태 인터페이스를 구현하기 때문이다. 그러면 바깥쪽 계층에선 상태 인터페이스를 이용할지, 전용 모델로 매핑해야할지 결정할 수 있다.
  • 행동을 변경하는 것이 상태 인터페이스에 의해 노출돼 있지 않기 때문에 실수로 도메인 객체의 상태를 변경하는 일은 발생하지 않는다.
  • 애플리케이션 계층에선 상태 인터페이스를 실제 도메인 모델로 매핑해서 도메인 모델의 행동에 접근할 수 있게 된다.
  • DDD의 팩터리(factory)라는 개념과 잘 어울린다.
    • 팩터리(factory) : 어떤 특정한 상태로부터 도메인 객체를 재구성할 책임을 가지고 있다.
  • 매핑 책임은 명확하다. 만약 한 계층이 다른 계층으로부터 객체를 받으면 해당 계층에서 이용할 수 있도록 다른 무언가를 매핑하는것이다. 그러므로 각 계층은 한 방향으로만 매핑한다.
  • 다른 전략에 비해 개념적으로 어렵다…
  • 계층 간의 모델이 비슷할 때 가장 효과적이다. 예를 들어, 읽기 전용 연산의 경우 상태 인터페이스가 필요한 모든 정보를 제공하기 때문에 웹 계층에서 전용 모델로 매핑할 필요가 전혀 없다.

언제 어떤 매핑 전략을 사용할 것인가?

  • 상황에 맞게 최선의 전략을 사용하면 된다.
  • 하나의 매핑 전략을 전역 규칙으로 삼지마라. 특정 작업에 대해 최선이 아님에도 불구하고 깔끔하게 느껴진다고 택하는것은 무책임하다.
  • 소프트웨어는 시간이 지나면서 변화하기 떄문에 언제든 매핑전략이 변경될 수 있다.
  • 팀 내에서 상황에 따른 매핑 전략 가이드를 마련하는게 좋다. 이를 성공적으로 적용시키려면 구성원들의 인지와 공감이 필요하다. 더 나아가 팀 차원에서 지속적으로 논의하고 수정이 필요하다.

예시 변경 유스케이스

  • 예를 들어, 변경 유스케이스와 쿼리 유스케이스에 서로 다른 매핑 가이드라인을 정해둘수있다. 또 웹 계층과 애플리케이션 계층 사이에서 사용할 매핑 전략과 애플리케이션 계층에서 영속성 계층 사이에서 사용할 매핑 전략을 다르게 세울 수 있다.

변경 유스케이스

  • 웹 <-> 애플리케이션 계층 사이에선 유스케이스간 결합을 제거하기 위해 ‘완전 매핑’ 전략을 첫번째 선택지로 택한다.
    • 이렇게 하면 유스케이스별 유효성 검증 규칙이 명확해지고 특정 유스케이스에서 필요하지 않은 필드를 다루지 않아도 된다
  • 애플리케이션 <-> 영속성 계층 사이에선 매핑 오버헤드를 줄이고 빠르게 코드를 짜기 위해 ‘매핑하지 않기’ 전략을 첫 번째 선택지로 둔다.
    • 하지만 애플리케이션 게층에서 영속성 문제를 다뤄야 하게 되면 ‘양방향’ 매핑 전략으로 바꿔 영속성 문제를 영속성 계층에 가둘수있다.

쿼리 유스케이스

  • 웹 <-> 애플리케이션, 애플리케이션 <-> 영속성 계층 모두 매핑 오버헤드를 줄이고 빠르게 코드를 짜기 위해 ‘매핑하지 않기’ 전략을 첫번째 선택지로 둔다.
  • 하지만 애플리케이션 계층에서 영속성 문제나 웹 문제를 다뤄야 하게 되면 웹 <-> 애플리케이션, 애플리케이션 <-> 영속성 계층 사이에서 각각 ‘양방향’ 매핑 전략으로 바꾼다.

유지보수 가능한 소프트웨어를 만드는데 어떻게 도움이 될까?

  • 좁은 포트를 사용하면 유스케이스마다 다른 매핑 전략을 사용할 수 있고, 다른 유스케이스에 영향을 미치지 않으면서 코드를 개선할 수 있기 때문에 특정 상황, 특정 시점에 최선의 전략을 선택할 수 있다.
  • 상황별로 매핑 전략을 선택하는 것은 모든 상황에 같은 매핑 전략을 사용하는것보다 분명 더 어렵고 더 많은 커뮤니케이션이 필요하다. 하지만 매핑 가이드라인이 있는 한 코드가 정확히 해야 하는 일만 수행하면서도 더 유지보수하기 쉬운 코드로 팀에 보상이 되어 돌아올 것이다.

Reference

This post is licensed under CC BY 4.0 by the author.

[만들면서 배우는 클린 아키텍처] Chapter7 - 아키텍처 요소 테스트하기

[만들면서 배우는 클린 아키텍처] Chapter9 - 애플리케이션 조립하기