Spring Boot] 멀티모듈 아키텍처 적용

말하는 감자·2025년 6월 16일

내일배움캠프

목록 보기
69/73
post-thumbnail

https://soso-hyeon.tistory.com/83

멀티모듈

멀티 모듈 프로젝트에서 하나의 레포지토리 안에 여러 모듈을 두고, 각 모듈에 대해 별도의 실행 파일(JAR)을 만들 수 있다.

이를 통해 각 모듈을 독립적으로 배포하거나 도커 컨테이너로 나눠서 배포하는 방식으로 운영할 수 있다.

목표

각각의 모듈(cs25-common, cs25-services, cs25-batches)은 별도의 JAR 파일로 빌드

이렇게 돼서 배치와 서비스를 분리하는것이 주 목적이다.
배치는 하루에 한번~두번 정도만 돌아가고, 돌아갈 때 리소스를 많이먹는다
서비스는 24시간 돌아가면서 ai 벡터 db인 크로마를 쓰기 때문에 기본 리소스가있다.
그래서 배치돌아가는 성능에 다른 상관없는 서비스들이 꽁으로 돌아가지않게 그냥 분리해버리려고한다.

저렇게 나누게되면 cs25-services, cs25-batches 는 서로서로가 내부에 대해서 알 필요가없음 있는지도모르게됨 (의도!!)

docker-compse.yml 최종형

배치배포하는 쪽에서는 common + batches 이미지 pull받고
써비스 하는 쪽에서는 common + services 이미지 pull받아서 쓰면되게끔

version: '3'
services:
  cs25-common:
    image: cs25-common:latest
    build:
      context: ./cs25-common
    ports:
      - "8081:8080"
  cs25-services:
    image: cs25-services:latest
    build:
      context: ./cs25-services
    ports:
      - "8082:8080"
    depends_on:
      - cs25-common
  cs25-batches:
    image: cs25-batches:latest
    build:
      context: ./cs25-batches
    ports:
      - "8083:8080"
    depends_on:
      - cs25-common






우리 프로젝트에서 적용할 모듈

공통 적용에 프로메테우스 추가
global같은 경우는 batch까지 적용되는 것만 남겨서 common 형식으로 빠지고,
jwt나 로그인 인증 필터 이런것들은 도메인→서비스 패키지 이름 바꿔서 안으로 넣으면될듯



Docker 이미지 2개 생성해야 함 + Dockerfile 2개 생성

  • cs25-common: 공통 모듈, 다른 서비스와 배치에서 공유되는 코드 (이미지 생성x)
  • cs25-service: 서비스 모듈 (비즈니스 로직)
  • cs25-batch: 배치 모듈 (일괄 처리 작업)



바뀔 프로메테우스yml

global:
  scrape_interval: 15s
  evaluation_interval: 15s

alerting:
  alertmanagers:
    - static_configs:
        - targets:

scrape_configs:
  - job_name: 'prometheus'
    static_configs:
      - targets: [ 'prometheus:9090' ]

  - job_name: 'cs25-service'
    metrics_path: '/actuator/prometheus'
    static_configs:
      - targets: [ 'cs25-service:8080' ]

  - job_name: 'cs25-batch'
    metrics_path: '/actuator/prometheus'
    static_configs:
      - targets: [ 'cs25-batch:8080' ]



전역에서 적용될 설정들

  1. 프로메테우스
  2. 전역 예외처리
  3. 응답 값 일관화
  4. JPA 레포지토리

구조는 아래처럼 설정해야한다

├── cs25-common       # 공통 모듈 (예: 유틸리티, 예외 처리 등)
│   └── build.gradle      # 공통 모듈의 Gradle 설정
├── cs25-services     # 서비스 모듈 (예: 비즈니스 로직)
│   └── build.gradle       # 서비스 모듈의 Gradle 설정
├── cs25-batches      # 배치 모듈 (예: 일괄 처리 작업)
│   └── build.gradle       # 배치 모듈의 Gradle 설정
└── build.gradle           # 상위 프로젝트의 Gradle 설정 (모든 모듈)

루트에 있던 src를 각 모듈에 맞는위치로 나눠서 넣어줬따
그리고 src 삭~ 제~ 속이 시원하다 ^^



적용해보기

root파일의 setting.gradle






common

  • 도커 이미지로 안올라감
  • service, batch 모듈이 common 의존성 주입 받아야함.

build.gradle

큰 설정은 필요xxx

plugins {
    id 'java'
    id 'org.springframework.boot' version '3.5.0'
    id 'io.spring.dependency-management' version '1.1.7'
}

ext {
    set('queryDslVersion', "5.0.0")
}

dependencies {

    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-validation'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    runtimeOnly 'com.mysql:mysql-connector-j'
    implementation 'org.springframework.boot:spring-boot-starter-data-redis'
    implementation 'jakarta.mail:jakarta.mail-api:2.1.0'

    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    //queryDSL
    implementation "com.querydsl:querydsl-jpa:${queryDslVersion}:jakarta"
    annotationProcessor "com.querydsl:querydsl-apt:${queryDslVersion}:jakarta"
    annotationProcessor "jakarta.annotation:jakarta.annotation-api"
    annotationProcessor "jakarta.persistence:jakarta.persistence-api"

    testImplementation 'org.springframework.boot:spring-boot-starter-test'

    //Monitoring
    implementation 'io.micrometer:micrometer-registry-prometheus'
    implementation 'org.springframework.boot:spring-boot-starter-actuator'
}

settings.gradle 꼭 삭제하게!!!!






service ( , batch)

  • 도커 이미지로 올라감
  • common 의존성 주입 받아야함.

plugins {
    id 'java'
    id 'org.springframework.boot' version '3.5.0'
    id 'io.spring.dependency-management' version '1.1.7'
}

ext {
    set('queryDslVersion', "5.0.0")
}

dependencies {
    implementation project(':cs25-common')

    implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
    implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server'
    implementation 'org.springframework.boot:spring-boot-starter-security'
    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'

	...
}

bootJar {
    enabled = true
}
jar {
    enabled = false
}

bootJar, jar 파일 설정값을 추가해줘야하고
implementation project(':cs25-common') 의존 받아야할 대상 추가도해줘야함

settings.gradle 꼭 삭제하게!!!!






Common에 있는 Entity, Repository 주입받기

주입 받아야할 대상 (service, batch 모듈에서 Config 파일 추가가 필요함!!)

@Configuration// 공통 모듈의 entity, repository, component를 인식하기 위한 스캔 설정
@EntityScan(basePackages = "com.example.cs25common.global")
@EnableJpaRepositories(basePackages = "com.example.cs25common.global")
@ComponentScan(basePackages = {
    "com.example.cs25batch",          // 자기 자신
    "com.example.cs25common"            // 공통 모듈
})
public class JPAConfig {
}

이런식으로 추가 해준다.
어디의 엔티티/레포지토리 받을것인지, 컴포넌트 스캔범위를 어디로 할것인지 설정해줘야함

참고!!!

@Configuration// 공통 모듈의 entity, repository, component를 인식하기 위한 스캔 설정
@EntityScan(basePackages = {
    "com.example.cs25common.global",
    "com.example.cs25service.domain"
})
@EnableJpaRepositories(basePackages = {
    "com.example.cs25common.global",
    "com.example.cs25service.domain"
})
@ComponentScan(basePackages = {
    "com.example.cs25service",          // 자기 자신
    "com.example.cs25common"            // 공통 모듈
})
public class JPAConfig {
    // 추가적인 JPA 설정이 필요하면 여기에 추가
}

이렇게 여러개 봐야하는 경우도 쓸 수있음






컴포즈에 올라가는 모듈들 dockerfile 적어주기


모듈 안에다가 적어줘야한다!!

application.properties 도 모듈마다 작성해줘야함 !!






프로메테우스 대상추가

global:
  scrape_interval: 15s
  evaluation_interval: 15s

alerting:
  alertmanagers:
    - static_configs:
        - targets:

scrape_configs:
  - job_name: 'prometheus'
    static_configs:
      - targets: [ 'prometheus:9090' ]

  - job_name: 'cs25-service'
    metrics_path: '/actuator/prometheus'
    static_configs:
      - targets: [ 'cs25-service:8080' ]

  - job_name: 'cs25-batch'
    metrics_path: '/actuator/prometheus'
    static_configs:
      - targets: [ 'cs25-batch:8080' ]

츄츄가 츄츄가 츄가츄가
'/actuator/prometheus' 여기서 같이볼 수 있게 path 설정도 같이해준다






최종 docker-compose.yml

services:

  cs25-service:
    container_name: cs25-service
    build:
      context: .
      dockerfile: cs25-service/Dockerfile
    depends_on:
      - mysql
      - redis
      - chroma
    ports:
      - "8080:8080"
    networks:
      - monitoring

  cs25-batch:
    container_name: cs25-batch
    build:
      context: .
      dockerfile: cs25-batch/Dockerfile
    depends_on:
      - mysql
      - redis
    ports:
      - "8081:8080"
    networks:
      - monitoring

  mysql:
    container_name: mysql
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_PASSWORD}
      MYSQL_DATABASE: cs25
    ports:
      - "3306:3306"
    volumes:
      - mysql-data:/var/lib/mysql
    networks:
      - monitoring

  redis:
    container_name: redis
    image: redis:7.2
    ports:
      - "6379:6379"
    volumes:
      - redis-data:/data
    networks:
      - monitoring

  chroma:
    container_name: chroma
    image: ghcr.io/chroma-core/chroma
    ports:
      - "8000:8000"
    restart: unless-stopped
    volumes:
      - chroma-data:/data
    networks:
      - monitoring

  jenkins:
    container_name: jenkins
    image: jenkins/jenkins:lts
    user: root
    ports:
      - "9000:8080"
      - "50000:50000"
    volumes:
      - jenkins_home:/var/jenkins_home
      - /var/run/docker.sock:/var/run/docker.sock
    restart: always
    networks:
      - monitoring

  prometheus:
    image: prom/prometheus
    container_name: prometheus
    volumes:
      - ./prometheus:/etc/prometheus
    ports:
      - "9090:9090"
    networks:
      - monitoring

  grafana:
    image: grafana/grafana
    container_name: grafana
    ports:
      - "3000:3000"
    volumes:
      - grafana-data:/var/lib/grafana
    depends_on:
      - prometheus
    networks:
      - monitoring

volumes:
  chroma-data:
  grafana-data:
  jenkins_home:
  mysql-data:
  redis-data:

networks:
  monitoring:

상당히 길어졌다..

배치는 이제 aws 람다로 올라가니깐 나중에 받아올 이미지 골라서 받아오면 되겠지??? 배포는 나중에또 팀원들이랑 상의해봐야한다

그리고 튜터님과 1차 MVP 시연을 준비하면서 팀원들과 배포 형식을 상의해봤다.

기존

개선

ㅍ프론트는 정적리소스가 많다보니 클라우드 프론트 기능써서 보내버리고 이렇게 멀티모듈로 프로젝트를 나눠서
배치는 람다, 백엔드는 EC2로 올라갈 것 같다.
서브넷도 그림은 퍼블릭에 EC2두개 올라간걸로 보이는데 전부 PRIVATE으로 돌릴 것 같음

튜터님이 문제없어보인다고 도장도찍어주심 쾅쾅

하.. 이제 팀원들이 수정하던 코드들을 모아서 이쪽에 얹어줘야한다....
이게 정말 고민이 많다.
common 쪽에서 거의 디비도 같이 저장하고있어서 모듈을 하나더 나눠서 db로 할 것인지 !!! 쓰으읍

profile
대충 데굴데굴 굴러가는 개발?자

6개의 댓글

comment-user-thumbnail
2025년 6월 16일

츄츄가 츄츄가 츄가츄가
할 것들이 정말 많네요. 오늘도 잘 보고 갑니다. 🫡

1개의 답글
comment-user-thumbnail
2025년 6월 17일

지.렸.다

1개의 답글
comment-user-thumbnail
2025년 6월 18일

뭐하냐 삼성전자, LG전자, SK텔레콤, KT, LG유플러스, 네이버, 카카오, 쿠팡, 넷마블, 엔씨소프트, 넥슨, 컴투스, 스마일게이트, 토스, KB국민은행, 신한은행, 우리은행, 하나은행, 마켓컬리, 배달의민족, 당근마켓, 뱅크샐러드, 크래프톤.. 이 언니 안 데려가고..

1개의 답글