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] 그룹 정보 보기 API 구현 #58

Merged
merged 23 commits into from
Feb 17, 2025
Merged

[✨ feat] 그룹 정보 보기 API 구현 #58

merged 23 commits into from
Feb 17, 2025

Conversation

jsoonworld
Copy link
Member

@jsoonworld jsoonworld commented Feb 17, 2025

🚀 What’s this PR about?

  • 작업 내용 요약: 그룹 정보 보기 API를 구현하였습니다.
  • 핵심 작업: QueryDSL 도입 및 조회 기능 최적화

🛠️ What’s been done?

  • 주요 변경 사항:
    • 그룹 정보 보기 API 구현
    • Response DTO가 많아짐에 따라 API별 DTO를 구분하기 위해 패키지를 분리
    • QueryDSL 도입 (다음과 같은 이점 확보)
      • 쿼리 최적화 및 성능 개선
      • 동적 쿼리 작성의 편의성
      • 가독성과 유지보수성 향상

🧪 Testing Details

  • 테스트 코드 및 결과:
    • 그룹 정보 조회 API의 동작을 검증하는 테스트 코드를 작성하였습니다.
    • 멤버, 멤버그룹, 그룹 간의 연관 관계를 설정하는 코드를 가독성 있게 개선하였습니다.
    • 기존 테스트와 새로 추가된 테스트가 모두 정상적으로 동작함을 확인하였습니다.

👀 Checkpoints for Reviewers

  • 리뷰 시 확인할 사항:
    • DTO 네이밍에 신경을 많이 썼는데, 적절한지 확인 후 피드백 부탁드립니다.
    • 그룹 정보를 반환하는 과정에서 groupInfo가 API 네이밍과 중복될 가능성이 있어 groupSummary로 변경하였습니다.
    • QueryDSL 적용이 적절한지 검토해 주시면 감사하겠습니다.

🎯 Related Issues

