나의 개발 일상 기록
[MSA] MSA 프로젝트 구조 및 설계 본문
좋은 구현이란?
강의를 통해 좋은 코드를 구현하는 방식에 대해서 알게 되었습니다. 많은 개발자들간에 코드로 소통이 이루어지기 위해서는 잘 짜여진 코드의 요건들이 존재하는 것을 알게 되었습니다. 어떠한 요건들이 있는지 정리를 해보았습니다.
1. 비즈니스 가치를 명확히 충족시켜야 합니다.
처음 개발에 입문하였을 때 많은 기술을 익히고 자유롭게 사용하면서 다양한 퍼포먼스를 보여주는 것이 개발자라고 생각하였습니다. 그렇지만 회사의 비즈니스 가치와 상관없이 기술적 성취에만 관심을 가진다면 회사와 개발자 서로 간의 목표를 달성하는 부분에 있어 어긋나는 경우가 발생하게 된다면 성공적인 성과를 낼 수 있을까라는 생각이 들었습니다. 기술은 도구일 뿐이고 그 도구를 활용하여 익숙해지고 좀 더 좋은 도구를 찾는 근원적인 이유도 비즈니즈 목표 달성을 위한 것이 된다면 회사가 추구하는 가치와 개발자가 추구하는 가치를 서로 충족이 될 것이라고 생각됩니다.
2. 가독성이 좋아야 합니다.
개발 업무를 하다보면 문서의 도움 없이 코드의 파악으로만 업무의 요건을 파악할 경우가 생길 수 있습니다. 그렇기 때문에 그만큼 코드 자체의 가독성이 좋아야 업무 효율을 지속적으로 높게 유지할 수 있습니다. 도메인 로직을 설명하는 별도의 문서보다는 코드 자체로 도메인을 파악할 수 있어야 합니다.
3. 테스트코드 작성이 쉬워야 합니다.
테스트 코드는 지속적인 기능 런칭과 리펙토링을 가능하게 해주는 안정장치입니다. 코드간의 의존성이 많다면 그만큼 테스트 코드 작성이 어렵습니다. 그에 반헤 테스트 코드 작성이 쉬운 코드는 데체적으로 코드 품질이 좋은 편입니다.
4. 변경에 유연해야 합니다.
100% 요건이 변경되지 않는 요건은 없습니다. 언제든지 요건은 변경이 가능하기 때문에 항상 그부분을 인지해야 합니다. 그렇게 때문에 코드 구현과 설계는 요구사항 변경에 유연하도록 작성되어야 합니다. 이를 가능하게 하는 여러가지 객체 지향 설계 원칙(SOLID)이 존재합니다. 이에 대한 개념을 잘 알고 적용하는 것이 좋은 코드를 구현하는 것에 많은 도움이 됩니다.
- 단일 책임 원칙(SRP)
'모든 클래스는 하나의 책임만 가지며, 클래스는 그 책임을 완전히 캡슐화해야한다.'는 프로그래밍 원칙입니다.
- 개방 폐쇄 원칙(OCP)
'소프트웨어 개체(클래스, 모듈, 함수 등등)는 확장에 대해 열려 있어야 하고, 수정에 대해서는 닫혀 있어야 한다.'는 프로그래밍 원칙입니다.
- 리스코프 치환 원칙(LSP)
'상위 타입의 객체를 하위 타입의 객체로 치환해도 상위 타입을 사용하는 프로그램은 정상적으로 동작해야 한다.'는 프로그래밍 원칙입니다.
- 인터페이스 분리 원칙(ISP)
'클라이언트가 자신이 이용하지 않는 메서드에 의존하지 않아야 한다.'는 프로그래밍 원칙입니다.
- 의존 관계 역전 원칙(DIP)
'상위 모듈은 하위 모듈의 구현 내용에 의존하면 안되고 상위 모듈과 하위 모듈 무도 추상화된 내용에 의존해야 한다.'는 프로그래밍 원칙입니다.
프로젝트 구조 및 설계
도메인 주도 설계(DDD)
MSA 아키텍처는 Domain중심으로 Service를 모델링하고 구현하는 컨셉을 가지고 있습니다. 각각의 복잡한 Domain을 모델링하고 표현력있게 설계하는 것을 도메인 주도 설계(Domain-Driven Design)라고 합니다.
* DDD의 목표
DDD는 소프트웨어의 존재 가치는 사용자가 사용함으로써 의미를 가진다고 말할 수 있습니다. 사용자가 이해하고 원하는대로 목적에 맞게 사용 될 수 있도록 하는 것이 최고의 목표가 되어야 합니다. DDD를 프로젝트에 반영하기 위해서는 기술보다 도메인이 더 높은 우선순위를 가져야 하고, 어떤 문제를 하기 위해서는 항상 도메인을 먼저 고민하는 것을 필요로 합니다. 그리고 그 도메인을 바탕으로 설계(Design)하고 프로젝트에 지속적으로 반복적으로 반영하여 개발(Development)합니다.
프로젝트 구조
일반적인 Layer의 구조는 아래의 그림과 같습니다. Layer의 참조 관계에서는 단방향 의존을 유지하고 계층간 호출에서는 인터페이스를 통한 호출이 되어야 합니다.

