간단한 코드 예제
위와 같은 흐름으로 커머스의 예를 들어본다.
도메인 중심으로 응집도 높은 모듈 구성하기
- 도메인 주도 설계의 관점으로 보인다.
- 도메인은 SW가 해결하고자 하는 문제. 서비스 영역이라고도 하고.
- 결국 이 시스템은 고객이 상품을 보고, 주문하고, 배송할 수 있는 기능을 제공해야 한다.
- 그 경계를 따라서 모듈을 구성할 것이다.
도메인이라는 것을 생각할 때, 함께 생각하면 좋은 도구 중에 하나가 DDD에서 언급하는 전략적 설계방법중 하나인 바운디드 컨텍스트다. 도메인의 경계를 찾아갈 수 있는 도움을 준다.
모듈화에서 쓸 수 있는 기법이 2가지 정도 있다.
계층 구조로 모듈화
- 가장 흔하게 볼 수 있는 구조
- 계층을 기준으로 모듈화를 하기 시작하면, 시스템이 커지면 계층 내부의 복잡도가 올라간다.
- 새로운 기능이 들어오면 모든 계층에 걸쳐서 변화가 일어난다.(Controller, Service, Repository)
- 고칠게 많다.
도메인 지향 모듈화 - 그러나 이것도 내부는 계층적이다
- 복잡해지기 시작하면, 도메인 지향 모듈화로 바꿀 수 있다.
- 도메인을 기반으로 상위 경계를 치고, 그 내부를 계층의 구조로 모듈화를 한다.
- 이렇게 구성하면 조금 더 자율적이고 독립적으로 모듈을 키워나갈 수있다.
- 각각 독립적으로 돌아갈 수 있다.
관심사에 따른 모듈화
- 관심사는 크게 2가지 부류가 있다.
- 수직적 관심사, 수평적 관심사
- 수직적 관심사는 해결하고자하는 비즈니스 도메인 - 이전의 도메인 기반 구성과 그림적으로는 비슷
- 수평적 관심사는 비즈니스와는 무관하지만, 시스템을 만들기 위해 필요한 것 ( 공통 모듈이라고 부르는 것 ) - 비즈니스와 무관
- 수직적 관심사는 재사용성이 관점이 아니다. 어떻게 핵심적으로 비즈니스 로직을 구현하고 제공하는지.
- 수평적은 재사용성이 키워드. 애플리케이션 전체에 걸쳐서 사용될 수 있는 곳. 범용적. 비즈니스 관심사가 들어오면 안된다. 섞지말자. 떨어뜨리자.
그 다음단계는 의존성을 신경쓰자
절대 복잡한 관계를 형성하지 않도록 의존성을 관리
화살표가 한쪽으로만 흐르고 있다.
여기서 주문은 주문을 해야하니까, 상품을 알아야하니까, 상품을 알고 있다.
결제가 완료되면 배송을 요청해야한다. 주문이 배송을 알아야 할 것 같다. 그런데? 지금은 배송이 주문을 알고있다. 이 관계에서 까닥 잘못하면 주문과 배송이 서로가 서로를 알게될 것이다. 그 관계를 피해야 한다.
- 주문쪽 코드에서 ShippingDesk는 배송접수대라고 보면된다. 배송 등록, 시작해줘라는 요청. 내부 의존성으로 해결
- 공개 인터페이스로 모듈밖으로 공개
- 그걸 구현한 구현체가 배송에 있다.
- 즉, 배송은 스스로가 어떻게 접수받고 어떻게 배송할건지를 알고있다. 책임은 배송에 있는게 맞다. 구현에 있어서
- 하지만 의존성에 대한 관리를 역전시켰다.
- 경계를 찾아가고, 경계를 어떻게 서로 연결시키고 관리할건지를 깊이 있게 보고싶다면 조영호-오브젝트 책 추천, 책 전반에 걸쳐 응집, 협력, 역할에 대해 다루고 있다. 혹은 강의라도 들어보자.
그러면 이제 통합을 어떻게 할 것인가?
모듈과 모듈을 통합할때 전략적 방법 중 하나가 컨텍스트 매핑이다. 바운디드 컨텍스트 간에 어떻게 통합할 것인가? DDD에서 언급되는 전략적 설계 방법 중 하나.
코드는 영상에서 확인할 수 있다.
위와 같이 주문의 흐름이 생긴다. 여기서 보면 ShippingDesk, ShipmentOrderService의 의존성이 역전되었다는 것이 중요하다.
모듈사이 경계를 넘어오지 못하게 선긋기
모듈 경계는 정말 손쉽게 넘을 수 있다. 그러면 의도치 않은 결합이 일어난다. 기대했던 것들을 얻을 수 없다.
사람은 쉽게 넘어버린다. 컴파일러나 외부 의존성 관리도구의 도움을 받자.
모듈내 구현 세부사항을 밖에서 알아선 안된다
- 세부사항을 밖에서 알면 안된다. 안은 숨겨야 한다. 공개된 인터페이스만으로 대화.
- 지금 상품과 order를 보면 다 public이다.
- 주문은 상품이 만들어놓은 이 class를 얼마든지 가져다 쓸 수 있다.
- 자바 언어가 제공해 주는 가시성을 이용하자(private, protected, default).
- 노출할 것 하지 않을 것을 잘 숨겨주자.
- 이렇게하면 구체적인 구현 클래스가 외부로 드러나지 않게 될 것이다. 모듈을 잘 보호할 수 있다.
한 발 더 나아가자! 멀티프로젝트 ( 궁금했던 부분 )
gradle 빌드도구의 도움을 받자. 하나의 저장소 안에서 여러개의 프로젝트를 나눠서 관리할 수 있다. 각 모듈별로 프로젝트를 구성한다. 이 프로젝트 간의 관계 형성 시 서로간에는 전혀 알 수 없게 만들어 버린다.
컴파일타임에서는 상품은 다른 아이들을 알 수 없다. 하지만 모듈과 모듈이 서로 협력해야하므로, 그걸 entry Application main이 책임을 져준다.
아까 integrate에 구현 클래스들은 애플리케이션에 위치한다. 통합을 책임져준다.
각 모듈들은 내부 안에서 돌아야한다. 외부 의존성을 가지면 안 된다.
모듈 자율성을 지켜주는 컨텍스트 경계 구성하기
모자란 부분이 있어서 추가적인 방법을 제안.
내부는 숨기고, 외부로는 공개된 API만 가지고 소통해야 한다.
우리는 스프링을 이용하고 있는데, 스프링의 특징 중 하나가 IoC 컨테이너를 통해서 애플리케이션이 조립된다.
결국 모듈들이 각각 스프링 빈을 등록하고 협력한다.
이 빈의 특징이 전역 변수다. 하나의 컨테이너 안에는 모든 빈은 전역과 다르지 않다. 얼마든지 참고가능.
단일 컨테이너 안에서는 자율성이 무너짐.
- 자바9부터는 모듈에서 공개할 것과 하지 않을 것을 구분할 수 있다.
- 자바의 가시성과는 다른 스펙
- 배보다 배꼽이 더 크다.
- 스프링으로 해결할 수 있는 방법을 고민
스프링은 여러개의 Context를 계층구조로 가진 어플리케이션을 만들어 낼 수 있다.
스프링부트로 배운 사람은 생소할 수 있다. 자식은 필요하다면 부모를 찾아가서 빈을 얻을 수 있다.
하지만 어쨋든 Order, Shipment 와 같이 Context간 협력이 필요하다.
자식컨테이너는 부모에게 어떤걸 공개할지 말한다. 부모는 공개된 빈을 자연스럽게 자식에게 줄 수 있다.
코드는 https://github.com/arawn/building-modular-monoliths-using-spring 참고
스프링의 설정은 각 내부 컨텍스트에 있어야 한다. DataSource도 각각 내부에 가지면 된다. 개별의 데이터소스, 트랜잭션까지도 생각할 수 있다.
'개발 > 서버 아키텍쳐' 카테고리의 다른 글
너무좋은 멀티모듈예시 (0) | 2021.09.03 |
---|---|
https://techblog.woowahan.com/2637/ (0) | 2021.09.03 |
[강의 정리] 우아한모노리스(1) - 우아한테크세미나 (0) | 2021.08.25 |
서버 아키텍처에 대한 고민과 조언 정리 (0) | 2021.08.24 |