멀티 모듈 프로젝트에서 모듈 구성 사례
module layering
- 가장 일반적이고 보편적인 모듈 구성을 그림으로 나타냈다.
- 각각의 네모 박스는 모듈을 의미한다.
- 멀티 모듈 프로젝트 내에서 모듈을 grouping 할 기준이 필요한데, layered architecture에 기반하여 grouping 하고 의존 방향성을 한 방향으로 관리하는 것이 가장 깔끔했다.
- 모듈 의존 방향은
Application -> Domain -> Support - Support layer : persistence IO, external IO, 기타 util
- Domain layer : 각 기능 집합 단위로 business module을 구성
- Application layer : 외부에서 요구하는 애플리케이션 인터페이스를 만족하기 위해, API, Admin, Batch를 각각 독립된 서버로 구성하고, Business module을 가져다 presentation DTO로 변환하여 인터페이스.
- 각 layer의 상세 책임은 https://umbum.dev/categories/layered-architecture/ 참고
- 모듈 의존 방향은
비즈니스 모듈을 여러개로 쪼개는게 나을까?
[!warning] MSA 구조로 서버를 그 책임에 따라 나눠 N개로 구성하면서, 비즈니스 모듈도 서버 개수 만큼 N개로 나뉘어져 있는 형태를 많이 보게 된다. (e.g. app-user 모듈과 domain-user 모듈)
하지만 서버를 몇대로 나누어서 띄울지와 모듈을 몇개로 나누어서 구성할지는 완전히 무관하다는 것을 기억해야 한다. 책임 서버 N대에 책임 모듈 N개, 책임 서버 N대에 올인원 모듈 1개 모두 가능한 구성이다. (올인원 서버 1대에 책임 모듈 N개도 가능하지만 안티패턴) 서버는 fault-tolerance의 기준 / 모듈은 코드 격리의 기준이므로 서로 나눠야 하는 기준이 다르다.
모놀리틱을 MSA로 분리하면 장점은?
비즈니스 모듈을 N개로 쪼갰을 때 발생하는 일
- 나는 이 방식을 별로 선호하지 않는데, 우선 스파게티 코드의 원인은 각 비즈니스가 단일 모듈에 들어있기 때문이 아니라, 모든 layer가 혼합되어 단일 모듈에 들어가 있기 때문에 발생한다고 보는 측면이 훨씬 크기 때문이다.
- 따라서 layer를 기준으로 모듈을 분리하는 것 만으로도 스파게티 코드 발생이 훨씬 완화된다고 보고있다.
- 도메인 모델 끼리의 참조는 99%가 필요해서 하는 참조이고, 단일 책임 원칙을 잘 지키면서 개발한다면 비즈니스 레이어 내에서 어색한 참조가 발생하는 케이스도 거의 없다고 본다.
- 실용성 관점에서 가장 큰 단점은 비즈니스 모듈 간 상호 참조가 불가능하다는 것이다.
- 도메인 레이어 내에서 각 비즈니스 모델들은 다른 비즈니스 모델과 상호작용을 통해 비즈니스 로직을 만들어 내야 한다.
- 따라서 근본적으로 다른 모델과 상호 참조가 발생 할 수 밖에 없는데, 모듈이 나뉘어져 있으면 단방향 참조 밖에 할 수가 없다.
- 두 비즈니스 모듈을 합치지 않는 한, 비즈니스 로직을 도메인 레이어에 두는 것이 불가능해진다.
이렇게 비즈니스 모듈을 N개로 가져가는 경우, 상호 참조가 필요한 비즈니스 로직을 만들어 내기 위해 크게 2가지 패턴으로 갈린다.
- (Monolitic) API module server를 하나만 세팅하는 경우, 도메인 모델 간 상호 참조가 필요한 비즈니스 로직은 N개의 비즈니스 모듈에 모두 접근 가능한 API module 에 들어간다.
- 비즈니스 로직이 application layer에 들어가는건 구조는 오히려 이상하다. 안티패턴에 가깝다.
- ( #MSA ) API module server를 N개로 두는 경우, 상호 참조가 필요한 비즈니스 로직은 한쪽 서버에서 다른 서버에 API call 해서 DTO를 끌고 와서 이를 활용해 처리한다.
- presentation, external response DTO 중복이 발생한다.
- DTO는 도메인 모델이 아니기 때문에, 이를 다른 도메인 모델과 엮어서 처리해줄 Service가 생겨야 하고, 이런 과정이 비즈니스 로직 중복을 야기한다.
- 유지보수도 더 어려워진다. MSA 특성 상 API call로 서비스 아키텍처가 구성되게 되는데, 객체 참조는 IDE에서 알려주기 때문에 수정 포인트를 쉽게 찾을 수 있는 반면 API call은 수정 포인트 찾기가 조금 더 까다롭다.
- presentation, external response DTO 중복이 발생한다.
그렇다면 비즈니스 모듈은 언제 쪼개야 하는가?
- 두 비즈니스 모듈이 상호 참조 없이 완전히 격리될 수 있는 케이스는 모듈을 분리할 수 있다.
- 한 팀 내에서 이런 비즈니스는 잘 존재하지 않는다. (존재 한다 해도 소수의 특이 케이스)
- 도메인 모듈이 분리하는 것이 득이 더 큰 경우 자체가 소수의 특이 케이스인데, 그런 특이 케이스를 위해서 ‘도메인 모듈을 모두 분리해서 MSA로 가자‘는 원칙을 잡는 것은 득보다 실이 커보인다.
[!info] 즉, 보통 코드베이스가 너무 비대해져서 분리를 고려 할 때 비즈니스 단위로 모듈을 나누는 것을 검토하게 되나, 비즈니스는 패키지 단위로 나누어도 충분하고, 비즈니스와 관련 없는 것들을 별도 모듈로 나누는 것이 더 효과적이었다.
DIP로 pure한 domain module 강제하기
https://learn.microsoft.com/ko-kr/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/ddd-oriented-microservice#the-infrastructure-layer
application -> domain -> support방향으로 의존하는 것이 일반적인 layered arch의 흐름이지만, 위와 같이support -> domain방향으로 의존하면 business module을 pure하게 유지하도록 강제 할 수 있다. ( DIP )- data mapper는 support layer에서.
- business module에서는 interface만 가지고 있고, 런타임에 support layer의 구현체를 DI
관심 분리
관심 분리의 ‘힘’은 패키지 < 모듈 < 서버 순으로 크다.
- 강력한 관심 분리가 필요한 경우, 패키지를 나누는 것 만으로는 부족한 경우가 있다.
- 접근 제어에 한계가 있어서 두 도메인 사이의 상호 참조가 자유롭게 가능하기 때문이다.
- 패키지 단위로 관심 분리 했을 때, 코드 레벨에서 상호 참조가 불가능하도록 컨트롤 하는 것은 어렵다.
- 모듈은 패키지 보다 더 명확하나, 단방향 참조만 가능하다는 제약이 있다.
- 서버를 나누면 강력한 관심 분리를 만족 할 수 있다. (MSA)
- 도메인 간의 관심사를 서버 단위로 명시적으로 분리하기 때문에, 상호 참조로 인한 스파게티 코드를 예방 할 수 있다.
[!info] 도메인을 별도 모듈로 나누면 강력한 의존성 분리, 그로 인한 스파게티 코드 예방 차원의 장점이 있다고는 하나, 상기한 것 처럼 rest call로 인해 발생하는 단점이 분명히 존재한다. 단일 팀 규모의 시스템이라면 보통은 도메인 모듈은 1-2개로, 그 안에서 패키지로 나누어도 충분했다.
참고
- https://techblog.woowahan.com/2637/
- domain module을 세세하게 쪼개고 infra를 함께 넣었는데, 나는 이런 방식 보다는 infra layer를 별도 모듈로 분리하고 domain module을 하나로 두는 방식을 선호한다.
- plasma-benefit/posts/485 사례도 참고해볼만 하다.
- https://share.navercorp.com/neday2022/lecture/1427158
- core less 아키텍쳐의 장점과 그럼에도 코드 중복을 줄일 수 있는 방법에 대해 얘기하고 있다. (core가 아닌 supports)
- https://kwonnam.pe.kr/wiki/web/%EC%8B%A0%EA%B7%9C%EC%84%9C%EB%B9%84%EC%8A%A4
ecommerce-core혹은ecommerce-common형태의 여러 도메인 비즈니스 로직을 모아둔 공통 모듈을 만들면 절대로 안 된다. 라고 하고 있는데, 역시나 내가 경험이 부족한건지, 모아뒀을 때 보다 분리했을 때 득보다 실이 더 컸던 것 같다.- 어쩌면 내가 경험했던 비즈니스가 충분히 복잡하지 않아서, 또는 너무 세부적으로 다 쪼개두어서 그렇게 느꼈던 것 일지도 모르겠다. (극단적인 MSA로 인한 피로도)


