feat: all user group/#691#709
Conversation
There was a problem hiding this comment.
Code Review
This pull request implements a 'GLOBAL' group category that automatically includes all active users. Changes include database migrations to support ownerless groups, automatic membership upon email verification, and policy constraints to protect the global group from deletion or modification. Feedback identifies a risk of NonUniqueResultException in the repository layer and recommends refactoring validation logic to ensure regular groups cannot be updated to the global category.
| .orElseThrow(GroupNotFoundException::new); | ||
|
|
||
| int currentMemberCount = groupRepositoryPort.countMembers(command.groupId()); | ||
| policy.validateNotGlobal(group); |
There was a problem hiding this comment.
The current validation only prevents modifying an existing GLOBAL group. It is critical to also prevent regular groups from being updated to the GLOBAL category, as this would create unauthorized global groups with owners, potentially breaking system assumptions and security boundaries.
| policy.validateNotGlobal(group); | |
| policy.validateNotGlobal(group.category()); | |
| policy.validateNotGlobal(command.category()); |
| @EntityGraph(attributePaths = {"owner"}) | ||
| Optional<GroupJpaEntity> findByCategory(GroupCategory category); |
There was a problem hiding this comment.
The findByCategory method returns an Optional, which implies that only one group can exist per category. While this is correct for the GLOBAL category, other categories like TEAM or CLUB typically have multiple groups. Calling this method with such categories will result in a NonUniqueResultException at runtime. Consider renaming this to findGlobalGroup() or changing the return type to List<GroupJpaEntity> to avoid unexpected crashes. Additionally, adding a unique constraint on the category column for the GLOBAL value in the database migration would further ensure data integrity.
| public void validateNotGlobal(Group group) { | ||
| if (group.category() == GroupCategory.GLOBAL) { | ||
| throw new GroupAccessDeniedException(); | ||
| } | ||
| } |
There was a problem hiding this comment.
Refactoring validateNotGlobal to accept a GroupCategory instead of a Group object makes the policy more versatile. This allows you to validate both the current state of a group and the intended state during an update (e.g., preventing a group from being changed to the GLOBAL category).
| public void validateNotGlobal(Group group) { | |
| if (group.category() == GroupCategory.GLOBAL) { | |
| throw new GroupAccessDeniedException(); | |
| } | |
| } | |
| public void validateNotGlobal(GroupCategory category) { | |
| if (category == GroupCategory.GLOBAL) { | |
| throw new GroupAccessDeniedException(); | |
| } | |
| } |
| Group group = groupRepositoryPort.findById(command.groupId()) | ||
| .orElseThrow(GroupNotFoundException::new); | ||
|
|
||
| groupPolicy.validateNotGlobal(group); |
변경사항
GroupCategory에GLOBAL카테고리 추가GroupPolicy에validateNotGlobal()및canQuitGroup()내 GLOBAL 체크 추가VerifyEmailService에서findByCategory(GLOBAL)후addMember호출GroupJpaRepository에findByCategory,GroupPersistenceAdapter에addMember메서드 추가groups.fk_user_idnullable 전환, GLOBAL 그룹 레코드 삽입, 기존 ACTIVE 유저 전원 자동 가입관련 이슈
Closes #691
추가 컨텍스트
fk_user_id가NULL이므로 기존 NOT NULL 제약 제거가 필요했습니다.VerifyEmailService가GroupRepositoryPort를 의존하게 됐는데, 도메인 경계 침범 여지가 있어 이벤트 방식으로 분리할 수도 있지만 우선 단순하게 직접 호출로 처리했습니다.