QueryDSL을 활용하기 위해 `querydsl-jpa` 및 관련 의존성을 추가하였습니다.
- `querydsl-jpa:${queryDslVersion}:jakarta` 추가
- `querydsl-apt:${queryDslVersion}:jakarta` 추가
- `jakarta.annotation-api` 및 `jakarta.persistence-api` 추가
`GroupCreateResponse`의 패키지를 `org.noostak.group.dto.response`에서
`org.noostak.group.dto.response.create`로 이동하였습니다.
`GroupCreateInternalResponse`의 패키지를 `org.noostak.group.dto.response`에서
`org.noostak.group.dto.response.create`로 이동하였습니다.
`GroupInternalRetrieveResponse`의 패키지를 `org.noostak.group.dto.response`에서
`org.noostak.group.dto.response.retrieve`로 이동하였습니다.
`GroupRetrieveResponse`의 패키지를 `org.noostak.group.dto.response`에서
`org.noostak.group.dto.response.retrieve`로 이동하였습니다.
`GroupsRetrieveResponse`의 패키지를 `org.noostak.group.dto.response`에서
`org.noostak.group.dto.response.retrieve`로 이동하였습니다.
QueryDSL을 사용하기 위한 설정 클래스를 추가하였습니다.
- `JPAQueryFactory`를 빈으로 등록하여 의존성 주입 가능하도록 구성
- `EntityManager`를 활용하여 `JPAQueryFactory` 인스턴스 생성
- `/api/v1/groups/{groupId}/members` 엔드포인트 추가
- `GroupService`를 활용하여 그룹 정보 조회
- `SuccessResponse<GroupInfoResponse>`를 반환하도록 구현
- `GroupCreateInternalResponse`의 패키지를 `org.noostak.group.dto.response.create`로 변경
- `GroupsRetrieveResponse`의 패키지를 `org.noostak.group.dto.response.retrieve`로 변경
- 관련 패키지 변경 사항을 `GroupService`, `GroupRetrieveService` 및 관련 클래스에 반영
- `GroupInfoService` 인터페이스 추가
- `getGroupInfo(Long memberId, Long groupId)` 메서드 정의
- 그룹 정보를 조회하는 기능을 제공하기 위한 서비스 인터페이스
- `GroupInfoService` 구현체 `GroupInfoServiceImpl` 추가
- 그룹 정보를 조회하고, 그룹장 및 그룹원 정보를 포함하는 `GroupInfoResponse` 반환
- 그룹 및 멤버 정보 조회 시 존재하지 않을 경우 예외 처리 (`GroupException` 활용)
- `S3Service`를 이용하여 그룹 및 멤버 프로필 이미지 URL 조회
- `Groups` 및 `MemberGroups` 사용을 제거하고 `List<Group>`을 직접 활용하도록 변경
- `memberGroupRepository.findGroupsByMemberId(memberId)`를 통해 직접 `Group` 목록을 조회하도록 수정
- 그룹 목록 검증 로직을 `validateGroups(List<Group>)`로 단순화
- `GroupInternalRetrieveResponse`를 제거하고 `GroupRetrieveResponse`를 직접 사용하도록 변경
- DTO 패키지 변경 사항 반영 (`org.noostak.group.dto.response.retrieve`)
- `GroupInfoService`를 활용한 `getGroupInfo(Long memberId, Long groupId)` 메서드 추가
- DTO 패키지 변경 사항 반영 (`GroupCreateInternalResponse`, `GroupCreateResponse`, `GroupsRetrieveResponse`)
- `groupInfoService`를 의존성으로 추가하여 그룹 정보 조회 기능 제공
- `MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND, "멤버를 찾을 수 없습니다.")` 추가
- 그룹 내 특정 멤버가 존재하지 않을 경우 예외 처리를 위해 추가
- `GROUP_INFO(HttpStatus.OK, "그룹 정보가 성공적으로 조회되었습니다.")` 추가
- 그룹 정보 조회 성공 시 사용할 응답 코드 추가
- `myInfo`(요청한 멤버의 정보)와 `groupInfo`(그룹 요약 정보) 포함
- 정적 팩토리 메서드 `of(GroupMemberInfoResponse, GroupSummaryResponse)` 추가
- `memberName`(멤버 이름)과 `memberProfileImageUrl`(멤버 프로필 이미지 URL) 포함
- 정적 팩토리 메서드 `of(String memberName, String memberProfileImageUrl)` 추가
- 그룹장 정보(`groupHostInfo`), 그룹명(`groupName`), 그룹 프로필 이미지 URL(`groupProfileImageUrl`) 포함
- 그룹 멤버 수(`groupMemberCount`), 초대 코드(`groupInvitationCode`), 그룹 멤버 목록(`groupMemberInfo`) 포함
- 정적 팩토리 메서드 `of(...)` 추가
- `MemberGroupRepositoryCustom` 인터페이스 확장
- 기존 JPA 메서드 `findByMemberId(Long memberId)` 유지
- 그룹 ID로 멤버 조회 메서드 `findMembersByGroupId(Long groupId)` 추가
- 그룹 ID로 그룹장 조회 메서드 `findGroupHostByGroupId(Long groupId)` 추가
- 멤버 ID로 그룹 목록 조회 메서드 `findGroupsByMemberId(Long memberId)` 추가
- `findMembersByGroupId(Long groupId)` : 그룹 ID로 멤버 목록 조회
- `findGroupHostByGroupId(Long groupId)` : 그룹 ID로 그룹장 조회
- `findGroupsByMemberId(Long memberId)` : 멤버 ID로 그룹 목록 조회
- `JPAQueryFactory`를 활용한 QueryDSL 기반 쿼리 구현
- `MemberGroupRepositoryCustom` 인터페이스 구현 추가
- `findMembersByGroupId(Long groupId)` : 그룹 ID로 멤버 조회 메서드 추가
- `findGroupHostByGroupId(Long groupId)` : 그룹 ID로 그룹장 조회 메서드 추가
- `findGroupsByMemberId(Long memberId)` : 멤버 ID로 그룹 목록 조회 메서드 추가
- 중복된 `saveAll` 메서드 제거 및 `save` 로직 개선
@jsoonworld jsoonworld added ✨ feat Something isn't working 🐋 장순 🐋 This issue or pull request already exists labels Feb 17, 2025
@jsoonworld jsoonworld requested a review from 0-tae February 17, 2025 11:11
@jsoonworld jsoonworld self-assigned this Feb 17, 2025
Copy link
Contributor

@0-tae 0-tae left a comment

Choose a reason for hiding this comment

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

