Skip to content

sjh9714/borrow_me

Repository files navigation

BorrowMe

CI

BorrowMe는 물건을 대여하고 반납하는 과정을 다룬 Spring Boot API입니다. 2024년에는 첫 백엔드 팀 프로젝트로 알림을 프론트엔드와 연결했고, 2026년에 코드를 다시 열어 요청부터 반납까지의 대여 생명주기를 완성했습니다.

2024년 팀 작업과 2026년 개인 작업은 PROVENANCE.md에서 나눠 설명합니다.

먼저 볼 것

궁금한 점 바로 확인할 곳
대여 상태와 역할 규칙 ReservationServiceImpl
동시 요청에서 재고를 지키는 방법 ReservationConcurrencyTest
승인·인도·반납 권한과 재시도 ReservationLifecycleIntegrationTest
기존 DB를 새 상태로 옮기는 방법 V2__reservation_lifecycle.sql

어디서 시작했나

BorrowMe는 2024년 GGUM 해커톤을 위해 약 2주간 준비하고, 1박 2일 동안 본 행사를 진행한 11인 팀 프로젝트였습니다. 이 정보와 프론트엔드 연동·팀 시연 경험은 저장소 소유자가 확인한 사실입니다. 기존 팀 저장소는 현재 비공개이므로, 접근할 수 없는 PR을 근거로 제시하지 않습니다.

당시 제가 맡은 범위는 다음과 같습니다.

  • 댓글·답글 알림 생성
  • 알림 조회, 읽음 처리, 읽은 알림 삭제
  • NotificationController의 REST API 전환
  • 프론트엔드 연동과 팀 시연

2024년 예약 기능 전체를 제가 만들었다고 설명하지 않습니다.

2026년에 다시 본 문제

기존 코드의 예약은 요청 즉시 재고를 차감하고, 취소하면 복원하는 수준에 머물러 있었습니다. 상품 소유자가 요청을 승인하고 물건을 인도한 뒤 반납을 확인하는 경로가 없었고, 누가 어떤 상태를 바꿘는지도 남지 않았습니다.

예약 버튼 하나가 아니라 실제 대여 과정을 다루기 위해 다음 질문으로 범위를 다시 잡았습니다.

  • 대여자와 소유자는 각각 어떤 작업을 할 수 있는가?
  • 같은 요청이 반복되어도 재고와 알림이 한 번만 바뀌는가?
  • 상품을 숨겨도 진행 중인 대여를 반납할 수 있는가?

현재 대여 흐름

REQUESTED -> APPROVED -> BORROWED -> RETURNED
    |            |
    +-> REJECTED +-> CANCELED
    +----------------> CANCELED
상태 의미 재고
REQUESTED 대여자가 요청함 요청 수량 hold
APPROVED 소유자가 요청을 승인함 hold 유지
BORROWED 소유자가 인도를 확인함 hold 유지
RETURNED 소유자가 반납을 확인함 정확히 한 번 복원
REJECTED 소유자가 요청을 거절함 정확히 한 번 복원
CANCELED 대여자가 인도 전에 취소함 정확히 한 번 복원

상태와 재고 지도

BorrowMe 대여 상태별 행위자와 재고 hold 또는 복원 시점

편집 가능한 diagrams.net 원본

다이어그램 transcript:

  • 시작: 대여자가 요청하면 REQUESTED가 되고 요청 수량만큼 재고를 hold합니다.
  1. 소유자가 요청을 승인하면 APPROVED가 되고 hold를 유지합니다.
  2. 소유자가 인도를 확인하면 BORROWED가 되고 hold를 유지합니다.
  3. 소유자가 반납을 확인하면 RETURNED가 되고 재고를 한 번 복원합니다.
  4. 소유자가 REQUESTED를 거절하면 REJECTED가 되고 재고를 한 번 복원합니다.
  5. 대여자는 REQUESTED 또는 APPROVED에서 취소할 수 있으며, CANCELED로 전이할 때 재고를 한 번 복원합니다.

종료 action을 다시 요청해도 상태, 재고, 이력, 알림은 다시 바뀌지 않습니다.

역할별 허용 작업

행위자 할 수 있는 작업
대여자 대여 요청, REQUESTED/APPROVED 취소
상품 소유자 승인, 거절, 인도 확인, 반납 확인
관계없는 사용자 목록·상세·상태 변경 모두 차단

응답 DTO는 현재 사용자가 실행할 수 있는 allowedActions와 전이 이력을 함께 돌려줍니다.

재고를 바꾸는 방법

대여 요청과 상태 변경은 모두 ReservationServiceImpl을 통합니다.

  1. Product 또는 ReservationPESSIMISTIC_WRITE 잠금으로 조회합니다.
  2. 현재 상태와 행위자 역할을 검사합니다.
  3. 상태, 재고, transition history, 알림을 같은 transaction에서 저장합니다.

같은 action이 재시도되면 이미 도달한 상태를 반환합니다. transition과 알림에는 예약별 event key unique constraint도 두어 중복 저장을 막습니다.

상품 수정과 보관

상품의 전체 재고를 변경할 때는 전체 재고 - 사용 가능 재고를 활성 hold로 계산합니다. 새 전체 재고가 활성 hold보다 작으면 변경을 거절합니다.

DELETE /api/products/{id}는 행을 지우지 않고 archived_at을 기록합니다. 보관된 상품은 목록과 새 대여 요청에서 제외되지만, 기존 대여의 반납과 이력은 남습니다.

API

POST /api/products/{productId}/reservations
GET  /api/reservations?perspective=BORROWER|OWNER
GET  /api/reservations/{reservationId}
POST /api/reservations/{reservationId}/actions

상태 변경 요청:

{
  "action": "APPROVE | REJECT | MARK_BORROWED | MARK_RETURNED | CANCEL",
  "reason": "optional"
}

기존 POST /api/products/{id}/reserve는 새 서비스로 위임하는 deprecated 경계로 남겨 두었습니다. 응답의 Deprecation 헤더와 Link 헤더가 새 API를 안내합니다.

자동 검증

시나리오 검증 내용
재고 50개, 동시 요청 100건 성공 50건, 실패 50건, 최종 재고 0
요청 -> 승인 -> 인도 -> 반납 상태·재고·이력·알림이 함께 바뀐
권한 밖 접근 비관계자 조회와 역할 밖 action 차단
중복·동시 취소/반납 재고, transition, 알림이 한 번만 반영
전체 재고 수정 활성 hold 보존, hold보다 작은 값 거절
상품 보관 새 요청 차단, 진행 중 대여 반납 허용
V1 -> V2 migration 기존 상태 변환과 최초 transition 생성

주요 테스트:

실행

필요 환경은 Java 17, MySQL, Docker(Testcontainers 실행 시)입니다.

./gradlew bootRun --args='--spring.profiles.active=local'

프로덕션 프로필에서는 DB, JWT, AWS, mail 정보를 환경 변수로 주입합니다.

JAVA_HOME=$(/usr/libexec/java_home -v 17) ./gradlew test

Docker 데몬이 실행 중이어야 MySQL Testcontainers 테스트가 동작합니다.

기술 선택

  • Java 17, Spring Boot 3.1.5
  • Spring Data JPA, MySQL, Flyway
  • Spring Security, JWT
  • AWS S3, Spring Mail
  • JUnit 5, Testcontainers

문서

주장하지 않는 것

  • 2024년 예약 기능 전체를 혼자 구현했다는 주장
  • 접근할 수 없는 팀 PR을 공개 근거로 사용하는 일
  • 과거 수치와의 성능 개선율
  • 운영 환경의 성능, 사용자 수, 장기 가용성

현재 공개 근거는 이 저장소의 코드와 자동 테스트입니다.

About

교내 물품 대여 팀 프로젝트 — 댓글 알림·REST 연동과 요청부터 반납까지의 대여 생명주기

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors