Skip to content

Conversation

@MODUGGAGI
Copy link
Collaborator

@MODUGGAGI MODUGGAGI commented Nov 29, 2025

🚀 변경사항

기존 Facade 아키텍쳐 구조에서 Spring Modulith를 사용해 진정한 도메인 분리 아키텍쳐를 구현하였습니다.
이렇게 전환함에 따라 DB 구조가 바뀌어 새로운 DB를 만들어 Migration 작업 완료했습니다.

🔗 관련 이슈

✅ 체크리스트

  • 로컬에서 테스트 완료
  • 코드 리뷰 준비 완료

📝 특이사항

Spring Modulith 구조 변경에 따라 README.md를 업데이트 하였습니다.
.env 파일의 최신화가 반드시 필요합니다.

MODUGGAGI and others added 30 commits November 8, 2025 01:21
Spring Modulith로의 전환을 위해 다음 클래스들을 common 패키지 내부로 이동하였습니다.
1. apiPayload
2. config
3. global/s3
4. global/entity/baseEntity.java
이 클래스 구조는 확정이 아닌 임시로, 패키지 이동 작업의 단계 중 일부입니다.
Spring Modulith 의존성을 추가하였습니다.
Spring Modulith로의 전환을 위해 domain/book의 book 도메인 모듈들을 최상단 디렉토리로 이동시켰습니다.
Spring Modulith로의 전환을 위해 domain/bookStory의 bookStory 도메인 모듈들을 최상단 디렉토리로 이동시켰습니다.
Spring Modulith로의 전환을 위해 domain/category의 category 도메인 모듈들을 최상단 디렉토리로 이동시켰습니다.
Spring Modulith로의 전환을 위해 domain/club의 club 도메인 모듈들을 최상단 디렉토리로 이동시켰습니다.
Spring Modulith로의 전환을 위해 domain/member의 member 도메인 모듈들을 최상단 디렉토리로 이동시켰습니다.
Spring Modulith로의 전환을 위해 domain/notification의 notification 도메인 모듈들을 최상단 디렉토리로 이동시켰습니다.
@AuthenticationPrincipal 어노테이션을 대체해서 한번에 SecurityContextHolder로부터 현재 사용자의 ID를 추출해주는 어노테이션을 member 패키지 내부로 이동시켰습니다.
자신의 정보를 제공하기 위해서 사용되는 DTO를 이제 각 도메인 모듈내부로 이동시켰습니다.
이벤트 내용을 전달하기 위한 이벤트 객체를 해당 이벤트를 발행하는 도메인 모듈 내부로 이동시켰습니다.
각 모듈이 논리적으로 분리가 완료되었는지 Spring Modulith의 verify() 메소드를 통해 확인하는 테스트 코드를 작성했습니다.
기존의 OOQueryFacade와 같은 형식의 이름을 전부 OOAPI로 변경하고 해당 인터페이스를 Public API로 설정했습니다.
구현체는 내부에 internal 패키지로 이동하였습니다.
Spring Modulith에서 events와 jpa 의존성을 추가하였습니다.
책임이 광범위한 Club 모듈에서 클럽, 클럽카테고리, 클럽멤버, 한줄평에 집중하는 ClubManagement 모듈을 따로 분리합니다.
책임이 광범위한 Club 모듈에서 클럽 정기모임에 집중하는 ClubMeeting 모듈을 따로 분리합니다.
책임이 광범위한 Club 모듈에서 클럽 공지사항, 투표에 집중하는 ClubNotice 모듈을 따로 분리합니다.
Book 엔티티에서 BookStory 연관관계를 제거하였습니다.
BookAPI에 public API로써 Book을 생성하거나 이미 존재할 경우 해당 Book의 ID(ISBN)을 반환하는 메소드를 추가하였습니다.
BookStory 엔티티에서 Book의 연관관계를 제거하고 ID값만 String으로 저장하게끔 설정하였습니다.
기존 BookCommandFacade를 통해 Book 생성을 요청하였지만, 이제 public API인 BookAPI를 통해 Book 생성을 요청하고 BookId값만 String으로 반환받는 로직으로 수정하였습니다.
이제 Book 객체 자체가 아닌 Book의 Id 값만 저장하기에 파라미터를 변경하였습니다.
기존 패키지 구조는 모듈의 경계를 명확히 식별하기 어려웠습니다. 따라서 캡슐화 영역을 internal/ 패키지로 이동시켰습니다.
CommandFacade가 별다른 기능 없이 단순 서비스 클래스로의 연결만 하고 있어서 삭제하고 각각의 서비스를 컨트롤러에 바로 연결시켜주었습니다.
parameter의 들여쓰기를 알아보기 쉽게 변경하였습니다.
CommandFacade가 별다른 기능 없이 단순 서비스 클래스로의 위임만 하고 있어서 삭제하고 각각의 서비스를 컨트롤러에 바로 연결시켜주었습니다.
CommandFacade가 별다른 기능 없이 단순 서비스 클래스로의 위임만 하고 있어서 삭제하고 각각의 서비스를 컨트롤러에 바로 연결시켜주었습니다.
MODUGGAGI and others added 23 commits November 23, 2025 20:03
Getter를 통해 값을 가져와 엔티티 외부에서 값 비교를 하던 로직을 엔티티 내부에 메소드를 만들어 호출하는 tell, dont't ask 원칙을 지켰습니다.
- 엔티티 외부에서 필드를 꺼내(get) 비교하던 구조를 제거하고, 엔티티 스스로 판단하도록 도메인 메서드 추가
 - isWriter(): 작성자 동일성 판단
 - isDifferent(): 클럽명 변경 여부 판단
 - isCreatedAfter(): 생성 시점 비교
 - isWithinVotingPeriod(): 투표 가능시간 여부 판단
- 객체가 가진 데이터에 기반한 책임을 스스로 수행하도록 구조 개선
- Tell, Don't Ask 원칙 적용으로 응집도 높은 도메인 모델로 리팩터링
페이징을 위해 분리되어 있던 메소드를 QueryDSL을 활용해 메소드를 통합했습니다.
String으로 사용되던 tag를 ENUM을 사용하는 방식으로 변경하였습니다.
README.md의 내용을 업데이트 했습니다.
package-info에 Spring Modulith의 @ApplicationModule을 사용해서 의존성을 명시적으로 적어주었습니다.
Spring Modulith를 활용하여 UML을 문서화한 내용을 추가하였습니다.
이미지 경로를 올바르게 수정하였습니다.
이미지를 왼쪽 정렬하였습니다.
ERD 링크를 추가하였습니다.
authentication 모듈에서만 사용되던 JwtProperties를 authentication 모듈로 이동하였습니다.
연관관계가 제거됨에 따라 JoinColumn을 제거하고 일반 Column으로 수정하였습니다.
읽기 전용 필드를 제거하였습니다.
읽기 전용 필드가 제거됨에 따라 JPA의 메소드 네이밍을 통한 db 쿼리 생성 방식을 수정했습니다.
읽기 전용 필드가 제거됨에 따라 JPA의 메소드 네이밍을 통한 db 쿼리 생성 방식을 수정했습니다.
카테고리 종류를 일치시키고 이름을 더 명확하게 하였습니다.
기존 null로 처리되는 걸 SYSTEM으로 변경하였습니다.
DB 마이그레이션을 위해 flyway를 적용하였습니다.
DB 마이그레이션을 위한 DDL 스크립트를 작성하였습니다.
V1에서 테이블들을 생성하고
V2에서 유니크 제약, 외래키 제약을 추가하였습니다.
로컬 디비 사용을 위해 설정 파일들을 수정하였습니다.
application.yml에서 prod 설정을 db로 변경하기 전에는 어떠한 영향도 없습니다.
@MODUGGAGI MODUGGAGI self-assigned this Nov 29, 2025
@coderabbitai
Copy link

coderabbitai bot commented Nov 29, 2025

Important

Review skipped

More than 25% of the files skipped due to max files limit. The review is being skipped to prevent a low-quality review.

162 files out of 289 files are above the max files limit of 100. Please upgrade to Pro plan to get higher limits.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

📝 Customizable high-level summaries are now available in beta!

You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.

  • Provide your own instructions using the high_level_summary_instructions setting.
  • Format the summary however you like (bullet lists, tables, multi-section layouts, contributor stats, etc.).
  • Use high_level_summary_in_walkthrough to move the summary from the description to the walkthrough section.

Example instruction:

"Divide the high-level summary into five sections:

  1. 📝 Description — Summarize the main change in 50–60 words, explaining what was done.
  2. 📓 References — List relevant issues, discussions, documentation, or related PRs.
  3. 📦 Dependencies & Requirements — Mention any new/updated dependencies, environment variable changes, or configuration updates.
  4. 📊 Contributor Summary — Include a Markdown table showing contributions:
    | Contributor | Lines Added | Lines Removed | Files Changed |
  5. ✔️ Additional Notes — Add any extra reviewer context.
    Keep each section concise (under 200 words) and use bullet or numbered lists for clarity."

Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@MODUGGAGI MODUGGAGI merged commit 8c923bf into checkmo2025:develop Nov 29, 2025
1 check passed
@gemini-code-assist
Copy link

Summary of Changes

Hello @MODUGGAGI, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

이 Pull Request는 프로젝트의 핵심 아키텍처를 Facade 패턴에서 Spring Modulith 기반으로 전환하는 작업을 완료합니다. 이 변경은 모놀리식 환경 내에서 도메인 간의 명확한 분리를 강화하고, 모듈 간의 느슨한 결합을 촉진하며, 자동화된 검증을 통해 아키텍처 일관성을 유지하는 것을 목표로 합니다. 또한, 새로운 아키텍처에 맞춰 데이터베이스 구조를 업데이트하고 관련 문서들을 최신화했습니다.

Highlights

  • Spring Modulith 아키텍처 전환: 기존 Facade 아키텍처에서 Spring Modulith를 활용한 도메인 분리 아키텍처로 전환되었습니다. 이는 패키지 구조로 모듈 경계를 강제하고 자동화된 검증 테스트를 통해 위반 사항을 검사하는 방식으로 변경되었습니다.
  • DB Migration 완료: 아키텍처 전환에 따라 변경된 DB 구조를 반영하기 위한 DB Migration 작업이 완료되었습니다.
  • README.md 업데이트: 새로운 Spring Modulith 아키텍처를 설명하고, 기존 Facade 패턴의 회고 및 모듈 의존성 그래프를 포함하도록 README.md 파일이 업데이트되었습니다.
  • 의존성 업데이트: Spring Modulith 및 Flyway 관련 의존성이 build.gradle 파일에 추가되었으며, dotenv 라이브러리 사용이 활성화되었습니다.
  • Docker Compose 설정 변경: 개발 환경용 Docker Compose 파일(compose-dev.yml)에서 MySQL 서비스가 활성화되었습니다.
  • 모듈 분리 및 리팩토링: 인증(authentication), 책(book), 책 이야기(bookStory), 클럽 관리(clubManagement), 클럽 미팅(clubMeeting), 클럽 공지(clubNotice) 등 주요 도메인들이 각각의 모듈로 분리되고 리팩토링되었습니다. 각 모듈은 Public API, ExternalDTO, Event를 통해 통신하며, 내부 구현은 internal 패키지로 캡슐화되었습니다.
  • 공통 모듈 도입: 기존의 전역(global) 유틸리티 및 예외 처리 로직이 common 모듈로 통합되어 재사용성과 모듈 간 결합도를 낮추는 데 기여했습니다.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

Spring Modulith로 아키텍처를 전환하는 대규모 리팩토링 작업을 성공적으로 진행하신 점이 인상적입니다. 모듈 간의 경계를 명확히 하고, 이벤트 기반 통신을 도입하여 시스템의 유지보수성과 확장성을 크게 향상시킨 점이 돋보입니다. 아키텍처 변경에 맞춰 문서화를 꼼꼼하게 업데이트한 점도 매우 훌륭합니다.

다만, 새로운 아키텍처 도입 과정에서 일부 기능의 구현이 마무리되지 않은 부분이 발견되었습니다. 특히 데이터의 정합성과 직결되는 삭제 로직이나 유효성 검증 로직이 누락된 경우가 있어, 이 부분을 보완하면 더욱 견고한 시스템이 될 것이라 생각합니다. 리뷰 코멘트에서 이러한 부분들을 상세히 제안드렸습니다.

Comment on lines +83 to +88
// TODO: 클럽이 삭제될 때, 이벤트 발행
public void deleteClub(Long clubId, String memberId) {
// Club을 삭제함으로써 Cascade.REMOVE가 동작되어 ClubManagement 모듈 내 모든 엔티티(클럽 멤버, 책 추천, 클럽 카테고리) 제거
// Meeting을 삭제함으로써 Cascade.REMOVE가 동작되어 ClubMeeting 모듈 내 모든 엔티티(토픽, 팀, 팀 토픽, 멤터 팀, 한줄평) 제거
// Notice를 삭제함으로써 Cascade.REMOVE가 동작되어 ClubNotice 모듈 내 모든 엔티티(투표, 회원 투표) 제거 (단, 비즈니스 요구사항 변경에 따라 Notice와 Vote는 연관관계 수정되어야 함 -2025.11.12 기준-)
}

Choose a reason for hiding this comment

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

high

deleteClub 메소드가 비어있고 TODO 주석만 남아있습니다. 클럽 삭제는 관련된 다른 모듈(예: clubMeeting, clubNotice)의 데이터를 함께 처리해야 하는 중요한 작업입니다. 현재 구현이 누락되어 데이터 정합성 문제가 발생할 수 있습니다.

