[쇼핑몰 프로젝트] 멀티모듈 도입기 -2편

대원·2024년 12월 23일

쇼핑몰

목록 보기
5/13
post-thumbnail

배경

이전 편에 이어서 멀티 모듈 도입과 관련된 글을 작성한다.
처음 구상한 멀티모듈은 어떻게 빈과 관련된 의존성들을 해결해주어서 스프링부트 어플리케이션 자체는 띄웠으나 아래와 같은 문제를 해결해야했다.

  1. 모듈 이름 및 역할 명확화
  2. Entry-Point 다변화 대비 설계 개선
  3. 특정 모듈 의존성 정리 및 Spring 의존 제거
    • common 모듈
  4. Infra와 External 통합 여부 검토

위 문제점들을 해결하기 앞서 배달의 민족 기술 블로그의 하단 글을 중점적으로 참고했음을 먼저 밝힌다.

해당 내용을 간략히 요약하면 다음과 같다.

  • 모듈은 독립적인 의미를 갖는다.
  • 모듈은 어떠한 추상화 정도에 대한 계층을 갖고 있다.
  • 계층간 의존관계에 대한 규칙이 있다.
  1. 독립 모듈 계층
    • 시스템과 무관하게 어디서나 사용가능한 라이브러리 성격의 모듈
    • 자체로서 독립적이다.
    • 토이프로젝트 사이즈에선 쓸 일이 크게 많지 않을 듯 하다.
  2. 공통 모듈 계층(System Core)
    • 하나의 프로젝트 내의 모든 모듈에서 사용되는 것들
    • Ex) Type, Utills
    • 프로젝트 내 어떤 모듈도 의존하지 않고 독립적이어 한다.
  3. 도메인 모듈 계층(System Domain)
    • 서비스 비즈니스를 모른다.
    • 하나의 모듈은 최대 하나의 Infrastructure에 대한 책임만을 지닌다.
      • Domain
      • Repository
      • Domain Service
  4. 내부 모듈 계층
    • 저장소, 도메인 외 시스템 필요 모듈
    • 애플리케이션, 도메인 비즈니스를 모른다.
  5. 애플리케이션 모듈 계층
    • batch, internal-api, external-api 등의 모듈이 존재

참고: 멀티모듈 설계 이야기 with Spring, Gradle | 우아한형제들 기술블로그

상단의 그림은 사실 실무에서 적용하는 내용들이기에 모든 Layer들을 토이프로젝트와 같이 작은 사이즈에 직접 대입해서 적용하기는 힘들 것이라고 판단했다.
(가령 독립 모듈계층과 같은 것들)

따라서 해당 계층 등은 개인의 판단 하에 제외하고 작성하였다.

배경부분에서 언급한 원인에 대해 세부적으로 고민했던 내용은 다음과 같다.

모듈 이름 및 역할 명확화

core라는 모듈은 너무 추상적으로 느껴졌다.
경우에 따라서 비즈니스 코어와 같은 이름으로 비춰질수도 있지만 좀 자의적이라고 생각했다.

지금은 개인이 하는 프로젝트이지만, 팀단위로 이뤄지는 작업일 경우엔 가독성이 무엇보다 중요할 것이기에 조금은 네이밍이 더 구체적인 방식으로 작성하는 편이 더 좋을 것이다.

따라서 위 글에서 참고한 것처럼 도메인을 기준으로 모듈을 작성하는 편이 좋은 방향이라고 느꼈다.

  • 변경내용
    • core 모듈 -> domain 모듈
      • 바로 밑에서 언급하지만, domain 모듈 내에서 domain-service, domain-rds, domain-redis와 같은 형식으로 모듈을 또 분리하였다.

Entry-point 다변화 대비 설계 개선

현재는 Web모듈만이 유일한 Entry-point이다.
그렇지만 실무 환경에서는 web만이 어플리케이션의 입구가 아닐 것이다.
가령 실 운영하는 서비스의 경우 대부분 admin(admin 전용 서버를 만든다면) , Batch , Comsumer 등이 필요할 것인데, 이 경우에 특정 모듈이 특정 기술에 의존해서는 안된다. 즉 의존성 다이어트가 필요하고 의존성 다이어트를 위해서는 domain(이전 core모듈)을 세분화 할 필요를 느꼈다.

주로 참고하였던 우아한형제들의 블로그 글에서 참고한 그림은 다음과 같다.

그림에서 보다시피 Entry Point(service Application, admin Application)에 따라서 각자 필요한 모듈에 직접적으로 접근해서 의존성을 제한할 수 있다.

즉, 본인의 프로젝트 경우엔 현재 core라는 모듈만이 존재하고 여러 인프라스트럭처에 접근하는 코드들이 하나의 모듈 내에 위치하고 있기 때문에 새로운 Entry Point가 구체적 기술 및 라이브러리에 쉽게 접근할 수 있게 되는 것이다.

가령, 특정 Entry -Point와 관련된 로직이 RDB와는 전혀 상관없는 로직을 요하는데 JPA와 같은 RDB에 접근할 수 있는 코드에 접근해서는 안될 것이다.

따라서 domain이라는 모듈 내에 domain-service, domain-rdb, domain-redis와 같은 모듈을 배치하였다.

  • 변경내용
    • 모듈 분리
    • domain-service
    • domain-rdb
    • domain-redis

특정 모듈의 의존성 정리 및 Spring 의존성 제거

계속해서 의존성과 관련된 내용이다.
common 모듈은 최대한 POJO만으로 유지하고 싶었다.
왜냐하면 common이라는 모듈 자체가 모든 모듈에서 쓰일 수 있는 Util성이기 때문에 특정 라이브러리 혹은 기술에 의존적이어서는 안된다고 생각했다.

현재 ErrorCode의 코드는 다음과 같다.
HttpStatus를 제거하여 HttpStatus와 관련된 의존성을 제거하거나 별도로 커스텀하게 작성이 필요하다.

package shoppingmall.common.exception;

import org.springframework.http.HttpStatus;

public interface ErrorCode {


    HttpStatus getHttpStatus();

    String getMessage();

    String name();

}

  • 변경(고려)사항
    • 해당 부분은 ErrorCode와 관련된 전반적 코드 검토가 필요하다.
    • 왜냐하면 외부 API에서 전달하는 ExternalErrorCode(임의로 네이밍 한 것)과 통합할 필요성도 있기에 추후 리팩토링을 하는게 더 좋다고 생각이 든다.
    • 일단 현재 지금 관점에서 중요한 것은 Spring에서 제공하는 HttpStatus의 의존성을 제거하는 것이지만, 추후 ErrorCode를 HttpStatus에의 의존성을 제거하는 방향으로 리팩토링을 하겠다.
      • CF) Spring의 HttpStatus는 Deprecated되었기 때문에 그 이유로 인해서도 커스텀하거나 제거하는 것이 더 좋은 방향성이라는 생각이 든다.

Infra와 External 통합 여부 검토

해당 모듈은 사실상 동일한 기능을 한다고 생각했다.
Infra모듈은 사실상 Redis와 관련된 Bean만을 정의했는데, 큰 의미가 없다고 느껴졌다.
왜냐면 domain-Redis라는 모듈을 생성하게 될 시에 Redis와 관련된 Bean과 설정을 해당 모듈에 위치시키면 된다고 생각했기 때문이다.

따라서 External을 없애고 Infra모듈로 통합하고 해당 모듈 내에 Toss Api를 호출하는 Feign Client를 넣어놓기로 결정했다.

  • 변경내용
    • infra, external 모듈 -> infra 모듈로 통합

개선한 멀티모듈 아키텍처

Application 모듈

  • web
  • batch(미구현)
  • comsumer(미구현)

entry-point들을 구성하는 모듈을 모아놓은 모듈이다.
내부에 web, batch(미구현) ,consumer(미구현)을 모아놓는다.

해당 모듈은 infra(redis or RDB)와 연결된 구체적 모듈에 의존하는것이 아니라 반드시 domain-service 모듈에 의존적이다.

따라서 Entry-point에서 비즈니스세부 기술에 접근할 수 없다.

domain

  • domain-rds
  • domain-redis
  • domain-service
    entry point는 domain -service에 의존하지만, 경우에 따라 특정 인프라스터럭처와 연결된 도메인(ex. domain-rds)를 직접 의존할 수도 있다.

domain service는 domain-rds, domain-redis에 의존하며 특정 라이브러리에 의존하지는 않는다.

comon

POJO로 구성된 공통 코드(ErrorCode 등 구성)한다.

infra

  • toss_payment 모듈

외부로 통신하는 Client 및 저장소 구성한다.
추후 외부 Infra 추가시 infra 하위에 모듈로 추가하도록 한다.

마무리 및 느낀점

현재 설계한 모듈구조 역시 당연히 고정적이지는 않다.
아마 토이프로젝트를 계속해서 만들어나가면서 구조역시 변화가 생길 수 있으리라 생각한다.
(추후 변경시 업데이트를 하겠다.)
소프트웨어라는 것이 애초에 상황과 맥락에 따라 계속해서 변화하는 것이기 때문에, 구조 역시 그러하다고 생각한다.

한 가지 크게 고민되었던 점은 멀티모듈을 적용하면서 나도 모르게 자꾸 아키텍처에 대한 고민을 하게 되었다는 것이다. 물론 아키텍처에 대한 고민 그 자체는 나쁜 것이 아니지만, 무의식적으로 모듈과 아키텍처를 혼동하는 것 같다는 느낌이 들기도 했다. 그러던 시점에 아래 영상을 보게 되었는데, 내가 무심코 실수할 수 있던 지점을 아주 적절하게 짚는 영상이었다.

멀티모듈 쓰지 말자

위 영상에서 주장하는 것 중 하나는 '멀티모듈 도입하는 것 그 자체는 특정 아키텍처와 직접적 연관관계는 없다'이다.
(도메인 성숙이 되야 모듈을 별도로 분리하는 것이 좋다라는 논지는 깊게 동의하지만 현재 실무에서 적용하는 것이 아닌, 토이프로젝트에 적용하는 것이기 때문에 일단은 참고정도 하였다.)

깊게 잘 생각해보면 멀티모듈 도입 -> 특정 아키텍처와는 직접적 연관이 절대로 없다.

현재는 그저 레이어드 아키텍처를 이용하되 멀티모듈로서 각자 모듈의 그 책임과 역할을 분리했다라고 보는게 타당할 것 같다.

profile
고민하고 공부하는 사람

0개의 댓글