Skip to content

[feat] CH9. API & Pagingย #10

@millkk04

Description

@millkk04

๐Ÿš€ ์กฐํšŒ API 3์ข… ๊ตฌํ˜„ ์™„๋ฃŒ

๐Ÿ“‹ ์ด์Šˆ ๊ฐœ์š”

ํšŒ์› ๋ฆฌ๋ทฐ, ๊ฐ€๊ฒŒ ๋ฏธ์…˜, ํšŒ์› ๋ฏธ์…˜ ์กฐํšŒ๋ฅผ ์œ„ํ•œ REST API 3์ข…์„ ๊ตฌํ˜„ํ•˜์˜€์Šต๋‹ˆ๋‹ค.


โœ… ๊ตฌํ˜„ ์™„๋ฃŒ API ๋ชฉ๋ก

1๏ธโƒฃ ๋‚ด๊ฐ€ ์ž‘์„ฑํ•œ ๋ฆฌ๋ทฐ ๋ชฉ๋ก ์กฐํšŒ API

  • API Path: GET /api/members/{memberId}/reviews
  • ๊ธฐ๋Šฅ: ํŠน์ • ํšŒ์›์ด ์ž‘์„ฑํ•œ ๋ฆฌ๋ทฐ ๋ชฉ๋ก์„ ํŽ˜์ด์ง•ํ•˜์—ฌ ์กฐํšŒ
  • ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ:
    • page (๊ธฐ๋ณธ๊ฐ’: 0) - ํŽ˜์ด์ง€ ๋ฒˆํ˜ธ
    • size (๊ธฐ๋ณธ๊ฐ’: 10) - ํŽ˜์ด์ง€ ํฌ๊ธฐ
  • ์‘๋‹ต ์ฝ”๋“œ: REVIEW200_4

2๏ธโƒฃ ํŠน์ • ๊ฐ€๊ฒŒ์˜ ๋ฏธ์…˜ ๋ชฉ๋ก ์กฐํšŒ API

  • API Path: GET /api/stores/{storeId}/missions
  • ๊ธฐ๋Šฅ: ํŠน์ • ๊ฐ€๊ฒŒ์˜ ๋ฏธ์…˜ ๋ชฉ๋ก์„ ํŽ˜์ด์ง•ํ•˜์—ฌ ์กฐํšŒ
  • ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ:
    • page (๊ธฐ๋ณธ๊ฐ’: 0) - ํŽ˜์ด์ง€ ๋ฒˆํ˜ธ
    • size (๊ธฐ๋ณธ๊ฐ’: 10) - ํŽ˜์ด์ง€ ํฌ๊ธฐ
  • ์‘๋‹ต ์ฝ”๋“œ: MISSION200_7

3๏ธโƒฃ ๋‚ด๊ฐ€ ์ง„ํ–‰ ์ค‘์ธ ๋ฏธ์…˜ ๋ชฉ๋ก ์กฐํšŒ API

  • API Path: GET /api/members/{memberId}/missions
  • ๊ธฐ๋Šฅ: ํŠน์ • ํšŒ์›์ด ์ง„ํ–‰ ์ค‘์ธ ๋ฏธ์…˜ ๋ชฉ๋ก์„ ํŽ˜์ด์ง•ํ•˜์—ฌ ์กฐํšŒ
  • ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ:
    • page (๊ธฐ๋ณธ๊ฐ’: 0) - ํŽ˜์ด์ง€ ๋ฒˆํ˜ธ
    • size (๊ธฐ๋ณธ๊ฐ’: 10) - ํŽ˜์ด์ง€ ํฌ๊ธฐ
  • ์‘๋‹ต ์ฝ”๋“œ: MISSION200_8

๐Ÿ”ง ๊ตฌํ˜„ ์„ธ๋ถ€์‚ฌํ•ญ

API 1: ๋‚ด๊ฐ€ ์ž‘์„ฑํ•œ ๋ฆฌ๋ทฐ ๋ชฉ๋ก ์กฐํšŒ

์ƒ์„ฑ/์ˆ˜์ •๋œ ํŒŒ์ผ

  • โœ… ReviewController.java - GET ์—”๋“œํฌ์ธํŠธ ์ถ”๊ฐ€
  • โœ… ReviewQueryService.java - ์ธํ„ฐํŽ˜์ด์Šค ๋ฉ”์„œ๋“œ ์ถ”๊ฐ€
  • โœ… ReviewQueryServiceImpl.java - ์กฐํšŒ ๋กœ์ง ๊ตฌํ˜„
  • โœ… ReviewResDTO.java - MyReviewPageDTO, MyReviewDTO ์ถ”๊ฐ€
  • โœ… ReviewConverter.java - DTO ๋ณ€ํ™˜ ๋ฉ”์„œ๋“œ ์ถ”๊ฐ€
  • โœ… ReviewRepository.java - findByMemberId() ๋ฉ”์„œ๋“œ ์ถ”๊ฐ€
  • โœ… ReviewSuccessCode.java - MY_REVIEW_LIST_RETRIEVED ์ฝ”๋“œ ์ถ”๊ฐ€