새로운 Modulith 아키텍처에 맞게 ClubDeletedEvent를 발행하여 다른 모듈들이 관련 데이터를 정리하도록 구현하는 것을 권장합니다.

Comment on lines +96 to +109
public void leaveClub(Long clubId, String memberId) {
clubManagementQueryService.validateClub(clubId);
ClubMember clubMember = clubMemberQueryService.validateClubMember(clubId, memberId);

// 2. 운영진(STAFF)은 탈퇴 불가
if (clubMember.isStaff()) {
throw new ClubManagementException(ClubManagementErrorStatus.CLUB_STAFF_CANNOT_LEAVE);
}

// 3. 탈퇴 처리
// TODO: 탈퇴 시 BLOCKED 상태로 변경하는 것으로 알고 있는데... 확인해보아야 함
// TODO: 추가적으로, 탈퇴 시 연관된 엔티티(BookReview, ClubMemberTeam, Topic)를 어떻게 처리할지 결정 필요(PM) -2025.11.14-
clubMemberRepository.delete(clubMember);
}

Choose a reason for hiding this comment

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

high

leaveClub 메소드에서 ClubMember 레코드를 바로 삭제하고 있습니다. TODO 주석에서도 언급되었듯이, 이는 다른 모듈에서 해당 회원을 참조하고 있을 경우 데이터 불일치를 유발할 수 있습니다.

단순 삭제보다는 회원의 상태를 BLOCKEDLEFT 등으로 변경하거나, ClubMemberLeftEvent와 같은 이벤트를 발행하여 다른 모듈에서 관련 데이터를 안전하게 처리(예: 익명화)하도록 하는 것이 더 안전한 방법입니다.

clubManagementAPI.validateStaffClubMember(clubId, memberId);

Vote vote = ClubNoticeConverter.toVote(request, clubId);
//TODO: 데드라인이 현재 시간보다 이전인지, 시작시간이 데드라인보다 이전인지, 시작시간이 현재시간보다 이전인지 검증이 필요하지 않나

Choose a reason for hiding this comment

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

high

TODO 주석에 언급된 것처럼, 투표 생성 시 시작 시간과 마감 시간에 대한 유효성 검증 로직이 누락되었습니다. 예를 들어, 마감 시간이 과거이거나 시작 시간보다 이른 경우 등 비정상적인 투표가 생성될 수 있습니다. 이는 버그로 이어질 수 있으므로 반드시 유효성 검증 로직을 추가해야 합니다.

Comment on lines +47 to 48
throw new AuthException(AuthErrorStatus.INVALID_CREDENTIALS, "이메일 또는 비밀번호가 일치하지 않습니다");
} catch (Exception e) {

Choose a reason for hiding this comment

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

medium

AuthException 생성자에 예외 메시지를 중복으로 전달하고 있습니다. GeneralExceptionAuthErrorStatus 열거형에서 이미 메시지를 가져오도록 설계되어 있으므로, 두 번째 인자는 불필요합니다. 코드를 더 간결하게 유지하고 메시지 관리를 일원화하기 위해 중복되는 메시지 인자를 제거하는 것이 좋습니다.

Suggested change
throw new AuthException(AuthErrorStatus.INVALID_CREDENTIALS, "이메일 또는 비밀번호가 일치하지 않습니다");
} catch (Exception e) {
throw new AuthException(AuthErrorStatus.INVALID_CREDENTIALS);

Comment on lines +50 to 51
throw new AuthException(AuthErrorStatus.INTERNAL_SERVER_ERROR, "서버 내부 오류: 로그인 처리 중 오류가 발생했습니다");
}

Choose a reason for hiding this comment

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

medium

AuthException 생성자에 예외 메시지를 중복으로 전달하고 있습니다. GeneralExceptionAuthErrorStatus 열거형에서 이미 메시지를 가져오도록 설계되어 있으므로, 두 번째 인자는 불필요합니다. 코드를 더 간결하게 유지하고 메시지 관리를 일원화하기 위해 중복되는 메시지 인자를 제거하는 것이 좋습니다.

Suggested change
throw new AuthException(AuthErrorStatus.INTERNAL_SERVER_ERROR, "서버 내부 오류: 로그인 처리 중 오류가 발생했습니다");
}
throw new AuthException(AuthErrorStatus.INTERNAL_SERVER_ERROR);

@zjhj0814 zjhj0814 self-assigned this Dec 11, 2025
@zjhj0814 zjhj0814 added the ♻️ refactor Refactor code label Dec 11, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

♻️ refactor Refactor code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[REFACTOR] spring-modulith 도입을 위한 패키지 구조 변경

2 participants