본문 바로가기

개발/서버 아키텍쳐

[강의 정리] 우아한모노리스(2) - 우아한테크세미나

간단한 코드 예제

위와 같은 흐름으로 커머스의 예를 들어본다. 

도메인 중심으로 응집도 높은 모듈 구성하기

  • 도메인 주도 설계의 관점으로 보인다.
  • 도메인은 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도 각각 내부에 가지면 된다. 개별의 데이터소스, 트랜잭션까지도 생각할 수 있다.