Layer간의 참조 관계에서 application과 Infrastructure는 Domain Layer를 바라보게 하고 양방향 참조는 허용 하지 않게 합니다. Doamin Layer는 Low Level의 기술에 상관없이 독립적으로 존재할 수 있어야 합니다.
이를 위해 대부분의 주요 로직은 추상화되고, runtime시에는 DIP개념을 활용하여 실제 구현체가 동작되게 해야 합니다.
- 사용자 인터페이스(Interfaces)
사용자에게 정보를 보여주고 사용자의 명령을 해석하는 책임을 집니다. Controller, Dto, Mapper가 Interfaces에 해당됩니다.
- 응용 계층(Application)
수행할 작업을 정의하고 표현력 있는 도메인 객체가 문제를 해결하게 합니다. 이 계층에서 책임지는 작업은 업무상 중요하거나 다른 시스템의 응용 계층과 상호 작용하는 데 필요한 것들입니다. 이 계층은 얇게 유지되고, 오직 작업을 조정하고 아래에 위치한 계층에 포함된 도메인 객체의 협력자에게 작업을 위임합니다.
- 도메인 계층(Domain)
업무 개념과 업무 상황에 대한 정보, 업무 규칙을 표현하는 일을 책임집니다. 이 계층에서는 업무 상황을 반영하는 상태를 제어하고 사용하며, 그와 같은 상태 저장과 관련된 기술적인 세부사항은 인프라 스트럭쳐에 위임합니다. Domain Layer는 소프트웨어의 핵심이라고 할 수 있습니다.
Domain Layer의 표준 구현 방법
1. Domain Layer에서의 Service에서는 해당 도메인의 전체 흐름을 파악할 수 있도록 구현되어야 합니다.
2. Domain Layer에서의 모든 클래스명을 Service로 선언될 필요는 없습니다.
3. Service 간에는 참조 관계를 가지지 않도록 합니다.
- 인프라 스트럭처 계층(Infrastructure)
상위 계층을 지원하는 일반화된 기술적 기능을 제공합니다. 이러한 기능에는 애플리케이션에 대한 메시지 전송, 도메인 영속화, UI에 위젯을 그리는 것 등이 있습니다.
* Layer간 참조 관계에서 application과 Infrastructure는 domain layer를 바라보게 하고 양방향 참조는 허용하지 않게 하여야 합니다. 또한 low level의 기술이 변경이 되더라도 domain layer는 독립적으로 존재하여야 합니다. 이를 위해 대부분의 주요 로직은 추상화 되고, runtime 시에는 DIP 개념을 활용하여 실제 구현체가 동작해야 합니다.
* DIP(의존성 역전 원칙)
추상화 레벨이 높은 상위 수준의 모듈이 추상화 레벨이 낮은 하위 모듈에 의존하면 안된다는 것입니다.

