개발 프로젝트/[팀 프로젝트] 냉파마스터

장보기 도메인 API 구현 정리

namerong 2026. 6. 29. 23:46

냉파마스터의 장보기 도메인 기능을 구현했다.
장보기 기능은 사용자가 구매할 재료를 목록으로 관리하고, 구매 여부를 체크한 뒤 냉장고 재료로 반영할 수 있는 흐름이다.

작업 범위는 다음과 같다.

  • #26 장보기 항목 추가 API
  • #27 장보기 목록 조회 API
  • #28 장보기 항목 삭제 API
  • #29 장보기 항목 체크/체크 해제 API
  • #30 장보기 항목 냉장고 반영 API

1. 장보기 항목 추가 API

장보기 항목 추가 API는 사용자가 구매할 재료를 장보기 목록에 등록하는 기능이다.

POST /api/v1/shopping-items

요청 데이터는 사전 재료 ID와 수량을 받는다.

{
  "productId": 1,
  "quantity": "1개"
}

이 API에서 중요한 점은 memberId를 요청으로 받지 않는다는 것이다.
회원 정보는 로그인 토큰에서 가져온 email을 기준으로 조회한다.

Member member = findMemberByEmail(email);

이후 요청으로 들어온 productId가 실제 존재하는 사전 재료인지 검증한다.

productService.validateExists(request.productId());

장보기 항목을 처음 추가할 때는 아직 구매한 상태가 아니기 때문에 isPurchased는 false로 저장했다.

shoppingItem.isPurchased = false;

2. 장보기 목록 조회 API

장보기 목록 조회 API는 로그인한 사용자의 장보기 항목을 조회한다.

GET /api/v1/shopping-items

이 API는 반드시 본인 데이터만 조회해야 한다.

조회 조건은 다음과 같다.

member_id = 로그인한 회원 id
is_deleted = false

장보기 테이블에는 productId만 있고, 실제 재료명과 카테고리는 products 테이블에 있다.
그래서 목록 응답에서는 ShoppingItem과 Product 정보를 합쳐서 ShoppingItemListResponse로 변환했다.

ShoppingItem
+ Product
= ShoppingItemListResponse

이 과정에서 productId 목록을 먼저 뽑고, 해당 ID들로 상품 정보를 한 번에 조회했다.

List<Long> productIds = shoppingItems.stream()
        .map(ShoppingItem::getProductId)
        .toList();

이렇게 한 이유는 장보기 항목마다 상품을 하나씩 조회하지 않고, 필요한 상품 정보를 한 번에 가져오기 위해서다.

3. 장보기 항목 삭제 API

장보기 항목 삭제 API는 실제 DB row를 삭제하지 않고 isDeleted 값을 변경하는 소프트 삭제 방식으로 구현했다.

DELETE /api/v1/shopping-items/{shoppingItemId}

삭제 대상은 다음 조건으로 찾는다.

shopping_item_id = 요청 path variable
member_id = 로그인한 회원 id
is_deleted = false

이 조건을 사용한 이유는 다른 사용자의 장보기 항목을 삭제하지 못하도록 하기 위해서다.

엔티티에는 삭제 상태를 변경하는 메서드를 두었다.

public void delete() {
    this.isDeleted = true;
    this.deletedAt = LocalDate.now();
}

삭제 후 목록 조회에서는 isDeleted = false 조건만 조회하기 때문에 삭제된 항목은 더 이상 보이지 않는다.

4. 장보기 항목 체크/체크 해제 API

장보기 항목 체크/체크 해제 API는 사용자가 장보기 항목을 구매했는지 표시하는 기능이다.

PATCH /api/v1/shopping-items/{shoppingItemId}/check

체크 처리:

{
  "isPurchased": true
}

체크 해제:

{
  "isPurchased": false
}

이 API는 isPurchased 값을 요청값에 따라 변경한다.

shoppingItem.updatePurchased(request.isPurchased());

isPurchased의 의미는 다음과 같다.

false = 아직 구매하지 않음
true = 구매 완료

이 기능은 장보기 목록에서 사용자가 어떤 항목을 이미 샀는지 표시하는 데 사용된다.

5. 장보기 항목 냉장고 반영 API

장보기 항목 냉장고 반영 API는 장보기에서 구매한 항목을 냉장고 재료로 등록하는 기능이다.

POST /api/v1/shopping-items/{shoppingItemId}/fridge

요청 Body는 냉장고에 추가로 필요한 값을 받는다.

{
  "expiryDate": "2026-07-05",
  "memo": "장보기에서 냉장고로 추가"
}

장보기 항목에는 이미 productId, quantity가 있기 때문에 요청으로 다시 받을 필요가 없다.
냉장고 재료 등록에 추가로 필요한 expiryDate, memo만 받았다.

처리 흐름은 다음과 같다.

1. 로그인 회원 조회
2. 내 장보기 항목 단건 조회
3. 장보기 항목의 productId, quantity로 FridgeItem 생성
4. fridge_items 테이블에 저장
5. 기존 shopping_items 항목은 삭제 처리
6. FridgeItemResponse 반환

즉 이 API는 단순 조회나 수정이 아니라:

장보기 항목 → 냉장고 재료

로 이동시키는 기능이다.

그래서 서비스 메서드에는 @Transactional을 적용했다.
냉장고 저장은 됐는데 장보기 삭제가 실패하거나, 반대로 장보기만 삭제되고 냉장고 저장이 실패하면 데이터가 꼬일 수 있기 때문이다.

6. 구현하면서 정리한 개념

이번 장보기 API를 구현하면서 가장 많이 사용한 흐름은 다음과 같다.

Controller
→ 요청값 받기

Service
→ 회원 검증
→ 본인 데이터 조회
→ 비즈니스 로직 처리

Repository
→ DB 조회/저장

Entity
→ 자기 상태 변경

DTO
→ 요청/응답 데이터 구조 분리

특히 장보기 목록 조회와 냉장고 반영 기능에서는 단순히 한 테이블만 보는 것이 아니라, 다른 도메인 데이터와 연결해야 했다.

장보기 목록 조회:

shopping_items + products

장보기 냉장고 반영:

shopping_items → fridge_items

이 과정을 통해 단순 CRUD뿐 아니라 도메인 간 데이터 흐름도 조금씩 이해할 수 있었다.

7. 정리

이번 작업으로 장보기 도메인의 기본 흐름을 구현했다.

사용자는 장보기 항목을 추가하고, 목록을 조회하고, 필요 없는 항목을 삭제할 수 있다.
또한 구매 여부를 체크하거나 해제할 수 있고, 구매한 항목을 냉장고 재료로 반영할 수 있다.

이번 작업에서 핵심은 “로그인한 사용자의 데이터만 다룬다”는 점이었다.
모든 조회/수정/삭제/반영 로직에서 memberId 조건을 함께 사용해 본인 데이터만 처리하도록 구현했다.

장보기 기능은 냉장고 기능과 연결되는 도메인이기 때문에, 이후 프론트 연동 시 장보기에서 냉장고로 자연스럽게 재료를 옮기는 사용자 흐름을 만들 수 있다.