티스토리 뷰

Architecture

클린 아키텍처: 매핑

Jaime.Lee 2022. 8. 15. 10:30

이 포스팅은 만들면서 배우는 클린 아키텍처를 읽고 작성하였습니다.
소스 코드는 여기 있습니다.

Overview

여태까지 다루지 않은 매핑에 대한 문제를 다룹니다.

매퍼 구현을 피하기 위해 두 계층에서 같은 모델을 사용하는 것에 대한 논쟁이 있습니다.

두 계층 간 매핑을 하지 않으면 두 계층에서 같은 모델을 사용해야 하는데 이렇게 하면 두 계층이 강력하게 결합하게 됩니다.

반면에 두 계층 간 매핑을 따로 하게 되면 보일러플레이트 코드가 너무 많이 늘어나게 됩니다. 대부분 유스케이스들이 CRUD 작업만 수행하는 경우 계층 간에도 같은 모델을 사용하는 경우가 많아 계층 사이의 매핑이 의미가 없을 수도 있습니다.

각 매핑 전략에 따른 장단점을 알아보도록 하겠습니다.

매핑하지 않기 전략

송금하기 유스케이스를 그림으로 나타낸 것입니다.

웹 계층에서 컨트롤러가 유스케이스 인터페이스를 호출하여 유스케이스를 실행합니다. 이 인터페이스는 Account 객체를 전달해줘야하므로 웹 계층, 애플리케이션 계층 모두 Account에 의존성을 가지게 됩니다.

그리고 영속성 계층도 마찬가지 입니다. 모든 계층이 같은 모델을 사용하므로 계층 간 매핑을 할 필요가 없습니다.

이러한 설계는 사실 전통적인 레이어 아키텍처에서도 지양하는 설계이긴 합니다.

웹 계층과 영속성 계층은 모델에 대한 특별한 요구사항이 존재할 수 있습니다. 웹 계층의 경우 JSON으로 직렬화하기 위한 애너테이션을 사용해야 하고, 영속성 계층의 경우 Entity를 사용하기 위한 애너테이션이 필요합니다.

도메인과 애플리케이션 계층은 JSON, Entity로 사용될 일이 없음에도 Account 모델은 이 모든 것을 다뤄야하게 됩니다.

이렇게 할 경우 Account가 변경될 이유가 너무 많아지기 때문에 단일 책임 원칙(SRP, Single Responsibility Principle)을 위반하게 됩니다.

그럼 매핑하지 않기 전략은 단점만 있는 걸까요?

간단한 CRUD 유스케이스의 경우 같은 필드를 모델을 웹, 도메인, 영속성으로 각각 매핑할 필요가 없습니다. JSON, ORM을 위한 애너테이션도 변경될 일이 없다면 서로 방해를 하지 않습니다.

이렇게 모든 계층이 정확하게 같은 구조를 가지고 같은 정보를 필요로 한다면 매핑하지 않기 전략이 최고의 선택지가 됩니다.

하지만 이후에 수정해야할 일이 생긴다면 전략을 수정하면 됩니다. 어떤 매핑 전략을 사용하더라도 나중에 언제든 바꿀 수 있습니다.

양방향 매핑 전략

매핑하지 않기 전략과 다르게 각 계층이 전용 매핑 모델을 가지고 있습니다.

웹 계층에서는 웹 모델을 인커밍 포트에서 필요한 도메인 모델로 매핑하고, 인커밍 포트에서 도메인 모델이 다시 반환될 때 웹 모델로 매핑합니다.

영속성 계층 역시 아웃고잉 포트가 사용하는 도메인 모델과 영속성 모델을 매핑해주어야 합니다.

이렇게 양 방향으로 매핑이 이루어지기 때문에 양방향 매핑이라고 부릅니다.

각 계층이 전용 모델을 가지고 있기 때문에 해당 모델이 변경되더라도 내용이 변경되지 않는한 다른 계층에는 영향을 주지 않습니다. 웹 모델은 웹에 표현할 수 있는 최적의 구조를, 영속성 모델은 DB에 맞는 구조를 가질 수 있습니다.

이 구조는 계층 간의 관심사 오염이 발생하지 않으므로 단일 책임 원칙을 만족하게되고 매핑의 책임이 명확해져 도메인 로직에 집중할 수 있게 됩니다.