Layer 별 구현
- Domain Layer
DDD의 목표는 기술보다는 도메인에 대한 모델에 집중해 더 나은 소프트웨어를 만들어내는 목표를 가지고 있습니다. 그렇기에 DDD에서는 Domain Layer가 핵심이 됩니다. DDD에서 말하는 Domain Layer의 역할은 업무 개념과 업무 상황에 대한 정보, 업무 규칙을 표현하는 일을 책임집니다. Domain Layer에선느 업무 상황을 반영하는 상태를 제어하고 사용하며 그와 같은 상태 저장과 관련된 기술적인 세부사항은 Infrastructuyre Layer에 위임합니다.
1. domain layer에서의 Service에서는 해당 도메인의 전체 흐름을 파악할 수 있도록 구현되어야 합니다.
domain layer는 어떤 업무를 어떤 순서로 처리했는지가 가장 중요한 관심사이기 때문에 실제 구현은 다른 layer에서 구현하는 것이 좋습니다. DIP를 활용하여 도메인이 사용하는 Interface의 실제 구현체를 주입 받아(Injection) 사용할 수 있도록 하는 것이 좋습니다. 예를 들어 기존 Spring JPA를 사용하던 기술을 MyBatis로 변경이 이루어진다면 하위 Layer에서의 변경만 이루어진다면 domain layer의 변경없이 언제든지 필요한 기술로 교체가 가능하게 됩니다.
2. 모든 클래스명이 OOOService로 선언될 필요가 없습니다.
비즈니스 로직을 담고 있는 클래스명을 Service로 한다고 배웠습니다. 그러나 하나의 도메인 패키지 내에 수많은 Service클래스가 존재하게 되면, 도메인 전체의 흐름을 컨트롤하는 Service가 무엇인지 알기 어렵습니다. 그렇기에 각각의 구현체는 그 책임과 역할에 맞는 네이밍으로 선언하는 것이 가독성에 좋습니다.
3. Service간에는 참조 관계를 가지지 않도록 합니다.
시간이 지날수록 업무가 다양해질수록 Service간에 참조 관계는 점점 늘어나는 경향이 있습니다. Service간에 참조관계가 많아질수록 가독성이 현지히 떨어지게 되고 업무를 파악하는 시간이 늘어날 수 있습니다. 그렇기에 Service내의 로직은 추상화 수준을 높게 가져가고 각 추상화의 실제 구현체는 잘게 쪼개어 만들어 도메인의 전체 흐름이 파악되면서도 로직이 간결하게 유지되는 코드를 가져갈 수 있습니다.
- Infrastructure Layer
Infrastructure Layer는 상위 계층을 지원화된 하위 Layer의 개념을 띄고 있으며 일반화된 기술적 기능을 제공하는 역할을 합니다.
1. DIP(의존성 역전 원칙) 개념을 활용합니다.
Domain Layer에 선언되고 사용되는 추상화된 Interface를 실제로 구현하여 runtime시에는 실제 로직이 동작하게 합니다.
2. Infrastructure Layer에서의 구현체 간에는 참조 관계를 허용합니다.
Infrastructure에서의 구현체는 Domain Layer에 선언된 Infrastructure를 구현하는 경우가 대부분이므로 Service에 비해 의존성을 많이 가지지 않게 됩니다. 로직의 재활용을 위해 Infrastructure내의 구현체를 의존 관계로 활용해도 좋지만 순환 참조가 발생하지 않도록 적절한 상하관계를 정의하는 것이 좋습니다.
- Application Layer
Application Layer는 비즈니스 규칙은 포함하지 않으며, 작업을 조정하고, 다음 하위계층에서 도메인 객체의 협력을 위해 업무를 위임합니다. 그렇기 때문에 해당 Layer는 얇게 유지가 되어야하며 작업을 조정하기만 하고 도메인 상태를 가지면 안됩니다.
1. Transaction으로 묶여야 하는 도메인 로직과 그 외의 로직을 Aggregation하는 역할로 한정을 짓습니다.
2. Facade의 개념은 복잡한 여러 개의 API를 하나의 인터페이스로 Aggregation하는 역할이지만 서비스 간의 조합으로 하나의 요구사항을 처리하는 클래스로 정의하는 방법도 좋습니다. 만약 주문완료 후 유저에게 카카오톡으로 주문 성공의 알림을 전달하는 경우 주문 처리과정에서의 모든 도메인 로직은 하나의 Transaction으로 묶여야 정합성에 이슈가 없어집니다. 그러나 알림 발송이 실패하였더라도 유저는 메인 서비스를 통해 주문완료를 확인 할 수 있습니다.
* Aggregation
전체가 부분을 포함하는(has a) 관계를 나타냅니다. 인스턴스들이 순환적인 관계를 갖지 않는 특성을 가지고 있습니다. 예를들어 한 학교에서 "홍길동"이라는 학생은 학생회도 속하지만 특정 동아리에도 속할 수 있을 것입니다. 학생회의 "홍길동"과 동아리의 "홍길동"은 동일한 인스턴스이므로 만일 홍길동의 전화번호를 변경한다면 두 곳 모두에서 변경된 전화번호를 가진 홍길동을 볼 수 있습니다. 이와 같이 보통 포함하는 관계이지만 포함하는 클래스와 포함되는 클래스의 생명주기(라이프사이클)는 다릅니다. 즉 학생회가 생길 때 학생이 생기지 않으며 학생회가 삭제된다고 학생이 삭제되지 않습니다.
- Interfaces Layer
Interfaces Layer는 사용자에게 정보를 보여주고 사용자의 명령을 해석하는 책임을 집니다.
1. http, gRPC, 비동기 메시징과 같은 서비스간 통신 기술은 가급적 Interfaces Layer에서만 사용하는 것이 좋습니다.
json처리 관련 로직이나 http cookie파싱 로직 등이 Domain layer에서 사용되는 식의 구현은 피하는 것이 좋습니다. 그렇게 하지 않으면 언제든지 교체될 수 잇는 외부 통신 기술로 인해 domain로직까지 변경되어야 하는 상황이 발생할 수 있습니다.
'MSA > MSA 구축' 카테고리의 다른 글
| [MSA] 선물하기 프로젝트_Flow설계 및 검토 (0) | 2021.09.17 |
|---|---|
| [MSA] 주문 프로젝트_OrderDomain (0) | 2021.09.16 |
| [MSA] 주문 프로젝트_ItemDomain (0) | 2021.09.14 |
| [MSA] 주문 프로젝트_Partner Domain2 (0) | 2021.09.13 |
| [MSA] 주문 프로젝트_Partner Domain1 (0) | 2021.09.09 |