POTATO는 자취생이 직접 요리한 식사를 인증하고, 보상으로 받은 경험치와 재화 스푼으로 캐릭터를 성장시키는 식생활 습관 형성 서비스입니다.
- 사용자가 직접 요리한 식사를 인증합니다.
- 인증 결과에 따라 경험치와 서비스 재화인 스푼을 지급합니다.
- 사용자는 스푼으로 상점 아이템을 구매합니다.
- 구매한 아이템을 인벤토리에서 장착해 캐릭터를 성장시킵니다.
요리 인증 → 경험치·스푼 지급 → 아이템 구매 → 인벤토리 저장 → 캐릭터 장착
POTATO-119/
├── Backend/
│ ├── src/main/java/com/example/potato/
│ │ ├── controller/ # HTTP 요청 및 응답 처리
│ │ ├── service/ # 구매, 인벤토리 등 비즈니스 규칙
│ │ ├── repository/ # 데이터베이스 접근
│ │ ├── entity/ # 사용자, 아이템, 인벤토리 모델
│ │ ├── dto/ # API 요청·응답 데이터
│ │ └── config/ # CORS 및 애플리케이션 설정
│ ├── src/main/resources/ # DB 및 Spring Boot 설정
│ └── src/test/ # 백엔드 테스트
└── Frontend/
├── src/pages/ # 로그인, 회원가입, 홈 화면
├── src/features/ # 인증 등 도메인별 기능
├── src/components/ # 공통 및 화면 구성 컴포넌트
├── src/lib/ # Axios API 클라이언트
├── src/router/ # 화면 경로 설정
├── src/store/ # 클라이언트 상태 관리
└── src/assets/ # 이미지와 정적 리소스
- Backend: Java 21, Spring Boot 4, Spring Data JPA, Gradle
- Database: MySQL
- Frontend: React 18, TypeScript, Vite
git clone https://github.com/POTATO-119/potato-back.git potato-backend
git clone https://github.com/POTATO-119/potato-front.git potato-frontend- Java 21
- MySQL 8.x
- Node.js 20 이상
- npm
MySQL에서 데이터베이스를 생성합니다.
CREATE DATABASE potato CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;백엔드 실행 터미널에 데이터베이스 접속 정보를 설정합니다. Spring Boot 환경 변수가 저장소의 기본 설정보다 우선 적용됩니다.
export SPRING_DATASOURCE_URL="jdbc:mysql://localhost:3306/potato?serverTimezone=Asia/Seoul&characterEncoding=UTF-8"
export SPRING_DATASOURCE_USERNAME="root"
export SPRING_DATASOURCE_PASSWORD="your-mysql-password"프론트엔드에서는 예제 파일을 복사한 뒤 로컬 API 주소를 설정합니다.
cd potato-frontend
cp .env.example .env.local.env.local의 값을 다음과 같이 변경합니다.
VITE_API_BASE_URL=http://localhost:8080
VITE_API_TIMEOUT=5000
VITE_APP_NAME=POTATO
VITE_APP_ENV=developmentcd potato-backend
bash ./gradlew bootRun서버가 실행되면 다음 주소에서 API 문서를 확인할 수 있습니다.
- Swagger UI: http://localhost:8080/swagger-ui/index.html
새 터미널에서 다음 명령을 실행합니다.
cd potato-frontend
npm install
npm run dev- Frontend: http://localhost:5173
- 백엔드 Swagger UI가 열리는지 확인합니다.
- 프론트엔드 로그인 화면이 표시되는지 확인합니다.
- 회원가입 또는 로그인 요청이
http://localhost:8080으로 전달되는지 브라우저 개발자 도구에서 확인합니다. - 상점 조회 → 아이템 구매 → 인벤토리 조회 → 아이템 장착 순서로 데이터가 연결되는지 확인합니다.
재화 차감과 아이템 지급이 일어나는 상점 커머스 도메인의 특성상, 대량의 동시 요청 상황에서도 데이터 무결성과 트랜잭션 원자성(Atomicity)을 완벽히 보장하도록 설계되었습니다.
- 트랜잭션 외부 Redis 락 대기:
Redisson RLock을 사용자 단위로 적용하여 동일 사용자의 중복 요청을 애플리케이션 입구에서 직렬화합니다. 락 획득 대기를@Transactional외부에서 수행함으로써 동시 요청 시 DB 커넥션 Pool(HikariCP)의 불필요한 점유를 최소화했습니다. - DB 레벨 2차 방어: 트랜잭션 내부에서 JPA
Pessimistic Write Lock및(user_id, item_id)DB 유니크 제약 조건을 조합하여 Race Condition 및 중복 지급을 차단합니다.
동일 사용자의 100 VUser 동시 결제 요청 상황을 재현하고, 락 적용 후의 데이터 정합성을 검증했습니다.
| 항목 | 실측 결과 | 비고 |
|---|---|---|
| 정상 결제 (200 OK) | 1건 | 최초 요청자 정상 재화 차감 및 아이템 지급 |
| 차단 요청 (400 Bad Request) | 99건 | 잔액 부족 및 중복 구매에 따른 정당한 비즈니스 예외 처리 |
| 서버 오류 (500 Server Error) | 0건 | Deadlock 및 타임아웃 없이 전량 안전하게 상쇄 |
| 최종 데이터 정합성 | 100% | 잔액 정확 차감, 인벤토리 1건 생성, Redis 락 잔존 0건 |
- 구매 실패 시 재화만 차감되거나 아이템만 지급되는 부분 반영(Partial Commit)을 방지합니다.
- 카테고리별 장착 아이템 자동 해제 및 소유권 검증 로직을 단일 트랜잭션 내에서 처리합니다.
POST /api/users/join: 회원가입POST /api/users/login: 로그인GET /api/items: 전체·카테고리별 아이템 조회POST /api/items/purchase: 아이템 구매 및 재화 차감GET /api/items/inventory/{userId}: 사용자 인벤토리 조회POST /api/items/inventory/equip: 아이템 장착 및 기존 아이템 자동 해제