하지만 간단한 내용임에도 각 계층에 모델이 만들이저게되면 수많은 보일러플레이트 코드가 생성됩니다. 특히 매핑시 제네릭 타입을 사용하면 매핑 로직을 디버깅하거나 검증하는 데도 시간이 많이 소모됩니다.

그리고 도메인 모델이 인커밍 포트, 아웃고잉 포트 계층을 넘어 파라미터로써 통신하는 데 사용되기 때문에 다른 계층이 변할 때 영향을 받을 수 있습니다.

앞서 이야기한 것 처럼 유스케이스의 상황에 맞게 적절한 매핑 전략을 사용할 필요가 있습니다.

완전 매핑 전략

완전 매핑 전략은 각 연산마다 별도의 입출력 모델을 사용하는 전략입니다. 계층 간 통신을 할 때 도메인 모델을 사용하는 대신 각 작업에 특화된 모델을 사용합니다. 이 때 모델을 커맨드 또는 요청과 같은 단어로 표현합니다.

앞서 소개했던 전략들처럼 각 계층별로 매핑하는 것이 아니라 작업 단위로 매핑하기 때문에 더 많은 코드가 필요하지만 여러 유스케이스의 요구사항을 함께 다뤄야 하는 매핑에 비해 단순해지기 때문에 구현이 쉽고 유지보수가 쉬워질 수 있습니다.

이 전략은 인커밍 어댑터와 애플리케이션 계층 사이에서 상태 변경 유스케이스의 경계를 명확히 할 때 가장 빛을 발하는 전략입니다. 애플리케이션 계층과 영속성 계층 사이에서는 사용하지 않는 게 좋습니다.

이처럼 매핑 전략은 여러 가지를 상황에 맞게 섞어 써야합니다.

단방향 매핑 전략

plantuml을 이용해서 만들었더니 그림이 이쁘게 안 만들어지네요 ㅜㅜ

단방향 매핑 전략에서는 모든 계층의 모델이 같은 인터페이스를 구현합니다. 이 인터페이스는 속성과 getter 메서드를 제공해 도메인 모델의 상태를 캡슐화합니다.

도메인 모델 자체는 풍부한 기능을 구현할 수 있고 애플리케이션 계층의 서비스에서 접근할 수 있습니다. 도메인 객체를 다른 계층으로 전달하고 싶으면 매핑이 필요 없습니다. 이미 같은 인터페이스를 구현하고있기 때문입니다.

다른 계층에서도 모두 마찬가지 특징을 가지게 됩니다.

이 전략에서 매핑의 책임은 한 계층에서 객체를 전달받으면 해당 계층에서 이용할 수 있도록 다른 무언가로 매핑하는 것입니다. 각 계층은 한 방향으로만 매핑하게 되므로 단방향 매핑 전략이라고 부릅니다.

하지만 계층을 넘나들며 매핑해야하기 때문에 다른 전략에 비해 개념적으로 어렵습니다.

이 전략은 읽기 전용 기능의 경우 효과적입니다.

어떤 매핑 전략을 사용할까?

이 질문의 답은 예상하셨다시피 "그때그때 다르다" 입니다.

각 매핑 전략이 장단점을 가지고있기 때문에 한 전략을 강제하려는 충동을 이겨내야합니다. 여러 전략이 섞여있으면 오히려 더 헷갈릴 거 같지만 최선이 아님에도 그저 깔끔해보이기 위해 전략을 하나로 통일하는 것은 무책임한 일입니다.

소프트웨어가 변해감에 따라 전략도 그에 맞춰 변경하면 됩니다.

필요에따라 전략에 대한 우선순위 가이드를 정하고 협업할 때 활용할 수도 있습니다.

유지보수에 어떻게 도움이 될까?

인커밍/아웃커밍 포트는 서로 다른 계층이 어떻게 통신해야하는지 정의합니다. 여기에는 계층 사이의 매핑 여부와 어떤 매핑 전략을 선택할지가 포함됩니다.

각 유스케이스에 대해 좁은 포트를 사용하면 유스케이스마다 다른 매핑전략을 사용할 수 있기 때문에 다른 유스케이스에 영향을 주지 않고 코드를 개선할 수 있고, 전략도 변경할 수 있습니다.

상황별로 매핑 전략을 선택하는 것은 하나로 통일하는 것보다 분명 더 어렵지만 매핑 가이드라인을 정하면 유지보수하기 쉬운 코드를 작성할 수 있을 것입니다.

댓글