์ฃผ์š” ๊ธฐ๋Šฅ

  • ํšŒ์› ID๋กœ ์ž‘์„ฑํ•œ ๋ฆฌ๋ทฐ ์กฐํšŒ
  • ์ตœ์‹ ์ˆœ ์ •๋ ฌ (createdAt DESC)
  • ํŽ˜์ด์ง• ์ฒ˜๋ฆฌ ์ง€์›
  • ๊ฐ€๊ฒŒ ์ •๋ณด ํฌํ•จ (Join Fetch ์ตœ์ ํ™” ๊ฐ€๋Šฅ)

์‘๋‹ต ์˜ˆ์‹œ

{
  "isSuccess": true,
  "code": "REVIEW200_4",
  "message": "๋‚ด๊ฐ€ ์ž‘์„ฑํ•œ ๋ฆฌ๋ทฐ ๋ชฉ๋ก ์กฐํšŒ์— ์„ฑ๊ณตํ–ˆ์Šต๋‹ˆ๋‹ค.",
  "result": {
    "currentPage": 0,
    "totalPages": 3,
    "totalElements": 25,
    "pageSize": 10,
    "hasNext": true,
    "hasPrevious": false,
    "reviews": [
      {
        "reviewId": 15,
        "storeId": 5,
        "storeName": "๋ง›์žˆ๋Š” ๋–ก๋ณถ์ด",
        "star": 5,
        "content": "์ •๋ง ๋ง›์žˆ์–ด์š”!",
        "createdAt": "2025-11-20T14:30:00"
      }
    ]
  }
}

API 2: ํŠน์ • ๊ฐ€๊ฒŒ์˜ ๋ฏธ์…˜ ๋ชฉ๋ก ์กฐํšŒ

์ƒ์„ฑ/์ˆ˜์ •๋œ ํŒŒ์ผ

  • โœ… MissionController.java - GET ์—”๋“œํฌ์ธํŠธ ์ถ”๊ฐ€
  • โœ… MissionQueryService.java - ์ธํ„ฐํŽ˜์ด์Šค ๋ฉ”์„œ๋“œ ์ถ”๊ฐ€
  • โœ… MissionQueryServiceImpl.java - ์กฐํšŒ ๋กœ์ง ๊ตฌํ˜„
  • โœ… MissionResDTO.java - MissionPageDTO, MissionDTO ์ถ”๊ฐ€
  • โœ… MissionConverter.java - DTO ๋ณ€ํ™˜ ๋ฉ”์„œ๋“œ ์ถ”๊ฐ€
  • โœ… MissionRepository.java - findByStoreId() ๋ฉ”์„œ๋“œ ์ถ”๊ฐ€
  • โœ… MissionSuccessCode.java - MISSION_LIST_RETRIEVED ์ฝ”๋“œ ์ถ”๊ฐ€

์ฃผ์š” ๊ธฐ๋Šฅ

  • ๊ฐ€๊ฒŒ ID๋กœ ๋ฏธ์…˜ ๋ชฉ๋ก ์กฐํšŒ
  • ๊ฐ€๊ฒŒ ์กด์žฌ ์—ฌ๋ถ€ ๊ฒ€์ฆ
  • ์ตœ์‹ ์ˆœ ์ •๋ ฌ (createdAt DESC)
  • ํŽ˜์ด์ง• ์ฒ˜๋ฆฌ ์ง€์›
  • ์ฐธ์—ฌ ์ธ์› ์ˆ˜ ํ‘œ์‹œ

์‘๋‹ต ์˜ˆ์‹œ

{
  "isSuccess": true,
  "code": "MISSION200_7",
  "message": "ํŠน์ • ๊ฐ€๊ฒŒ์˜ ๋ฏธ์…˜ ๋ชฉ๋ก ์กฐํšŒ์— ์„ฑ๊ณตํ–ˆ์Šต๋‹ˆ๋‹ค.",
  "result": {
    "currentPage": 0,
    "totalPages": 1,
    "totalElements": 8,
    "pageSize": 10,
    "hasNext": false,
    "hasPrevious": false,
    "missions": [
      {
        "missionId": 3,
        "conditional": "๋–ก๋ณถ์ด 3ํšŒ ๊ตฌ๋งค",
        "point": 1000,
        "deadline": "2025-12-31",
        "createdAt": "2025-11-15T10:00:00",
        "participantCount": 12
      }
    ]
  }
}

API 3: ๋‚ด๊ฐ€ ์ง„ํ–‰ ์ค‘์ธ ๋ฏธ์…˜ ๋ชฉ๋ก ์กฐํšŒ

์ƒ์„ฑ/์ˆ˜์ •๋œ ํŒŒ์ผ

  • โœ… MemberMissionRepository.java - ์‹ ๊ทœ ์ƒ์„ฑ (member ํŒจํ‚ค์ง€)
  • โœ… MissionController.java - GET ์—”๋“œํฌ์ธํŠธ ์ถ”๊ฐ€
  • โœ… MissionQueryService.java - ์ธํ„ฐํŽ˜์ด์Šค ๋ฉ”์„œ๋“œ ์ถ”๊ฐ€
  • โœ… MissionQueryServiceImpl.java - ์กฐํšŒ ๋กœ์ง ๊ตฌํ˜„
  • โœ… MissionResDTO.java - MyMissionPageDTO, MyMissionDTO ์ถ”๊ฐ€
  • โœ… MissionConverter.java - DTO ๋ณ€ํ™˜ ๋ฉ”์„œ๋“œ ์ถ”๊ฐ€
  • โœ… MissionSuccessCode.java - MY_MISSION_LIST_RETRIEVED ์ฝ”๋“œ ์ถ”๊ฐ€
  • โœ… MissionCommandServiceImpl.java - import ์ˆ˜์ •

์ฃผ์š” ๊ธฐ๋Šฅ

  • ํšŒ์› ID๋กœ ์ง„ํ–‰ ์ค‘์ธ ๋ฏธ์…˜ ์กฐํšŒ
  • ํšŒ์› ์กด์žฌ ์—ฌ๋ถ€ ๊ฒ€์ฆ
  • ์ตœ์‹ ์ˆœ ์ •๋ ฌ (createdAt DESC)
  • ํŽ˜์ด์ง• ์ฒ˜๋ฆฌ ์ง€์›
  • ๋ฏธ์…˜ ์™„๋ฃŒ ์—ฌ๋ถ€ ํ‘œ์‹œ
  • ๊ฐ€๊ฒŒ ์ •๋ณด ํฌํ•จ

Repository ์ถ”๊ฐ€ ๋ฉ”์„œ๋“œ

// MemberMissionRepository
Page<MemberMission> findByMemberId(Long memberId, Pageable pageable);
boolean existsByMemberIdAndMissionId(Long memberId, Long missionId);
Page<MemberMission> findInProgress(@Param("memberId") Long memberId, Pageable pageable);
Page<MemberMission> findCompleted(@Param("memberId") Long memberId, Pageable pageable);

์‘๋‹ต ์˜ˆ์‹œ

{
  "isSuccess": true,
  "code": "MISSION200_8",
  "message": "๋‚ด๊ฐ€ ์ง„ํ–‰ ์ค‘์ธ ๋ฏธ์…˜ ๋ชฉ๋ก ์กฐํšŒ์— ์„ฑ๊ณตํ–ˆ์Šต๋‹ˆ๋‹ค.",
  "result": {
    "currentPage": 0,
    "totalPages": 2,
    "totalElements": 15,
    "pageSize": 10,
    "hasNext": true,
    "hasPrevious": false,
    "missions": [
      {
        "memberMissionId": 1,
        "missionId": 5,
        "storeName": "๋ง›์žˆ๋Š” ๋–ก๋ณถ์ด",
        "conditional": "๋–ก๋ณถ์ด 3ํšŒ ๊ตฌ๋งค",
        "point": 1000,
        "deadline": "2025-12-31",
        "isComplete": false,
        "startedAt": "2025-11-20T10:30:00"
      }
    ]
  }
}

๐Ÿ—๏ธ ๊ณตํ†ต ์•„ํ‚คํ…์ฒ˜ ํŒจํ„ด

๋ชจ๋“  ์กฐํšŒ API๋Š” ๋™์ผํ•œ ๋ ˆ์ด์–ด๋“œ ์•„ํ‚คํ…์ฒ˜๋ฅผ ๋”ฐ๋ฆ…๋‹ˆ๋‹ค:

Controller โ†’ QueryService โ†’ Repository โ†’ Entity
    โ†“            โ†“              โ†“           โ†“
 ์š”์ฒญ ์ˆ˜์‹     ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง    ๋ฐ์ดํ„ฐ ์กฐํšŒ   DB ๋งคํ•‘
    โ†“            โ†“
Converter โ† DTO ๋ณ€ํ™˜
    โ†“
 Response ๋ฐ˜ํ™˜

๊ณ„์ธต๋ณ„ ์—ญํ• 

  1. Controller Layer

    • HTTP ์š”์ฒญ ์ˆ˜์‹  ๋ฐ ๊ฒฝ๋กœ ๋งคํ•‘
    • ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ ์ฒ˜๋ฆฌ (page, size)
    • ์‘๋‹ต ํฌ๋งท ํ†ต์ผ (ApiResponse)
  2. Service Layer

    • ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ์‹คํ–‰
    • ์—”ํ‹ฐํ‹ฐ ์กด์žฌ ์—ฌ๋ถ€ ๊ฒ€์ฆ
    • ํŽ˜์ด์ง• ์„ค์ • (Pageable ์ƒ์„ฑ)
  3. Repository Layer

    • JPA ๋ฉ”์„œ๋“œ ์ฟผ๋ฆฌ ๋˜๋Š” JPQL ์‚ฌ์šฉ
    • ํŽ˜์ด์ง• ์ง€์› (Page<T> ๋ฐ˜ํ™˜)
  4. Converter Layer

    • Entity โ†’ DTO ๋ณ€ํ™˜
    • Page ๊ฐ์ฒด ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์ถ”์ถœ
  5. DTO Layer

    • ์‘๋‹ต ๋ฐ์ดํ„ฐ ๊ตฌ์กฐํ™”
    • ํŽ˜์ด์ง• ์ •๋ณด ํฌํ•จ

๐ŸŽฏ ๊ณตํ†ต ๊ธฐ๋Šฅ

ํŽ˜์ด์ง• ์ฒ˜๋ฆฌ

  • Spring Data JPA์˜ Pageable ํ™œ์šฉ
  • ๊ธฐ๋ณธ๊ฐ’: page=0, size=10
  • ์ •๋ ฌ: ์ตœ์‹ ์ˆœ (createdAt DESC)

์—๋Ÿฌ ์ฒ˜๋ฆฌ

  • ์กด์žฌํ•˜์ง€ ์•Š๋Š” ํšŒ์›: MemberException
  • ์กด์žฌํ•˜์ง€ ์•Š๋Š” ๊ฐ€๊ฒŒ: StoreException
  • ํ†ต์ผ๋œ ์—๋Ÿฌ ์‘๋‹ต ํฌ๋งท

์‘๋‹ต ๊ตฌ์กฐ

๋ชจ๋“  API๋Š” ๋‹ค์Œ ์ •๋ณด๋ฅผ ํฌํ•จ:

  • currentPage: ํ˜„์žฌ ํŽ˜์ด์ง€ ๋ฒˆํ˜ธ
  • totalPages: ์ „์ฒด ํŽ˜์ด์ง€ ์ˆ˜
  • totalElements: ์ „์ฒด ํ•ญ๋ชฉ ์ˆ˜
  • pageSize: ํŽ˜์ด์ง€ ํฌ๊ธฐ
  • hasNext: ๋‹ค์Œ ํŽ˜์ด์ง€ ์กด์žฌ ์—ฌ๋ถ€
  • hasPrevious: ์ด์ „ ํŽ˜์ด์ง€ ์กด์žฌ ์—ฌ๋ถ€
  • [data]: ์‹ค์ œ ๋ฐ์ดํ„ฐ ๋ฆฌ์ŠคํŠธ

๐Ÿ› ํ•ด๊ฒฐํ•œ ์ด์Šˆ

MemberMissionRepository ์ค‘๋ณต ๋ฌธ์ œ

  • ๋ฌธ์ œ: MemberMissionRepository๊ฐ€ member, mission ํŒจํ‚ค์ง€์— ์ค‘๋ณต ์ƒ์„ฑ
  • ์›์ธ: Bean ์ด๋ฆ„ ์ถฉ๋Œ ๋ฐœ์ƒ
  • ํ•ด๊ฒฐ:
    1. mission ํŒจํ‚ค์ง€์˜ Repository ์‚ญ์ œ
    2. member ํŒจํ‚ค์ง€๋กœ ํ†ตํ•ฉ
    3. ๋ชจ๋“  ๋ฉ”์„œ๋“œ ๋ณ‘ํ•ฉ
    4. import ๋ฌธ ์ˆ˜์ •

๐Ÿ“ ํ…Œ์ŠคํŠธ ๋ฐฉ๋ฒ•

1. ๋‚ด๊ฐ€ ์ž‘์„ฑํ•œ ๋ฆฌ๋ทฐ ๋ชฉ๋ก ์กฐํšŒ

GET http://localhost:8080/api/members/1/reviews?page=0&size=10

2. ํŠน์ • ๊ฐ€๊ฒŒ์˜ ๋ฏธ์…˜ ๋ชฉ๋ก ์กฐํšŒ

GET http://localhost:8080/api/stores/1/missions?page=0&size=10

3. ๋‚ด๊ฐ€ ์ง„ํ–‰ ์ค‘์ธ ๋ฏธ์…˜ ๋ชฉ๋ก ์กฐํšŒ

GET http://localhost:8080/api/members/1/missions?page=0&size=10

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions