Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: 모놀리틱 구조에서 멀티 모듈 구조로 전환 #116

Merged
merged 31 commits into from
Feb 18, 2024

Conversation

versatile0010
Copy link
Member

@versatile0010 versatile0010 commented Feb 1, 2024

멀티 모듈 구조로의 전환

오랜만에 PR 을 올립니다~~~

(모놀리틱 구조와 멀티모듈 구조에 관한 이해가 필요한 PR 입니다)


1.기존의 모놀리틱 구조의 문제점

기존의 대피로 백엔드는 모놀리틱 구조로 이루어져 있었는데요,
짧은 시간에 많은 요구사항을 시스템에 담으려하다 보니
기능 변경에 따라 영향도를 예측하기 어려워졌고
결국 관리 포인트가 늘어나게 되면서 확장성이 좋지 않은 것 같습니다.

또한 순환 참조나 코드 중복이 곳곳에 일어나면서 가독성도 떨어지고 로직을 한눈에 파악하기 힘들어져
유지보수성이 낮은 상황입니다.

( 중복된 코드 로직은 곳곳에 아직 남아있으며, 제거해야합니다. )

이러한 문제를 해결하고자 멀티 모듈 구조로의 전환을 시도했습니다.
멀티 모듈로의 전환이 성공하면, 앞으로 정말 깔끔하게 코드 베이스를 유지해나갈 수 있을 거에요~~


2. 왜 멀티모듈 구조로?

  1. 여러 책임이 몰려있던 하나의 시스템을, 각각의 책임을 가진 모듈들로 분리함으로써 큰 기능을 분리된 단위로 관리할 수 있게 해줍니다.
  2. 잘 모듈화 된 프로젝트는 전체 흐름을 파악하기 쉽고, 기능 추가/변경 시 일어나는 영향도를 쉽게 예상할 수 있어 유지보수하기에 좋아집니다.
  3. 코드 중복을 최소화 할 수 있습니다.
  4. 아직 작업을 하진 않았지만, 앞으로는 변경이 일어난 모듈에 대해서만 빌드함에 따라 배포 효율화를 할 수 있습니다. 기존의 모놀리틱 구조에서는 항상 전체 프로젝트를 빌드해야 하므로 배포 시간이 느린 문제가 있었습니다.
  5. 모듈 간 책임과 의존성이 시스템적으로 정의되었기 때문에, 개발자는 해당 제약사항을 준수해야만 기능 개발/변경을 할 수 있으며 이는 곧 시스템이 클린하게 유지될 수 있는 가능성을 높여주는 것 같습니다.
  6. 추후에 MSA 로 전환을 시도한다면, 모놀리틱 구조에서 MSA 로 바로 전환하는 것보다 멀티 모듈 구조에서 MSA 로 전환하는 것이 더 효율적일 수 있습니다.

멀티모듈 설계 이야기 이 글을 읽어보는 것도 좋을 것 같아요!


3. 대피로 멀티모듈 구조 설명

아래와 같이 멀티모듈을 설계해보았는데요,
개발하면서 조금씩 모듈 간 책임, 의존성을 조정하며 좋은 구조로 갈 수 있도록 같이 고민해보면 좋을 것 같습니다!

image
  • api 모듈

    • 사용자 요청을 받아서 처리하는 Controller Layer 와 핵심 비지니스 로직(Service Layer) 를 담당합니다.
  • core 모듈

    • db 설정, db connection 등 storage 에 관한 책임을 담당합니다. 엔티티들은 core 모듈에서 관리합니다. api 모듈에서 엔티티, db 관련 기능이 필요할 때 캡슐화된 기능을 제공함으로써 비지니스 로직에 집중할 수 있도록 합니다.
  • redis 모듈

    • redis 관련 설정을 담당합니다.
  • common 모듈

    • 우리 서비스에서는 크롤링이 핵심 기능인 만큼, 추후 기능이 확장될 여지가 많아보여서 모듈로 분리하였습니다. 웹 크롤링 및 데이터 수집 로직을 담당합니다.
  • auth 모듈

    • 프로젝트 내 인증 및 권한 관리 로직을 포함합니다.
  • common 모듈

    • 모든 모듈에서 사용할 수 있는 유틸리티 기능을 제공합니다. 순수 자바 코드로 이루어진, 의존성이 없는 로직만 존재해야 합니다. 모든 모듈에서 common 모듈을 의존하고 있는 만큼 common 모듈에 기능을 추가하는 것은 많은 고민을 해야하며, common 모듈이 무거워지지 않도록 최대한 주의해야 합니다..

이외의 작업

  1. 멀티 모듈 구조에서 기존의 배포 스크립트가 작동하도록 수정했습니다.
  2. Heap Memory OutOfMemory 가 발생해서 서버가 다운되면, heap dump 파일이 자동으로 생성되도록 jvm options 을 추가해두었습니다.

아마 처음 멀티모듈 구조를 경험하면 많이 불편하고 낯설 것 같은데요,,

하다가 막히는 점이나 궁금한 점이 생기면 바로 공유해주세요~~


  1. 로그인, 인증 관련 코드를 auth 모듈로 이관하는 작업은 해당 브랜치가 머지되면 진행해주세요!
  2. 그동안 멀티모듈 구조에서 테스트 코드 작성이 쉽도록, 테스트 기반을 구축하�고 배포 스크립트를 조금 더 효율적으로 수정하려고 합니다,,
  • gradle script 를 좀 다듬어야 하는데 제가 요즘 시간이 없어서 못보고 있었더니.. 이후 작업이 계속 지체되고 있는 것 같아서 먼저 PR 올린 뒤에 브랜치를 따로 파서 정리하려고 합니다 ㅜㅜㅜ

@versatile0010 versatile0010 added 🚀 feature 새로운 기능 구현 🐧 infra 인프라 관련 작업 labels Feb 1, 2024
@versatile0010 versatile0010 self-assigned this Feb 1, 2024
Copy link
Member

@nohy6630 nohy6630 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

올려준 글이랑 코드들 잘 읽었어 고생많아쓰!! 👍👍 모듈 구조도 괜찮은 것 같아!
메인 서비스 코드들을 어느 모듈에 둬야될지가 좀 고민이 드는 것 같아!

@@ -58,7 +59,8 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse

String email = jwtUtil.getEmail(token);
try {
Member member = memberService.findByEmail(email);
Member member = memberRepository.findByEmail(email)
.orElseThrow(NotFoundMemberException::new);;
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

auth모듈이 api모듈에 의존하지 않기 위해 memberService를 거치지 않고 memberRepository를 직접 사용하신거군요! 이 부분을 보다보니까 Service계층 코드들은 api같은 상위 모듈이 아니라 core나 common같은 비교적 하위 모듈에 배치하는건 어떨까요? Service 컴포넌트들은 메인 로직이라 다른 모듈들에서 의존을 어쩔수 없이 하게될 것같아요.
근데 또 이렇게 되면 공통 모듈만 너무 커져서 올려주신 우테코 게시글 처럼 공통 모듈만 너무 커지는 문제도 생길 것 같긴하네요. Service계층 코드들을 어디다 놔야할지 고민이 되는것같아요.... 어떻게 생각하시나요??

package com.numberone.backend.domain.user.service;

public class UserAuthService {
// todo: api 모듈의 token service 를 auth 모듈로 이관
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

}

tasks.bootJar {
enabled = false
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

올려주신 글에서 봤던 내용이네요. 메인 메소드가 있는 api모듈 말고는 모두 bootJar enabled값을 false로 해주는군요!

Comment on lines -9 to +8
@Service
@Component
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Comment on lines +98 to +114
private void convertAndSave(SaveDisasterRequest saveDisasterRequest) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
LocalDateTime dateTime = LocalDateTime.parse(saveDisasterRequest.getCreatedAt(), formatter);
Disaster savedDisaster =
disasterRepository.save(
Disaster.of(
saveDisasterRequest.getDisasterType(),
saveDisasterRequest.getLocation(),
saveDisasterRequest.getMsg(),
saveDisasterRequest.getDisasterNum(),
dateTime
)
);
log.info("재난 발생 이벤트 발행");
eventPublisher.publishEvent(DisasterEvent.of(savedDisaster)); // 신규 재난 발생 이벤트
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

disasterService의 크롤링관련 부분을 아예 crawler모듈쪽으로 옮겨서 crawler모듈과 api모듈간의 독립성을 신경써주셨군요! 좋은것같아요!👍

Comment on lines +2 to +7
include 'daepiro-api'
include 'daepiro-core'
include 'daepiro-common'
include 'daepiro-redis'
include 'daepiro-auth'
include 'daepiro-crawler'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

글에서 봤던 내용이네요. 멀티모듈 구조로 만들때 하위 프로젝트들을 루트 프로젝트의 settings.grade에서 모두 include해주는거군요!


@SpringBootApplication
@ServletComponentScan
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ServletComponentScan 어노테이션이 어디에 쓰이는건가용? 구글링해봤는데 저희 프로젝트에서는 쓰이는 곳을 잘 못찾겠네요!

Comment on lines 37 to 68
- name: get Current Time
run: echo "CURRENT_TIME=$(date +'%Y-%m-%d %H:%M:%S')" >> $GITHUB_ENV

- name: 🔔 Send Slack Message when deploy started
uses: 8398a7/action-slack@v3
with:
status: custom
custom_payload: |
{
"attachments": [
{
"title": "대피로 백엔드 배포를 시작합니다. 👻",
"pretext": "Daepiro backend is deploying...",
"fields": [
{
"title": "Author 💻",
"value": "${{ github.actor }}",
"short": true
},
{
"title": "Deploy time 🕚",
"value": "${{ env.CURRENT_TIME }}",
"short": true
}
]
}
]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
if: always()

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Comment on lines 77 to 100
mkdir -p ./daepiro-api/src/main/resources
cd ./daepiro-api/src/main/resources
touch ./application.yml
echo "${{ secrets.PROPERTIES_PROD }}" | base64 --decode > ./application.yml
ls -la
shell: bash

- name: 🐧 create service-account.json
run: |
cd ./src/main/resources
cd ./daepiro-api/src/main/resources
touch ./service-account.json
echo "${{ secrets.FCM }}" | base64 --decode > ./service-account.json
ls -la
shell: bash

- name: Build with jib
run: |
./gradlew jib -x test \
./gradlew :daepiro-api:jib -x test \
-Djib.to.auth.username=${{ secrets.DOCKER_USERNAME }} \
-Djib.to.auth.password=${{ secrets.DOCKER_PASSWORD }} \
-Djib.to.image="${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_IMAGE }}:latest"
env:
EC2_PUBLIC_IP: ${{ secrets.EC2_PUBLIC_IP }}
JMX_PORT: ${{ secrets.JMX_PORT }}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

main 메서드가 api모듈에 있기 때문에 api모듈 경로에서 빌드를 해주는거군요!

@versatile0010 versatile0010 merged commit 460a9fe into dev Feb 18, 2024
1 check passed
@versatile0010 versatile0010 deleted the feat/multi-module branch February 24, 2024 06:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🚀 feature 새로운 기능 구현 🐧 infra 인프라 관련 작업
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants