[항해99] 단기 스킬업: Redis를 활용한 대규모 트래픽 처리 1주차 회고(멀티 모듈 설계)
지금까지 여러 프로젝트를 진행하였지만, 실력이 비슷한 동료들과 함께하다보니 코드 구현을 올바르게 했는지 확신이 서지 않았다. 또한 항상 마감기한이 정해져있다보니 새로운 기술을 사용하기보다 항상 알고 익숙한 기술들만 사용했다.
즉, 백엔드 개발자로서 API 구현은 가능하지만 실무에서 요구하는 대규모 트래픽 처리, 성능 최적화, 멀티 모듈 등에 대한 경험을 쌓고 싶었다.
내가 원하는 프로그램을 찾던 중, 항해 99에서 진행하는 단기 스킬업 과정을 보게 되었다. 해당 과정은 다른 부트캠프나 프로그램과는 다르게, 학습을 위한 강의가 따로 제공되지 않는다. 대신, 매주 제공된 시나리오에 대한 과제를 풀면 현직자의 꼼꼼한 코드 리뷰를 받을 수 있게 된다. 또한, 슬랙 플랫폼을 통하여 현직자에게 질문 또한 자유롭게 가능하다.
현직자에게 코드 리뷰를 받는다는 점, 실무에서 발생 가능한 시나리오 중심의 실습으로 진행한다는 점이 내가 프로그램에 참여하려는 목표와 부합해보여서 단기 스킬업 과정 1기에 참여하게 되었다.
과제를 수행하면서 공부한 내용
멀티 모듈이란?
멀티 모듈이란, 하나의 프로젝트 내에서 여러 모듈로 나누어 구성된 아키텍처로, 모듈을 나누는 기준은 '도메인'이 될 수도 있으며 혹은 Layered Architecture 에서 설명되는 presentation, domain, intra 등에 해당되는 개념으로도 나눌 수 있다.
전자('도메인'을 기준으로 나눈 경우)는 각각의 모듈이 별도의 Application 형태로 구성될 가능성이 있다. 이런 클래스를 MSA(Microservie Architecture)라고 한다. MSA는 각 서비스가 독립적으로 배포되고 운영될 수 있도록 분리된 아키텍처로, 한 서비스의 장애가 다른 서비스에 영향을 주지 않게 끔 구성하는 데 노력이 필요하다.

단기 스킬업 1기 과정에서는 후자('Layer'를 기준으로 나눈 경우)의 멀티 모듈 아키텍처 형식으로 진행한다.
bootJar와 jar의 차이
bootJar를 true로 하면 /build/libs 경로에 모듈명.jar 파일이 생기게 되고, jar를 true로 했다면 /build/libs 경로에 모듈명-plain.jar 파일이 생기게 된다.
//excutable jar
Manifest-Version: 1.0
Main-Class: org.springframework.boot.loader.launch.JarLauncher
Start-Class: com.sickgyun.server.ServerApplication
Spring-Boot-Version: 3.2.1
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx
Spring-Boot-Layers-Index: BOOT-INF/layers.idx
Build-Jdk-Spec: 21
Implementation-Title: main-service
//plain-jar
Manifest-Version: 1.0
jar 파일에는 Manifest의 버전 정보만 담고 있기에, plain-jar 파일 단독으로 실행할 수 없다.
즉, 실제로 실행되는 모듈은 jar 파일만 필요로 하는 것이고, 바로 실행되지 않는 모듈은 plain-jar를 필요로 한다.
참고) https://elsboo.tistory.com/27
1주차 과제 제출과 피드백
테이블 설계 (제출)

- Theater과 Seat 테이블은 주어진 요구사항으로만 봤을 때에는 따로 테이블로 분리할 필요가 없지만, Theater는 상영관 위치, Seat은 좌석 등급과 같은 확장이 가능하다고 생각했기 때문에 따로 분리하였다.
- 현재 요구사항에는 모든 상영관이 A1~E5이지만 상영관별로 좌석의 행과 열의 정보가 달라질 수 있다고 생각해서, Seat 테이블에 예매 여부 속성을 넣었다. 이를 통해 클라이언트는 모든 좌석에 대한 정보를 알 수 있을 것이다.
테이블 설계 (피드백)

- 변수 네이밍에 대한 피드백을 받았다. 멘토님 말씀처럼 LocalDateTime 타입의 변수명은 불일치를 최소화하기 위해 at이라는 네이밍을 사용해야겠다!

- 예매라는 행위도 Seat의 특성 중 하나라고 생각했는데, 공유 자원과 행위가 결합되면 관리가 힘들어진다는 것을 새로 알게되었다! 예시로 상품-주문-결제 도메인을 들어주셔서 더 이해가 잘됐다 :)

- 성능 최적화는 성능 문제가 발생했을 때 진행해야한다는 말씀에 큰 깨달음을 얻었다! 조회 속도를 측정해보고, 성능 최적화를 통해 향상된 조회 속도가 데이터 중복 제거 및 무결성 유지보다 더 중요하다고 생각되면, 테이블 역정규화를 진행하도록 하자!
멀티 모듈 설계 (제출)

- multi-modules (루트 모듈)
- build.gradle에서 allprojets (또는 subprojects)라는 키워드로 sub modules들의 설정을 잡아준다.
- 루트 모듈은 하위 모듈을 관리하는 역할만 하기 때문에 src 폴더를 삭제한다.
- module-presentation (표현 모듈)
- 애플리케이션의 진입점으로, SpringApplication을 실행한다.
- 사용자의 요청을 받고, 응답을 보내주는 역할로, Controller 클래스와 RequestDto 클래스를 포함하고 있다.

- module-application (응용 모듈)
- 사용자가 요청한 기능을 처리하는 역할로, Service 클래스와 ResponseDto 클래스를 포함하고 있다.

- module-domain (도메인 모듈)
- 시스템이 제공할 도메인 규칙을 구현하는 역할로, Entity 클래스와 BaseEntity 클래스를 포함하고 있다.
- JPA 레포지토리를 의존하지만, 도메인 모듈에 repository 인터페이스를 따로 구현하지 않았다. 또한, POJO 모델 객체를 따로 구현하지 않고 JPA의 Entity 클래스를 도메인 모듈에 바로 구현하였다. JPA에서 다른 기술로 변경될 것이라고 예측하여 DIP를 적용하는 것은 오버 엔지니어링이라고 생각했기 때문이다.

- module-infra (인프라스트럭처 모듈)
- 데이터베이스와 같은 외부 시스템과의 연동을 처리하는 역할로, JpaRepository 클래스와 application.yml 파일을 포함하고 있다.

- module-core (코어 모듈)
- Convertor, Error-Response와 같이 특정 Layer에 종속되지 않는 기능을 처리한다.

멀티 모듈 설계 (피드백)

- implementation과 다르게 api는 추이 의존성을 허용하면서, application -> infra -> jpa 의존 방향이라면, application에서 jpa 의존성을 정의할 필요가 없다! 따라서 presentation 모듈, domain 모듈의 jpa 의존성을 삭제해야한다.

- Application이 실행되는 presentation 모듈은 bootJar를 true로, jar를 false로 하고, 그 외의 모듈은 bootJar를 false로 jar를 true로 설정해야한다.

- module prefix는 불필요하므로 수정해야한다.


- 멘토님 말씀처럼 현재 내 프로젝트의 core 모듈은 공통 역할이 아니지만 클래스들을 구현해놓은 것 같다. anti-pattern이므로 현재 core 모듈에 있는 response와 exception 관련된 코드는 application 모듈로 변경하고, core 모듈을 삭제하도록 해야겠다.
- 현재까지 5개의 layer로 나눔으로써의 단점은 DTO 와 Controller 가 분리된다는 측면에서는 같은 역할(사용자의 요청을 처리하고 반환)을 위한 클래스들이 별도의 모듈로 분리되기 때문에 응집도가 떨어진다는 것이다. 2주차를 진행하면서 모듈을 합치는 게 맞을지, 분리하는 게 맞을지 생각해봐야겠다.

- 현재 내 코드에서는 MoviesNowShowingDbDto가 infra 모듈에 위치함으로써 application 모듈이 infra 모듈에 의존하고, infra 모듈이 entity 클래스를 사용함으로써 domain 모듈을 사용하고 있다. 복잡한 관계(AS-IS)를 TO-BE 구조로 변경하기 위해 MoviesNowShowingDbDto를 domain 모듈이 위치시켜, domain 모듈이 infra 모듈에 의존하지 않도록 하자.
기타 피드백
- BaseEntity는 추상 클래스로 제공해도 좋을 것 같다.
- 서버사이드에서는 Enum을 영어로 관리해도 문제 없을 것 같다.
- API 이름에 list를 명시하는 것보다, 일반적으로 리소스 이름만으로 충분히 목록 반환이라는 의미를 전달할 수 있다. (e.g /movies)
- 그룹핑을 영화 이름을 기준으로 하는 것보다 영화 ID로 하는 것이 적합해보인다.
- new 키워드로 객체를 생성하는 방식은 직접 객체를 생성하기 때문에 테스트에서 의존성을 주입하기 어렵다. 빈으로 만들어서 제공하자.
- log.error 는 실무에서 알람(Alert)를 구성하기도 한다. 따라서 클라이언트 사이드 오류는 심각한 에러가 아니므로 info로 수정하자.
최종 설계본
테이블 설계

멀티 모듈 설계

느낀 점
멀티 모듈 설계를 처음 적용해봐서 과제를 혼자 진행할 때에는 많은 어려움이 있었다. 하지만 피드백을 바탕으로 모듈을 수정하면서 layer 5개로 모듈을 분리하는 것은 오버엔지니어링이구나를 깨달았고, 3개의 모듈로 구성하는 것이 가장 적합함을 느낄 수 있었다. 또한, table 설계 부분에서도 공유 자원과 행위를 하나의 테이블에 포함시키면 안된다는 것을 깨달았고, 코드적인 부분에서도 많은 도움을 얻을 수 있었다.
다음 주차에서는 성능 최적화에 관련된 과제를 진행하는데, 다음주에도 중요한 내용인만큼 열심히 해봐야겠다. :)