고생 많으셨어요. 요청하신 부분과 함께 궁금한 점 남기고 마치겠습니다.

네이밍은 적절하게 잘 변형된 것 같습니다. GroupInfo처럼, ~Info 접미사를 통해서 반환하고자 하는 도메인 정보의 최상단으로 명시하고, Summary는 바꾸신 것 처럼 중간 요소를 의미하는 것으로 규칙을 고정하면 될 것 같습니다.

QueryDSL은 쿼리 최적화에 뛰어난 것 같습니다. CRUD 기능 개발을 자주 하게 된다면 유지보수가 좋다는 장점도 부각될 것 같습니다☺️

.filter(m -> m.getMemberId().equals(memberId))
.findFirst()
.orElseThrow(() -> new GroupException(GroupErrorCode.MEMBER_NOT_FOUND));
}
Copy link
Contributor

Choose a reason for hiding this comment

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

findMembersByGroupId -> findMemberByGroupIdAndMemberId처럼 WHERE 조건 절에 MemberId와 GroupId가 일치하는 확인하는 방법으로 해결할 수 없을까요?

조건부 연산을 DBMS에서 수행할 수 있으면, DBMS에서 넘어오는 데이터에 대한 Bandwidth의 부담을 줄일 수 있습니다!

Copy link
Member Author

Choose a reason for hiding this comment

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

피드백 감사합니다! 😊

현재 findMembersByGroupId(groupId)를 호출한 후 Java 스트림을 이용해 memberId를 필터링하고 있는데, QueryDSL으로 구현된 로직을 개선하면 해당 필터링을 DB에서 직접 수행할 수 있을 것 같습니다.

제안해주신 대로 findMemberByGroupIdAndMemberId(groupId, memberId)와 같은 형태로 WHERE 조건을 적용하면, 불필요한 데이터를 애플리케이션으로 가져오지 않고 DBMS가 직접 필터링을 수행하여 네트워크 및 메모리 사용량을 줄일 수 있을 것 같습니다.

예를 들어, 현재 findMembersByGroupId(groupId)는 다음과 같이 모든 멤버를 가져옵니다:

public List<Member> findMembersByGroupId(Long groupId) {
    return queryFactory
        .select(member)
        .from(memberGroup)
        .join(memberGroup.member, member)
        .where(memberGroup.group.groupId.eq(groupId))
        .fetch();
}

이후 Java 스트림을 사용하여 필터링하고 있는데, QueryDSL에서 직접 처리하는 방식으로 변경하면 더 효율적일 것입니다:

public Optional<Member> findMemberByGroupIdAndMemberId(Long groupId, Long memberId) {
    return Optional.ofNullable(
        queryFactory
            .select(member)
            .from(memberGroup)
            .join(memberGroup.member, member)
            .where(memberGroup.group.groupId.eq(groupId)
                .and(member.memberId.eq(memberId)))
            .fetchOne()
    );
}

이렇게 하면 DB에서 직접 groupIdmemberId 조건을 적용해 검색하므로 불필요한 데이터 전송을 방지할 수 있을 것 같아요!

따라서 findMemberInGroup 메서드를 다음과 같이 변경하는 것이 더 적절할 것 같습니다:

private Member findMemberInGroup(Long memberId, Long groupId) {
    return memberGroupRepository.findMemberByGroupIdAndMemberId(groupId, memberId)
            .orElseThrow(() -> new GroupException(GroupErrorCode.MEMBER_NOT_FOUND));
}

이렇게 하면 전체 데이터를 불러온 후 필터링하는 것이 아니라, DB에서 직접 원하는 데이터를 가져오기 때문에 성능상 더 유리겠어요!

이 부분은 이슈로 등록하고 개선하겠습니다!

좋은 피드백 감사합니다! 🙌 🚀

.join(memberGroup.member, member)
.where(memberGroup.group.groupId.eq(groupId))
.fetch();
}
Copy link
Contributor

Choose a reason for hiding this comment

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

👍👍👍

@jsoonworld jsoonworld merged commit 81100fd into develop Feb 17, 2025
1 check passed
@jsoonworld jsoonworld deleted the feat/54 branch February 18, 2025 04:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
✨ feat Something isn't working 🐋 장순 🐋 This issue or pull request already exists
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants