Conversation
📋 개요Walkthrough이 변경사항은 그룹 멤버 조회 기능을 추가하고, 역할 관리를 Long 기반 ID에서 RoleEntity 엔티티 기반으로 리팩토링하며, GroupId/UserId 값 객체를 제거하고 GroupMember 도메인 모델을 재설계합니다. Querydsl 설정이 추가되고 새로운 저장소 추상화가 도입됩니다. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
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 |
There was a problem hiding this comment.
Actionable comments posted: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/main/java/flipnote/group/adapter/out/persistence/GroupRoleRepositoryAdapter.java (1)
88-93:⚠️ Potential issue | 🔴 Critical
findByGroupIdAndRole이null을 반환할 경우 NPE 발생
findByGroupIdAndRole의 반환 타입이RoleEntity이므로 (Optional이 아님), 해당 역할이 존재하지 않을 때null이 반환될 수 있습니다. 92번 줄에서 null 체크 없이roleEntity.getId()를 호출하면NullPointerException이 발생합니다.제안 수정안
public boolean checkRole(Long userId, Long groupId, GroupMemberRole groupMemberRole) { RoleEntity roleEntity = groupRoleRepository.findByGroupIdAndRole(groupId, groupMemberRole); - - return groupMemberRepository.existsByUserIdAndGroupRoleId(userId, roleEntity.getId()); + if (roleEntity == null) { + return false; + } + return groupMemberRepository.existsByUserIdAndGroupRoleId(userId, roleEntity.getId()); }또는 repository 메서드 반환 타입을
Optional<RoleEntity>로 변경하는 것을 권장합니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/flipnote/group/adapter/out/persistence/GroupRoleRepositoryAdapter.java` around lines 88 - 93, checkRole currently calls groupRoleRepository.findByGroupIdAndRole which can return null (RoleEntity), then immediately calls roleEntity.getId() causing NPE; update checkRole (in GroupRoleRepositoryAdapter) to handle a missing role: call findByGroupIdAndRole and if it returns null either return false (no such role -> user can't have it) or change the repository signature to Optional<RoleEntity> and unwrap it safely (e.g., roleOpt.map(role->groupMemberRepository.existsByUserIdAndGroupRoleId(userId, role.getId())).orElse(false)); ensure references: checkRole, groupRoleRepository.findByGroupIdAndRole, RoleEntity, and groupMemberRepository.existsByUserIdAndGroupRoleId are used to locate the fix.
🧹 Nitpick comments (10)
src/main/java/flipnote/group/infrastructure/config/QuerydslConfig.java (2)
11-19:@PersistenceContext필드 주입 사용을 고려해 보세요 (선택 사항)현재 코드는 정상 동작합니다. Spring 컨테이너는
EntityManager주입 시 단순 인스턴스 대신 특수 프록시를 주입하며, Spring은SharedEntityManagerCreator타입의 프록시를 주입합니다. 컨테이너는 각EntityManager가 하나의 스레드에 한정되도록 보장하므로, 생성자 주입으로 받아도 스레드 안전성은 유지됩니다.다만
@PersistenceContext의 기본 영속성 컨텍스트 타입은PersistenceContextType.TRANSACTION이며,@PersistenceContext로 어노테이션을 붙이면 트랜잭션 영속성 컨텍스트를 사용함을 명시할 수 있습니다. 이 방식이 JPA 표준 관례에 더 부합하고 의도를 명확히 드러냅니다.♻️ `@PersistenceContext` 적용 예시
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import com.querydsl.jpa.impl.JPAQueryFactory; import jakarta.persistence.EntityManager; -import lombok.RequiredArgsConstructor; +import jakarta.persistence.PersistenceContext; `@Configuration` -@RequiredArgsConstructor public class QuerydslConfig { - private final EntityManager entityManager; + `@PersistenceContext` + private EntityManager entityManager; `@Bean` public JPAQueryFactory jpaQueryFactory() { return new JPAQueryFactory(entityManager); } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/flipnote/group/infrastructure/config/QuerydslConfig.java` around lines 11 - 19, Replace the constructor-injected final EntityManager with a field-injected persistence context: remove `@RequiredArgsConstructor` and the final EntityManager constructor parameter, add a non-final EntityManager field annotated with `@PersistenceContext` in class QuerydslConfig, and keep the jpaQueryFactory() method using that field so the bean uses a transaction-scoped persistence context as intended.
11-19:@PersistenceContext사용을 고려해 보세요 (선택 사항)현재
@RequiredArgsConstructor를 통한 생성자 주입도 완전히 정상 동작합니다. Spring Boot의 JPA 자동 설정이EntityManager를 트랜잭션 범위의 프록시(SharedEntityManagerCreator)로 등록하기 때문에 스레드 안전성에는 문제가 없습니다.
@PersistenceContext로 변경하는 것은 JPA 표준 방식이며, 다음과 같은 이점이 있습니다:
- JPA 포터빌리티: 표준 JPA 어노테이션으로 더 이식 가능한 코드
- 고급 기능 지원: 필요시
type=EXTENDED같은 영속성 컨텍스트 타입을 명시 가능- 명시적 의도 표현:
EntityManager가 컨테이너 관리 프록시임을 코드에서 명확히 드러냄♻️ `@PersistenceContext` 적용 예시
-import lombok.RequiredArgsConstructor; +import jakarta.persistence.PersistenceContext; `@Configuration` -@RequiredArgsConstructor public class QuerydslConfig { - private final EntityManager entityManager; + `@PersistenceContext` + private EntityManager entityManager; `@Bean` public JPAQueryFactory jpaQueryFactory() { return new JPAQueryFactory(entityManager); } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/flipnote/group/infrastructure/config/QuerydslConfig.java` around lines 11 - 19, Replace constructor-injected EntityManager with a container-managed one by annotating the EntityManager field with `@PersistenceContext` in QuerydslConfig: remove the final field and RequiredArgsConstructor usage, declare a non-final EntityManager annotated with `@PersistenceContext`, and keep the jpaQueryFactory() bean returning new JPAQueryFactory(entityManager) so the bean uses the JPA-standard, container-managed EntityManager proxy.src/main/java/flipnote/group/adapter/in/web/InvitationController.java (1)
1-18: 플레이스홀더 컨트롤러에 Spring 어노테이션이 누락되어 있습니다.향후 구현 시
@RestController,@RequestMapping등의 어노테이션이 필요합니다. 구현 전이라면 빈 클래스를 커밋하기보다는 이슈 트래커에서 관리하는 것이 깔끔할 수 있습니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/flipnote/group/adapter/in/web/InvitationController.java` around lines 1 - 18, InvitationController currently lacks Spring web annotations; either annotate the class with `@RestController` and a base `@RequestMapping` (e.g., "/invitations") and add appropriate imports so it becomes a valid Spring bean, or if you want to keep it as a placeholder remove the empty class from the commit and track the work in the issue tracker instead; locate the InvitationController class to apply the annotation change or delete it as described.src/main/java/flipnote/group/adapter/in/web/GroupController.java (1)
18-28:FindGroupMemberResponseDto와FindGroupMemberCommandimport가 이 컨트롤러에서 사용되지 않습니다.그룹 멤버 조회 엔드포인트가
MemberController에 구현되어 있다면, 이 import들은 불필요합니다. 실제로 이 컨트롤러에 해당 엔드포인트를 추가할 계획이 없다면 제거하는 것이 좋습니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/flipnote/group/adapter/in/web/GroupController.java` around lines 18 - 28, Remove the unused imports FindGroupMemberResponseDto and FindGroupMemberCommand from GroupController.java since there is no group-member endpoint in this controller (the member lookup is implemented in MemberController); locate the import lines referencing FindGroupMemberResponseDto and FindGroupMemberCommand and delete them so the file only imports types actually used (e.g., FindGroupResponseDto, FindGroupCommand, CreateGroupCommand, ChangeGroupCommand, DeleteGroupCommand, and their use-case interfaces).src/main/java/flipnote/group/api/dto/response/FindGroupMemberResponseDto.java (1)
8-14: API DTO가 도메인 모델MemberInfo를 직접 노출합니다.
FindGroupMemberResponseDto가 도메인 모델인MemberInfo를 API 응답에 직접 사용하고 있습니다. 이렇게 하면 도메인 모델 변경 시 API 계약이 의도치 않게 변경될 수 있습니다. API 전용 내부 record(예:MemberInfoResponse)를 정의하고 매핑하는 것이 계층 분리에 더 적합합니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/flipnote/group/api/dto/response/FindGroupMemberResponseDto.java` around lines 8 - 14, FindGroupMemberResponseDto currently exposes the domain model MemberInfo directly; instead create an API-only record (e.g., MemberInfoResponse) that contains only the fields needed by the API and update FindGroupMemberResponseDto to use List<MemberInfoResponse> for memberInfoList; in the static factory from(FindGroupMemberResult) map each domain MemberInfo from result.memberInfoList() to a MemberInfoResponse (using a converter method or constructor on MemberInfoResponse) and return the DTO with the mapped list to avoid leaking domain types in the API layer.src/main/java/flipnote/group/application/service/FindGroupMemberService.java (1)
28-28:existsUserInGroup이 void를 반환하면서 예외로 검증하는 패턴은 의도를 파악하기 어렵습니다.메서드 이름
existsUserInGroup은boolean반환을 암시하지만, 실제로는void를 반환하고 내부에서 예외를 던집니다. 호출부에서 반환값도 확인하지 않고 결과도 사용하지 않으므로, 메서드명을validateUserInGroup또는checkUserInGroup으로 변경하면 side-effect 기반 검증임을 명확히 전달할 수 있습니다.이 변경은
GroupMemberRepositoryPort인터페이스와 해당 구현체에도 적용되어야 합니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/flipnote/group/application/service/FindGroupMemberService.java` at line 28, The method existsUserInGroup currently returns void and signals invalid state via exceptions which is confusing given its name; rename existsUserInGroup to a side-effect-validating name (e.g., validateUserInGroup or checkUserInGroup) across the API and implementations, update the GroupMemberRepositoryPort interface and all concrete implementations to the new void method, and update the caller in FindGroupMemberService to invoke the new method name (replace groupMemberRepository.existsUserInGroup(...) with groupMemberRepository.validateUserInGroup(...) or chosen name) so the intent (validation via exception) is clear and consistent.src/main/java/flipnote/group/infrastructure/persistence/jpa/GroupMemberRepositoryRepository.java (1)
12-12:existsByUserIdAndGroupRoleId의 두 번째 파라미터명id가 모호합니다.Spring Data 쿼리 메서드의 파라미터명
id는 어떤 엔티티의 ID인지 불분명합니다.roleId또는groupRoleId로 변경하면 가독성이 향상됩니다.♻️ 수정 제안
- boolean existsByUserIdAndGroupRoleId(Long userId, Long id); + boolean existsByUserIdAndGroupRoleId(Long userId, Long groupRoleId);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/flipnote/group/infrastructure/persistence/jpa/GroupMemberRepositoryRepository.java` at line 12, Rename the ambiguous parameter `id` in the repository method existsByUserIdAndGroupRoleId to a descriptive name like `roleId` or `groupRoleId` in the GroupMemberRepositoryRepository interface and update any callers/implementations to use the new parameter name; ensure the method signature reads existsByUserIdAndGroupRoleId(Long userId, Long roleId) (or Long groupRoleId) so the intent is clear and compilation references are adjusted accordingly.src/main/java/flipnote/group/adapter/out/entity/GroupMemberEntity.java (1)
57-69: Javadoc의@param과@return태그를 업데이트하세요.
role파라미터가 추가되었지만 Javadoc에는 반영되지 않았고,@return태그는 설명 없이 비어있습니다.📝 수정 제안
/** * 멤버 생성 * `@param` groupId * `@param` userId + * `@param` role - * `@return` + * `@return` 생성된 GroupMemberEntity */🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/flipnote/group/adapter/out/entity/GroupMemberEntity.java` around lines 57 - 69, Update the Javadoc for GroupMemberEntity.create to reflect the current signature: add a `@param` tag for the new role parameter (RoleEntity role) describing its purpose, ensure `@param` tags for groupId and userId are meaningful, and replace the empty `@return` tag with a brief description like "the created GroupMemberEntity" so the documentation matches the create(Long groupId, Long userId, RoleEntity role) factory method.src/main/java/flipnote/group/adapter/out/persistence/GroupRoleRepositoryAdapter.java (1)
53-62:Collectors.toMap내에서save()호출은 부수효과(side-effect)로 적절하지 않습니다.Stream의 collector value mapper에서
groupRoleRepository.save()를 호출하는 것은 부수효과입니다. 순차 스트림에서는 동작하지만, 스트림 연산은 순서를 보장하지 않으며 향후.parallel()전환 시 예측 불가능한 동작이 발생할 수 있습니다. 명시적 반복문이나 각 역할을 먼저 저장한 후 Map으로 수집하는 방식이 더 안전합니다.♻️ 수정 제안
- Map<GroupMemberRole, RoleEntity> roleEntityByRole = - Arrays.stream(new GroupMemberRole[]{ - GroupMemberRole.OWNER, - GroupMemberRole.HEAD_MANAGER, - GroupMemberRole.MANAGER, - GroupMemberRole.MEMBER - }).collect(Collectors.toMap( - role -> role, - role -> groupRoleRepository.save(RoleEntity.create(groupId, role)) - )); + List<RoleEntity> savedRoles = Arrays.stream(GroupMemberRole.values()) + .map(role -> RoleEntity.create(groupId, role)) + .map(groupRoleRepository::save) + .toList(); + + Map<GroupMemberRole, RoleEntity> roleEntityByRole = savedRoles.stream() + .collect(Collectors.toMap(RoleEntity::getRole, entity -> entity));🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/flipnote/group/adapter/out/persistence/GroupRoleRepositoryAdapter.java` around lines 53 - 62, The collector is invoking groupRoleRepository.save(...) as a side-effect inside Collectors.toMap which is unsafe; change GroupRoleRepositoryAdapter so you first iterate the GroupMemberRole values (OWNER, HEAD_MANAGER, MANAGER, MEMBER), call groupRoleRepository.save(RoleEntity.create(groupId, role)) for each in an explicit loop or stream map-to-list, collect the saved RoleEntity results, then build the Map<GroupMemberRole, RoleEntity> (roleEntityByRole) by mapping each saved entity back to its GroupMemberRole; ensure all saves happen before creating the final map and reference groupRoleRepository.save, RoleEntity.create, GroupMemberRole and roleEntityByRole when locating the code to change.src/main/java/flipnote/group/adapter/out/persistence/GroupMemberRepositoryAdapter.java (1)
39-46:existsUserInGroup메서드명이 실제 동작과 불일치합니다.메서드명이
exists...로 시작하여boolean반환을 암시하지만, 실제로는void를 반환하고 예외를 던집니다. 또한IllegalArgumentException은 너무 범용적이어서 상위 레이어에서 적절한 에러 핸들링이 어렵습니다.♻️ 수정 제안
- 메서드명을
validateUserInGroup등으로 변경하여 검증 동작임을 명확히 하세요.- 커스텀 도메인 예외(예:
UserNotInGroupException)를 사용하면@ExceptionHandler에서 적절한 HTTP 상태 코드를 반환할 수 있습니다.- public void existsUserInGroup(Long groupId, Long userId) { + public void validateUserInGroup(Long groupId, Long userId) { boolean isMember = groupMemberRepository.existsByGroupIdAndUserId(groupId, userId); if(!isMember) { - throw new IllegalArgumentException("user not in Group"); + throw new UserNotInGroupException(groupId, userId); } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/flipnote/group/adapter/out/persistence/GroupMemberRepositoryAdapter.java` around lines 39 - 46, Rename and change the semantics of existsUserInGroup in GroupMemberRepositoryAdapter to a validation-style method (e.g., validateUserInGroup) rather than an existence-query: keep it void but throw a specific domain exception instead of IllegalArgumentException; create a custom UserNotInGroupException (or similar) and throw that when groupMemberRepository.existsByGroupIdAndUserId(groupId, userId) is false, and update all callers to use the new method name/exception so higher layers can handle it via `@ExceptionHandler`.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@src/main/java/flipnote/group/adapter/out/persistence/mapper/GroupMemberMapper.java`:
- Around line 47-53: The mapping in GroupMemberMapper that builds MemberInfo
uses entity.getRole().getRole() which can NPE if Role is null; update the map in
GroupMemberMapper (the stream mapping that calls MemberInfo.builder()) to
defensively handle a null role—either by checking entity.getRole() and using a
default (e.g., an Optional.empty/unknown role) or by skipping/throwing a clearer
exception—and ensure any behaviour aligns with callers like findAllByGroupId;
alternatively, document the not-null invariant for Role if you choose not to add
runtime guarding.
- Around line 24-35: The toDomain method in GroupMemberMapper is missing mapping
for userId, causing GroupMember.userId to be null; update
GroupMemberMapper.toDomain to set the userId on the returned GroupMember by
reading entity.getUserId() (i.e., add the .userId(entity.getUserId()) call to
the GroupMember.builder() chain so GroupMemberEntity.userId is propagated into
the GroupMember domain object).
In
`@src/main/java/flipnote/group/application/port/out/GroupMemberRepositoryPort.java`:
- Around line 5-9: The port GroupMemberRepositoryPort incorrectly depends on
adapter-layer RoleEntity; change its save signature to accept a domain type
(e.g., replace RoleEntity with a domain enum/value object such as
GroupMemberRole or use existing MemberInfo) and move conversion logic into the
adapter implementation (GroupMemberRepositoryAdapter) so the adapter maps
GroupMemberRole -> RoleEntity before persisting; update CreateGroupService to
pass the domain role type to GroupMemberRepositoryPort and ensure only domain
types appear in the port API.
In
`@src/main/java/flipnote/group/application/port/out/GroupRoleRepositoryPort.java`:
- Around line 3-7: The port GroupRoleRepositoryPort currently exposes the
persistence entity RoleEntity; change its method signature to use the domain
model GroupMemberRole (e.g., RoleMemberRole create(Long groupId) ->
GroupMemberRole create(Long groupId)) so the application port only depends on
domain types, and likewise update GroupMemberRepositoryPort.save(...) to accept
the domain GroupMemberRole (not RoleEntity). Implement entity<->domain mapping
inside the adapter layer (the adapter that implements GroupRoleRepositoryPort
and GroupMemberRepositoryPort should convert between RoleEntity and
GroupMemberRole when persisting/returning). Ensure GroupRepositoryPort remains
unchanged as it already uses the domain Group model.
In `@src/main/java/flipnote/group/application/service/CreateGroupService.java`:
- Line 6: CreateGroupService currently depends on adapter.out.entity.RoleEntity
which violates hexagonal boundaries; change CreateGroupService to use domain
types (e.g., GroupMemberRole or GroupMember) instead of RoleEntity, update
usages of GroupRoleRepositoryPort.create() to return a domain role type, and
modify GroupMemberRepositoryPort.save() to accept a domain GroupMember;
implement the entity↔domain mapping inside adapter implementations (converter
methods) so adapter classes handle RoleEntity <-> domain conversions and the
application service only references domain ports and models (symbols to change:
CreateGroupService, RoleEntity, GroupRoleRepositoryPort.create(),
GroupMemberRepositoryPort.save(), GroupMember/GroupMemberRole).
In
`@src/main/java/flipnote/group/application/service/FindGroupMemberService.java`:
- Around line 25-35: The findGroupMember method performs only reads
(existsUserInGroup and findMemberInfo) and must be annotated with
`@Transactional`(readOnly = true) to prevent TOCTOU inconsistencies and enable JPA
read optimizations; add the annotation to FindGroupMemberService.findGroupMember
(or the service class) and import
org.springframework.transaction.annotation.Transactional, and apply the same
change to FindGroupService.findGroup for consistency.
In `@src/main/java/flipnote/group/domain/model/member/GroupMember.java`:
- Line 3: GroupMember currently depends on JPA RoleEntity; change GroupMember to
use the domain enum GroupMemberRole (or a new domain value object carrying
roleId+roleType) instead of RoleEntity: remove the import
flipnote.group.adapter.out.entity.RoleEntity and replace any RoleEntity-typed
fields/constructors/getters in the GroupMember class with GroupMemberRole (or
the new VO) and update constructors/builders accordingly; then implement
conversion logic in the persistence adapter (the repository implementation that
maps between GroupMemberEntity/RoleEntity and GroupMember) to translate
RoleEntity <-> GroupMemberRole (or RoleVO) so the domain model stays
persistence-agnostic.
In
`@src/main/java/flipnote/group/infrastructure/persistence/jpa/GroupMemberRepositoryRepository.java`:
- Around line 10-11: Rename the interface GroupMemberRepositoryRepository to
GroupMemberRepository to match the project's naming convention, and update the
method signature existsByUserIdAndGroupRoleId(Long userId, Long id) by renaming
the second parameter from id to groupRoleId so it reads
existsByUserIdAndGroupRoleId(Long userId, Long groupRoleId); adjust any
imports/usages accordingly to the new interface name.
---
Outside diff comments:
In
`@src/main/java/flipnote/group/adapter/out/persistence/GroupRoleRepositoryAdapter.java`:
- Around line 88-93: checkRole currently calls
groupRoleRepository.findByGroupIdAndRole which can return null (RoleEntity),
then immediately calls roleEntity.getId() causing NPE; update checkRole (in
GroupRoleRepositoryAdapter) to handle a missing role: call findByGroupIdAndRole
and if it returns null either return false (no such role -> user can't have it)
or change the repository signature to Optional<RoleEntity> and unwrap it safely
(e.g.,
roleOpt.map(role->groupMemberRepository.existsByUserIdAndGroupRoleId(userId,
role.getId())).orElse(false)); ensure references: checkRole,
groupRoleRepository.findByGroupIdAndRole, RoleEntity, and
groupMemberRepository.existsByUserIdAndGroupRoleId are used to locate the fix.
---
Nitpick comments:
In `@src/main/java/flipnote/group/adapter/in/web/GroupController.java`:
- Around line 18-28: Remove the unused imports FindGroupMemberResponseDto and
FindGroupMemberCommand from GroupController.java since there is no group-member
endpoint in this controller (the member lookup is implemented in
MemberController); locate the import lines referencing
FindGroupMemberResponseDto and FindGroupMemberCommand and delete them so the
file only imports types actually used (e.g., FindGroupResponseDto,
FindGroupCommand, CreateGroupCommand, ChangeGroupCommand, DeleteGroupCommand,
and their use-case interfaces).
In `@src/main/java/flipnote/group/adapter/in/web/InvitationController.java`:
- Around line 1-18: InvitationController currently lacks Spring web annotations;
either annotate the class with `@RestController` and a base `@RequestMapping` (e.g.,
"/invitations") and add appropriate imports so it becomes a valid Spring bean,
or if you want to keep it as a placeholder remove the empty class from the
commit and track the work in the issue tracker instead; locate the
InvitationController class to apply the annotation change or delete it as
described.
In `@src/main/java/flipnote/group/adapter/out/entity/GroupMemberEntity.java`:
- Around line 57-69: Update the Javadoc for GroupMemberEntity.create to reflect
the current signature: add a `@param` tag for the new role parameter (RoleEntity
role) describing its purpose, ensure `@param` tags for groupId and userId are
meaningful, and replace the empty `@return` tag with a brief description like "the
created GroupMemberEntity" so the documentation matches the create(Long groupId,
Long userId, RoleEntity role) factory method.
In
`@src/main/java/flipnote/group/adapter/out/persistence/GroupMemberRepositoryAdapter.java`:
- Around line 39-46: Rename and change the semantics of existsUserInGroup in
GroupMemberRepositoryAdapter to a validation-style method (e.g.,
validateUserInGroup) rather than an existence-query: keep it void but throw a
specific domain exception instead of IllegalArgumentException; create a custom
UserNotInGroupException (or similar) and throw that when
groupMemberRepository.existsByGroupIdAndUserId(groupId, userId) is false, and
update all callers to use the new method name/exception so higher layers can
handle it via `@ExceptionHandler`.
In
`@src/main/java/flipnote/group/adapter/out/persistence/GroupRoleRepositoryAdapter.java`:
- Around line 53-62: The collector is invoking groupRoleRepository.save(...) as
a side-effect inside Collectors.toMap which is unsafe; change
GroupRoleRepositoryAdapter so you first iterate the GroupMemberRole values
(OWNER, HEAD_MANAGER, MANAGER, MEMBER), call
groupRoleRepository.save(RoleEntity.create(groupId, role)) for each in an
explicit loop or stream map-to-list, collect the saved RoleEntity results, then
build the Map<GroupMemberRole, RoleEntity> (roleEntityByRole) by mapping each
saved entity back to its GroupMemberRole; ensure all saves happen before
creating the final map and reference groupRoleRepository.save,
RoleEntity.create, GroupMemberRole and roleEntityByRole when locating the code
to change.
In
`@src/main/java/flipnote/group/api/dto/response/FindGroupMemberResponseDto.java`:
- Around line 8-14: FindGroupMemberResponseDto currently exposes the domain
model MemberInfo directly; instead create an API-only record (e.g.,
MemberInfoResponse) that contains only the fields needed by the API and update
FindGroupMemberResponseDto to use List<MemberInfoResponse> for memberInfoList;
in the static factory from(FindGroupMemberResult) map each domain MemberInfo
from result.memberInfoList() to a MemberInfoResponse (using a converter method
or constructor on MemberInfoResponse) and return the DTO with the mapped list to
avoid leaking domain types in the API layer.
In
`@src/main/java/flipnote/group/application/service/FindGroupMemberService.java`:
- Line 28: The method existsUserInGroup currently returns void and signals
invalid state via exceptions which is confusing given its name; rename
existsUserInGroup to a side-effect-validating name (e.g., validateUserInGroup or
checkUserInGroup) across the API and implementations, update the
GroupMemberRepositoryPort interface and all concrete implementations to the new
void method, and update the caller in FindGroupMemberService to invoke the new
method name (replace groupMemberRepository.existsUserInGroup(...) with
groupMemberRepository.validateUserInGroup(...) or chosen name) so the intent
(validation via exception) is clear and consistent.
In `@src/main/java/flipnote/group/infrastructure/config/QuerydslConfig.java`:
- Around line 11-19: Replace the constructor-injected final EntityManager with a
field-injected persistence context: remove `@RequiredArgsConstructor` and the
final EntityManager constructor parameter, add a non-final EntityManager field
annotated with `@PersistenceContext` in class QuerydslConfig, and keep the
jpaQueryFactory() method using that field so the bean uses a transaction-scoped
persistence context as intended.
- Around line 11-19: Replace constructor-injected EntityManager with a
container-managed one by annotating the EntityManager field with
`@PersistenceContext` in QuerydslConfig: remove the final field and
RequiredArgsConstructor usage, declare a non-final EntityManager annotated with
`@PersistenceContext`, and keep the jpaQueryFactory() bean returning new
JPAQueryFactory(entityManager) so the bean uses the JPA-standard,
container-managed EntityManager proxy.
In
`@src/main/java/flipnote/group/infrastructure/persistence/jpa/GroupMemberRepositoryRepository.java`:
- Line 12: Rename the ambiguous parameter `id` in the repository method
existsByUserIdAndGroupRoleId to a descriptive name like `roleId` or
`groupRoleId` in the GroupMemberRepositoryRepository interface and update any
callers/implementations to use the new parameter name; ensure the method
signature reads existsByUserIdAndGroupRoleId(Long userId, Long roleId) (or Long
groupRoleId) so the intent is clear and compilation references are adjusted
accordingly.
| public static GroupMember toDomain(GroupMemberEntity entity) { | ||
|
|
||
| if (entity == null) { | ||
| return null; | ||
| } | ||
|
|
||
| return GroupMember.builder() | ||
| .id(entity.getId()) | ||
| .groupId(entity.getGroupId()) | ||
| .role(entity.getRole()) | ||
| .build(); | ||
| } |
There was a problem hiding this comment.
toDomain()에서 userId 매핑이 누락되었습니다 — 도메인 객체의 userId가 항상 null이 됩니다.
GroupMemberEntity에는 userId 필드가 있고, GroupMember 도메인 모델에도 userId 필드가 있지만, 빌더에서 .userId(entity.getUserId()) 호출이 빠져 있습니다.
🐛 수정 제안
return GroupMember.builder()
.id(entity.getId())
.groupId(entity.getGroupId())
+ .userId(entity.getUserId())
.role(entity.getRole())
.build();📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| public static GroupMember toDomain(GroupMemberEntity entity) { | |
| if (entity == null) { | |
| return null; | |
| } | |
| return GroupMember.builder() | |
| .id(entity.getId()) | |
| .groupId(entity.getGroupId()) | |
| .role(entity.getRole()) | |
| .build(); | |
| } | |
| public static GroupMember toDomain(GroupMemberEntity entity) { | |
| if (entity == null) { | |
| return null; | |
| } | |
| return GroupMember.builder() | |
| .id(entity.getId()) | |
| .groupId(entity.getGroupId()) | |
| .userId(entity.getUserId()) | |
| .role(entity.getRole()) | |
| .build(); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@src/main/java/flipnote/group/adapter/out/persistence/mapper/GroupMemberMapper.java`
around lines 24 - 35, The toDomain method in GroupMemberMapper is missing
mapping for userId, causing GroupMember.userId to be null; update
GroupMemberMapper.toDomain to set the userId on the returned GroupMember by
reading entity.getUserId() (i.e., add the .userId(entity.getUserId()) call to
the GroupMember.builder() chain so GroupMemberEntity.userId is propagated into
the GroupMember domain object).
| return entities.stream() | ||
| .map(entity -> MemberInfo.builder() | ||
| .userId(entity.getUserId()) | ||
| .role(entity.getRole().getRole()) | ||
| .build() | ||
| ) | ||
| .toList(); |
There was a problem hiding this comment.
entity.getRole()이 null일 경우 NPE가 발생할 수 있습니다.
Line 50의 entity.getRole().getRole()는 role이 null이면 NullPointerException을 발생시킵니다. 현재 findAllByGroupId에서 join fetch를 사용하고 있지만, 이 mapper가 다른 경로에서 호출될 경우를 대비해 방어 코드를 추가하거나, role이 항상 not-null임을 전제로 한다면 해당 전제를 문서화하는 것이 좋습니다.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@src/main/java/flipnote/group/adapter/out/persistence/mapper/GroupMemberMapper.java`
around lines 47 - 53, The mapping in GroupMemberMapper that builds MemberInfo
uses entity.getRole().getRole() which can NPE if Role is null; update the map in
GroupMemberMapper (the stream mapping that calls MemberInfo.builder()) to
defensively handle a null role—either by checking entity.getRole() and using a
default (e.g., an Optional.empty/unknown role) or by skipping/throwing a clearer
exception—and ensure any behaviour aligns with callers like findAllByGroupId;
alternatively, document the not-null invariant for Role if you choose not to add
runtime guarding.
| import flipnote.group.adapter.out.entity.RoleEntity; | ||
| import flipnote.group.domain.model.member.MemberInfo; | ||
|
|
||
| public interface GroupMemberRepositoryPort { | ||
| void save(Long groupId, Long userId, Long roleId); | ||
| void save(Long groupId, Long userId, RoleEntity role); |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Port 인터페이스가 RoleEntity(adapter 계층)에 의존합니다 — Hexagonal Architecture 위반의 근본 원인입니다.
GroupMemberRepositoryPort는 application 계층의 port로서 domain 타입만 사용해야 합니다. RoleEntity를 domain 타입(예: GroupMemberRole enum 또는 새로운 도메인 값 객체)으로 교체하고, entity 변환 책임은 adapter 구현체(GroupMemberRepositoryAdapter)에 위임해야 합니다.
CreateGroupService에서 언급한 것과 동일한 문제의 근본 원인이 이 인터페이스 정의에 있습니다.
♻️ 수정 방향 제안
-import flipnote.group.adapter.out.entity.RoleEntity;
import flipnote.group.domain.model.member.MemberInfo;
+import flipnote.group.domain.model.member.GroupMemberRole;
public interface GroupMemberRepositoryPort {
- void save(Long groupId, Long userId, RoleEntity role);
+ void save(Long groupId, Long userId, GroupMemberRole role);
void existsUserInGroup(Long groupId, Long userId);
List<MemberInfo> findMemberInfo(Long groupId);
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@src/main/java/flipnote/group/application/port/out/GroupMemberRepositoryPort.java`
around lines 5 - 9, The port GroupMemberRepositoryPort incorrectly depends on
adapter-layer RoleEntity; change its save signature to accept a domain type
(e.g., replace RoleEntity with a domain enum/value object such as
GroupMemberRole or use existing MemberInfo) and move conversion logic into the
adapter implementation (GroupMemberRepositoryAdapter) so the adapter maps
GroupMemberRole -> RoleEntity before persisting; update CreateGroupService to
pass the domain role type to GroupMemberRepositoryPort and ensure only domain
types appear in the port API.
| import flipnote.group.adapter.out.entity.RoleEntity; | ||
| import flipnote.group.domain.model.member.GroupMemberRole; | ||
|
|
||
| public interface GroupRoleRepositoryPort { | ||
| Long create(Long groupId); | ||
| RoleEntity create(Long groupId); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n src/main/java/flipnote/group/application/port/out/GroupRoleRepositoryPort.javaRepository: FlipNoteTeam/FlipNote-Group
Length of output: 459
🏁 Script executed:
rg -n 'import flipnote\.group\.adapter\.' --glob '*/application/port/**'Repository: FlipNoteTeam/FlipNote-Group
Length of output: 219
🏁 Script executed:
fd -t f 'GroupMemberRole\.java' -o 'RoleEntity\.java'Repository: FlipNoteTeam/FlipNote-Group
Length of output: 222
🏁 Script executed:
# Find all imports of adapter layer within application/port directory
rg 'import flipnote\.group\.adapter\.' --type java -A 1 | head -50Repository: FlipNoteTeam/FlipNote-Group
Length of output: 4890
🏁 Script executed:
# Find RoleEntity definition to confirm it's a JPA entity
fd -t f 'RoleEntity.java'Repository: FlipNoteTeam/FlipNote-Group
Length of output: 134
🏁 Script executed:
# Check GroupMemberRole definition
fd -t f 'GroupMemberRole.java'Repository: FlipNoteTeam/FlipNote-Group
Length of output: 140
🏁 Script executed:
# Look for other repositories in application/port to check pattern consistency
fd -t f -name '*RepositoryPort.java' | head -10Repository: FlipNoteTeam/FlipNote-Group
Length of output: 301
🏁 Script executed:
cat -n src/main/java/flipnote/group/adapter/out/entity/RoleEntity.javaRepository: FlipNoteTeam/FlipNote-Group
Length of output: 1801
🏁 Script executed:
cat -n src/main/java/flipnote/group/domain/model/member/GroupMemberRole.javaRepository: FlipNoteTeam/FlipNote-Group
Length of output: 220
🏁 Script executed:
fd -t f -name '*RepositoryPort.java'Repository: FlipNoteTeam/FlipNote-Group
Length of output: 301
🏁 Script executed:
cat -n src/main/java/flipnote/group/application/port/out/GroupMemberRepositoryPort.javaRepository: FlipNoteTeam/FlipNote-Group
Length of output: 560
🏁 Script executed:
fd -t f -path '*/application/port/out/*RepositoryPort.java'Repository: FlipNoteTeam/FlipNote-Group
Length of output: 239
🏁 Script executed:
cat -n src/main/java/flipnote/group/application/port/out/GroupRepositoryPort.javaRepository: FlipNoteTeam/FlipNote-Group
Length of output: 376
🏁 Script executed:
# Check if there's a Role domain aggregate or value object
fd -t f -path '*/domain/model/*' -name '*Role*'Repository: FlipNoteTeam/FlipNote-Group
Length of output: 239
애플리케이션 포트에서 어댑터 레이어의 엔티티(RoleEntity)를 직접 참조하고 있습니다.
헥사고날 아키텍처에서 application.port.out 인터페이스는 도메인 모델만 참조해야 합니다. 현재 adapter.out.entity.RoleEntity(JPA 엔티티)를 반환 타입과 파라미터로 사용하면 애플리케이션 레이어가 영속성 어댑터에 의존하게 되어 의존성 방향이 역전됩니다.
이와 동일한 문제가 GroupMemberRepositoryPort에서도 발견됩니다(save() 메서드 파라미터). 대조적으로 GroupRepositoryPort는 도메인 모델(Group)을 올바르게 사용하고 있습니다.
도메인 모델(기존 GroupMemberRole 또는 Role 도메인 애그리거트)을 포트의 반환/파라미터 타입으로 사용하고, 어댑터에서만 엔티티↔도메인 변환을 처리하도록 리팩토링해야 합니다.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@src/main/java/flipnote/group/application/port/out/GroupRoleRepositoryPort.java`
around lines 3 - 7, The port GroupRoleRepositoryPort currently exposes the
persistence entity RoleEntity; change its method signature to use the domain
model GroupMemberRole (e.g., RoleMemberRole create(Long groupId) ->
GroupMemberRole create(Long groupId)) so the application port only depends on
domain types, and likewise update GroupMemberRepositoryPort.save(...) to accept
the domain GroupMemberRole (not RoleEntity). Implement entity<->domain mapping
inside the adapter layer (the adapter that implements GroupRoleRepositoryPort
and GroupMemberRepositoryPort should convert between RoleEntity and
GroupMemberRole when persisting/returning). Ensure GroupRepositoryPort remains
unchanged as it already uses the domain Group model.
| import org.springframework.stereotype.Service; | ||
| import org.springframework.transaction.annotation.Transactional; | ||
|
|
||
| import flipnote.group.adapter.out.entity.RoleEntity; |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Application 서비스가 Adapter 계층의 RoleEntity에 직접 의존합니다.
Hexagonal Architecture에서 application 서비스는 domain/port에만 의존해야 합니다. RoleEntity는 adapter.out.entity 패키지의 JPA 엔티티이므로, application 계층에서 직접 참조하면 의존성 방향이 역전됩니다.
GroupRoleRepositoryPort.create()가 domain 타입(예: GroupMemberRole 또는 새 도메인 모델)을 반환하고, GroupMemberRepositoryPort.save()도 domain 타입을 받도록 리팩토링하는 것이 좋습니다. Adapter 구현체에서 entity ↔ domain 변환을 담당해야 합니다.
이 문제는 GroupMemberRepositoryPort, GroupMember 도메인 모델 등 PR 전반에 영향을 미칩니다.
Also applies to: 41-44
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main/java/flipnote/group/application/service/CreateGroupService.java` at
line 6, CreateGroupService currently depends on adapter.out.entity.RoleEntity
which violates hexagonal boundaries; change CreateGroupService to use domain
types (e.g., GroupMemberRole or GroupMember) instead of RoleEntity, update
usages of GroupRoleRepositoryPort.create() to return a domain role type, and
modify GroupMemberRepositoryPort.save() to accept a domain GroupMember;
implement the entity↔domain mapping inside adapter implementations (converter
methods) so adapter classes handle RoleEntity <-> domain conversions and the
application service only references domain ports and models (symbols to change:
CreateGroupService, RoleEntity, GroupRoleRepositoryPort.create(),
GroupMemberRepositoryPort.save(), GroupMember/GroupMemberRole).
| @Override | ||
| public FindGroupMemberResult findGroupMember(FindGroupMemberCommand cmd) { | ||
|
|
||
| groupMemberRepository.existsUserInGroup(cmd.groupId(), cmd.userId()); | ||
|
|
||
| List<MemberInfo> memberInfoList = groupMemberRepository.findMemberInfo(cmd.groupId()); | ||
|
|
||
| //todo 멤버 정보에서 유저 아이디를 grpc를 통해 가져온 후 반환 | ||
|
|
||
| return new FindGroupMemberResult(memberInfoList); | ||
| } |
There was a problem hiding this comment.
읽기 전용 트랜잭션 어노테이션이 누락되었습니다.
findGroupMember는 existsUserInGroup과 findMemberInfo 두 개의 DB 조회를 수행합니다. @Transactional(readOnly = true) 없이는 두 호출 사이에 데이터 불일치가 발생할 수 있으며 (TOCTOU), JPA 더티 체킹 최적화 기회도 놓칩니다.
참고로, 기존 FindGroupService.findGroup()에도 동일한 패턴이 보이므로 함께 개선하는 것이 좋습니다.
♻️ 수정 제안
+import org.springframework.transaction.annotation.Transactional;
+
`@Service`
`@RequiredArgsConstructor`
public class FindGroupMemberService implements FindGroupMemberUseCase {
private final GroupMemberRepositoryPort groupMemberRepository;
`@Override`
+ `@Transactional`(readOnly = true)
public FindGroupMemberResult findGroupMember(FindGroupMemberCommand cmd) {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| @Override | |
| public FindGroupMemberResult findGroupMember(FindGroupMemberCommand cmd) { | |
| groupMemberRepository.existsUserInGroup(cmd.groupId(), cmd.userId()); | |
| List<MemberInfo> memberInfoList = groupMemberRepository.findMemberInfo(cmd.groupId()); | |
| //todo 멤버 정보에서 유저 아이디를 grpc를 통해 가져온 후 반환 | |
| return new FindGroupMemberResult(memberInfoList); | |
| } | |
| `@Override` | |
| `@Transactional`(readOnly = true) | |
| public FindGroupMemberResult findGroupMember(FindGroupMemberCommand cmd) { | |
| groupMemberRepository.existsUserInGroup(cmd.groupId(), cmd.userId()); | |
| List<MemberInfo> memberInfoList = groupMemberRepository.findMemberInfo(cmd.groupId()); | |
| //todo 멤버 정보에서 유저 아이디를 grpc를 통해 가져온 후 반환 | |
| return new FindGroupMemberResult(memberInfoList); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main/java/flipnote/group/application/service/FindGroupMemberService.java`
around lines 25 - 35, The findGroupMember method performs only reads
(existsUserInGroup and findMemberInfo) and must be annotated with
`@Transactional`(readOnly = true) to prevent TOCTOU inconsistencies and enable JPA
read optimizations; add the annotation to FindGroupMemberService.findGroupMember
(or the service class) and import
org.springframework.transaction.annotation.Transactional, and apply the same
change to FindGroupService.findGroup for consistency.
|
|
||
| import flipnote.group.domain.model.group.GroupId; | ||
| import flipnote.group.domain.model.user.UserId; | ||
| import flipnote.group.adapter.out.entity.RoleEntity; |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
도메인 모델 GroupMember가 JPA 엔티티 RoleEntity에 의존합니다.
도메인 모델은 인프라/persistence 계층과 독립적이어야 합니다. RoleEntity는 @Entity, @ManyToOne 등 JPA 어노테이션이 포함된 영속성 엔티티이므로, 도메인 모델에서 직접 참조하면 도메인이 JPA에 종속됩니다.
GroupMemberRepositoryPort 리뷰 코멘트에서 제안한 대로 RoleEntity 대신 GroupMemberRole enum(이미 도메인 패키지에 존재)을 사용하거나, 필요 시 role ID + role type을 포함하는 도메인 값 객체를 도입하는 것이 적합합니다.
Also applies to: 15-15
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main/java/flipnote/group/domain/model/member/GroupMember.java` at line 3,
GroupMember currently depends on JPA RoleEntity; change GroupMember to use the
domain enum GroupMemberRole (or a new domain value object carrying
roleId+roleType) instead of RoleEntity: remove the import
flipnote.group.adapter.out.entity.RoleEntity and replace any RoleEntity-typed
fields/constructors/getters in the GroupMember class with GroupMemberRole (or
the new VO) and update constructors/builders accordingly; then implement
conversion logic in the persistence adapter (the repository implementation that
maps between GroupMemberEntity/RoleEntity and GroupMember) to translate
RoleEntity <-> GroupMemberRole (or RoleVO) so the domain model stays
persistence-agnostic.
| public interface GroupMemberRepositoryRepository | ||
| extends JpaRepository<GroupMemberEntity, Long> { |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check if the old GroupMemberRepository interface still exists alongside the new one
fd "GroupMemberRepository.java" --type f
echo "---"
# Check which repository is used by the adapter
rg -n "GroupMemberRepository" --type java -g '!**/GroupMemberRepositoryRepository.java'Repository: FlipNoteTeam/FlipNote-Group
Length of output: 2063
🏁 Script executed:
#!/bin/bash
# Read the GroupMemberRepositoryRepository file
cat -n src/main/java/flipnote/group/infrastructure/persistence/jpa/GroupMemberRepositoryRepository.java
echo "---"
# Check naming patterns for other JPA repositories
fd "Repository.java" --path "*/infrastructure/persistence/jpa/*" --type f | head -10Repository: FlipNoteTeam/FlipNote-Group
Length of output: 1128
🏁 Script executed:
#!/bin/bash
# Check naming patterns for other JPA repositories
find src/main/java/flipnote/group/infrastructure/persistence/jpa -name "*Repository*.java" -type f
echo "---"
# Show content of a few other repositories to check naming patterns
find src/main/java/flipnote/group/infrastructure/persistence/jpa -name "*Repository*.java" -type f | head -5 | while read f; do echo "=== $f ==="; head -15 "$f"; echo; doneRepository: FlipNoteTeam/FlipNote-Group
Length of output: 2230
GroupMemberRepositoryRepository 인터페이스 이름의 "Repository" 중복을 수정하세요.
프로젝트의 다른 JPA 저장소들은 GroupRepository, GroupRoleRepository, GroupRolePermissionRepository 등으로 명명되어 있습니다. GroupMemberRepositoryRepository는 이 명명 규칙에 맞지 않으며, 단순히 GroupMemberRepository로 변경해야 합니다.
추가로 12번 줄의 existsByUserIdAndGroupRoleId(Long userId, Long id) 메서드에서 두 번째 매개변수 이름이 id로 되어 있어 모호합니다. Spring Data 명명 규칙에 맞게 groupRoleId로 변경하는 것이 좋습니다.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@src/main/java/flipnote/group/infrastructure/persistence/jpa/GroupMemberRepositoryRepository.java`
around lines 10 - 11, Rename the interface GroupMemberRepositoryRepository to
GroupMemberRepository to match the project's naming convention, and update the
method signature existsByUserIdAndGroupRoleId(Long userId, Long id) by renaming
the second parameter from id to groupRoleId so it reads
existsByUserIdAndGroupRoleId(Long userId, Long groupRoleId); adjust any
imports/usages accordingly to the new interface name.
Summary by CodeRabbit
릴리스 노트
새로운 기능
예정
개선사항