-
Notifications
You must be signed in to change notification settings - Fork 1
Spring Modulith 전환 작업 완료 및 DB Migration 완료 #127
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
Spring Modulith 전환 작업 완료 및 DB Migration 완료 #127
Conversation
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가 별다른 기능 없이 단순 서비스 클래스로의 위임만 하고 있어서 삭제하고 각각의 서비스를 컨트롤러에 바로 연결시켜주었습니다.
Getter를 통해 값을 가져와 엔티티 외부에서 값 비교를 하던 로직을 엔티티 내부에 메소드를 만들어 호출하는 tell, dont't ask 원칙을 지켰습니다.
- 엔티티 외부에서 필드를 꺼내(get) 비교하던 구조를 제거하고, 엔티티 스스로 판단하도록 도메인 메서드 추가 - isWriter(): 작성자 동일성 판단 - isDifferent(): 클럽명 변경 여부 판단 - isCreatedAfter(): 생성 시점 비교 - isWithinVotingPeriod(): 투표 가능시간 여부 판단 - 객체가 가진 데이터에 기반한 책임을 스스로 수행하도록 구조 개선 - Tell, Don't Ask 원칙 적용으로 응집도 높은 도메인 모델로 리팩터링
페이징을 위해 분리되어 있던 메소드를 QueryDSL을 활용해 메소드를 통합했습니다.
…refactor/1/SpringModulith
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로 변경하기 전에는 어떠한 영향도 없습니다.
|
Important Review skippedMore 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 ✨ Finishing touches
🧪 Generate unit tests (beta)
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.
Example instruction:
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. Comment |
Summary of ChangesHello @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
Using Gemini Code AssistThe 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
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 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
|
There was a problem hiding this 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로 아키텍처를 전환하는 대규모 리팩토링 작업을 성공적으로 진행하신 점이 인상적입니다. 모듈 간의 경계를 명확히 하고, 이벤트 기반 통신을 도입하여 시스템의 유지보수성과 확장성을 크게 향상시킨 점이 돋보입니다. 아키텍처 변경에 맞춰 문서화를 꼼꼼하게 업데이트한 점도 매우 훌륭합니다.
다만, 새로운 아키텍처 도입 과정에서 일부 기능의 구현이 마무리되지 않은 부분이 발견되었습니다. 특히 데이터의 정합성과 직결되는 삭제 로직이나 유효성 검증 로직이 누락된 경우가 있어, 이 부분을 보완하면 더욱 견고한 시스템이 될 것이라 생각합니다. 리뷰 코멘트에서 이러한 부분들을 상세히 제안드렸습니다.
| // 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 기준-) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| 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); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| clubManagementAPI.validateStaffClubMember(clubId, memberId); | ||
|
|
||
| Vote vote = ClubNoticeConverter.toVote(request, clubId); | ||
| //TODO: 데드라인이 현재 시간보다 이전인지, 시작시간이 데드라인보다 이전인지, 시작시간이 현재시간보다 이전인지 검증이 필요하지 않나 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| throw new AuthException(AuthErrorStatus.INVALID_CREDENTIALS, "이메일 또는 비밀번호가 일치하지 않습니다"); | ||
| } catch (Exception e) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AuthException 생성자에 예외 메시지를 중복으로 전달하고 있습니다. GeneralException은 AuthErrorStatus 열거형에서 이미 메시지를 가져오도록 설계되어 있으므로, 두 번째 인자는 불필요합니다. 코드를 더 간결하게 유지하고 메시지 관리를 일원화하기 위해 중복되는 메시지 인자를 제거하는 것이 좋습니다.
| throw new AuthException(AuthErrorStatus.INVALID_CREDENTIALS, "이메일 또는 비밀번호가 일치하지 않습니다"); | |
| } catch (Exception e) { | |
| throw new AuthException(AuthErrorStatus.INVALID_CREDENTIALS); |
| throw new AuthException(AuthErrorStatus.INTERNAL_SERVER_ERROR, "서버 내부 오류: 로그인 처리 중 오류가 발생했습니다"); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AuthException 생성자에 예외 메시지를 중복으로 전달하고 있습니다. GeneralException은 AuthErrorStatus 열거형에서 이미 메시지를 가져오도록 설계되어 있으므로, 두 번째 인자는 불필요합니다. 코드를 더 간결하게 유지하고 메시지 관리를 일원화하기 위해 중복되는 메시지 인자를 제거하는 것이 좋습니다.
| throw new AuthException(AuthErrorStatus.INTERNAL_SERVER_ERROR, "서버 내부 오류: 로그인 처리 중 오류가 발생했습니다"); | |
| } | |
| throw new AuthException(AuthErrorStatus.INTERNAL_SERVER_ERROR); |
🚀 변경사항
기존 Facade 아키텍쳐 구조에서 Spring Modulith를 사용해 진정한 도메인 분리 아키텍쳐를 구현하였습니다.
이렇게 전환함에 따라 DB 구조가 바뀌어 새로운 DB를 만들어 Migration 작업 완료했습니다.
🔗 관련 이슈
✅ 체크리스트
📝 특이사항
Spring Modulith 구조 변경에 따라 README.md를 업데이트 하였습니다.
.env 파일의 최신화가 반드시 필요합니다.