์ค์๊ฐ ๊ฒฝ๋งค ํ๋ซํผ Highlight์ ๋ฐฑ์๋ API ์๋ฒ. ๋ค์ ์ฌ์ฉ์๊ฐ ๋์์ ๊ฒฝํฉํ๋ ์ ์ฐฐ ํ๊ฒฝ์์ ๋ฐ์ดํฐ ์ ํฉ์ฑ ยท ์ค์๊ฐ์ฑ ยท ๊ฒฝ๋งค ์๋ช ์ฃผ๊ธฐ ์๋ํ๋ฅผ ํต์ฌ ๊ณผ์ ๋ก ์ผ์ ์ค๊ณํ์ต๋๋ค.
2025 ๋ธ๋ ์ด๋ฒ์ค MVP ๊ฐ๋ฐ ํด์ปคํค ์์ฆ2 ์์์ ยท ๊ฐ๋ฐ ๊ธฐ๊ฐ 2025.08 (์ฝ 1๊ฐ์)
๊ฒฝ๋งค๋ "๊ฐ์ ์๊ฐ, ๊ฐ์ ๋ฌผ๊ฑด, ์ฌ๋ฌ ์ฌ๋"์ด ๋ถ๋ชํ๋ ๋๋ฉ์ธ์ ๋๋ค. ์ด ์๋ฒ๋ ๊ทธ ๊ฒฝํฉ ์ง์ ์ ์ด๋ป๊ฒ ์์ ํ๊ณ ๋น ๋ฅด๊ฒ ์ฒ๋ฆฌํ๋๊ฐ์ ์ง์คํ์ต๋๋ค.
์ฌ๋ฌ ์ฌ์ฉ์๊ฐ ๋์์ ๊ฐ์ ๊ฒฝ๋งค์ ์
์ฐฐํ๋ฉด race condition์ผ๋ก ์ต๊ณ ๊ฐ๊ฐ ๋ฎ์ด์จ์ง๊ฑฐ๋ ์ค๋ณต ๋์ฐฐ์ด ๋ฐ์ํ ์ ์์ต๋๋ค. ์
์ฐฐ ํธ๋์ญ์
์ง์
์์ ์ ํด๋น Auction ํ์ ๋ฐฐํ ๋ฝ(PESSIMISTIC_WRITE) ์ ๊ฑธ์ด, ๊ฒ์ฆ โ ์ด์ ์ต๊ณ ๊ฐ OUTBID ์ ํ โ ์ ๊ท ์
์ฐฐ ์ ์ฅ โ ์ต๊ณ ๊ฐ ๊ฐฑ์ ์ ํ๋์ ์์์ ํ๋ฆ์ผ๋ก ์ง๋ ฌํํฉ๋๋ค.
// AuctionRepository
@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("SELECT a FROM Auction a WHERE a.id = :auctionId")
Optional<Auction> findByIdWithLock(@Param("auctionId") Long auctionId);- ์ต์ ์ธ์ํญ(
minimumBid)ยท์ ์ฐฐ ๋จ์(bidUnit) ๊ฒ์ฆ, ๋์ผ๊ฐ ์ ์ฐฐ์ ์ ์ฐฉ์ ์ฐ์ - ์๋ ์ ์ฐฐ(auto-bid) ์ํ ๊ฒ์ฆ
- ๋ฐ๋ ค๋ ์ด์ ์ต๊ณ ์
์ฐฐ์๋ ์ฆ์
OUTBID์ํ๋ก ์ ํํ๊ณ ๊ฐ์ธ ์๋ฆผ ๋ฐ์ก
STOMP ๋ฉ์์ง ๋ธ๋ก์ปค + SockJS fallback(/ws, /ws/auctions, /ws/direct)์ผ๋ก ์ค์๊ฐ ์ฑ๋์ ๊ตฌ์ฑํ๊ณ , ๋ธ๋ก๋์บ์คํธ์ ๊ฐ์ธ ์๋ฆผ์ ์ฑ๋ ๋จ์๋ก ๋ถ๋ฆฌํ์ต๋๋ค.
| ์ฑ๋ | ์ฉ๋ |
|---|---|
/topic/auction/{id} |
1:N โ ๊ฒฝ๋งค๋ณ ์ค์๊ฐ ์ ๋ณด(์ ์ ์ฐฐยทํ์ฌ๊ฐยท์ํยท์นด์ดํธ๋ค์ด) |
/topic/auctions |
1:N โ ์ ์ฒด ๊ฒฝ๋งค ๋ชฉ๋ก ๋ณ๊ฒฝ ์คํธ๋ฆผ |
/queue/user/{id}/notifications |
1:1 โ ๊ฐ์ธ outbid ์๋ฆผ |
์ ์ฐฐ ํ ๊ฑด์ด ์ฑ์ฌ๋๋ฉด ๊ตฌ๋ ์ ์ ์์๊ฒ ์ต๊ณ ๊ฐ๋ฅผ push ํ๋ ๋์์, ๋ฐ๋ ค๋ ์ง์ ์ต๊ณ ์ ์ฐฐ์์๊ฒ๋ง ๊ฐ์ธ ํ๋ก ์๋ฆผ์ ๋ณด๋ ๋๋ค.
// ๊ฒฝ๋งค๋ณ ์์ ์๊ฐ์ ๋ง์ถฐ ๊ฐ๋ณ ์์ฝ, ScheduledFuture๋ก ์ทจ์ยท์ฌ์์ฝ ๊ด๋ฆฌ
ScheduledFuture<?> task = taskScheduler.schedule(() -> startAuction(id), startTime);
scheduledTasks.put(auctionId, task); // ConcurrentHashMap<Long, ScheduledFuture<?>>- ๋์ ์์ฝ: ๊ฒฝ๋งค๋ง๋ค
TaskScheduler๋ก ์์ ์๊ฐ์ ์์ฝํ๊ณConcurrentHashMap์ผ๋ก ๊ฐ๋ณ ์ทจ์ยท์ฌ์์ฝ - ์์ ๋ง(self-healing): ์๋ฒ ์ฌ์์ยท์์ฝ ๋๋ฝ์ ๋๋นํด 1๋ถ ์ฃผ๊ธฐ ์ค์์ผ๋ก ๋์น ์์/๋ง๋ฃ ์ข ๋ฃ๋ฅผ ์๋ ๋ณต๊ตฌํ๊ณ ์ํ ์ํ๋ฅผ ๋๊ธฐํ
- 1์ด ์นด์ดํธ๋ค์ด: ์งํ ์ค ๊ฒฝ๋งค์ ๋จ์ ์๊ฐ์ ์ด ๋จ์๋ก ๋ธ๋ก๋์บ์คํธ
์ฌ์ฉ์ ์กฐํ ์ด๋ ฅ(UserProductView)์ ๋ถ์ํด ํจ๊ป ๋ณธ ์ํ(co-view) ์ฐ๊ด๋๋ฅผ ๊ณ์ฐํ๊ณ , ์์ธ ํ์ด์ง ์ถ์ฒ์ ํ์ฉํฉ๋๋ค.
@Scheduled(cron = "0 0 3 * * *")โ ๋งค์ผ ์๋ฒฝ 3์@Async๋ฐฐ์น๋ก ์ต๊ทผ 30์ผ ์ด๋ ฅ ์ง๊ณ โProductAssociation๊ฐฑ์@Scheduled(cron = "0 0 4 * * 0")โ ๋งค์ฃผ ์ผ์์ผ ์๋ฒฝ 4์ ์ค๋๋ ์ฐ๊ด ๋ฐ์ดํฐ ์ ๋ฆฌ
- JWT Access/Refresh ์ด์ค ํ ํฐ,
JwtAuthenticationFilter๋ฅผ ์ํ๋ฆฌํฐ ํํฐ์ฒด์ธ ์ ๋์ ๋ฐฐ์น, ์ธ์ ์STATELESS - CoolSMS ํด๋ํฐ ๋ณธ์ธ์ธ์ฆ ๊ธฐ๋ฐ ํ์๊ฐ์ /๋ก๊ทธ์ธ
- ๋๋ฉ์ธ๋ณ
ErrorCodeenum +BusinessException+@RestControllerAdviceGlobalExceptionHandler๋ก ์ผ๊ด๋ ์๋ฌ ์๋ต ๊ท๊ฒฉํ
flowchart LR
subgraph Client["Client โ Next.js Web"]
FE["React 19<br/>STOMP.js / SockJS"]
end
FE -- "REST ยท JWT" --> CTRL["Controller Layer<br/>(19 controllers)"]
FE -- "WebSocket ยท STOMP" --> BROKER["STOMP Broker<br/>/topic ยท /queue"]
CTRL --> SVC["Service Layer<br/>(18 services)"]
BROKER --> SVC
SVC --> REPO["Repository โ Spring Data JPA"]
REPO --> DB[("MySQL 8.0<br/>AWS RDS")]
SVC --> S3[("AWS S3<br/>์ํ ์ด๋ฏธ์ง")]
SCH["Scheduler<br/>TaskScheduler ยท @Scheduled"] --> SVC
BAT["Batch<br/>์ฐ๊ด๋ @Async cron"] --> REPO
Layered Architecture โ Controller โ Service โ Repository โ Domain, ํก๋จ ๊ด์ฌ์ฌ๋ security ยท exception ยท util ยท validation์ผ๋ก ๋ถ๋ฆฌ.
sequenceDiagram
participant C as Client
participant Ctrl as BidController
participant Svc as BidService
participant DB as MySQL
participant WS as STOMP Broker
C->>Ctrl: POST /api/bids (JWT)
Ctrl->>Svc: createBid(request, userId)
Svc->>DB: findByIdWithLock(auctionId) [PESSIMISTIC_WRITE]
Svc->>Svc: ์
์ฐฐ ๊ฒ์ฆ (์ต์ ์ธ์ํญ ยท ์
์ฐฐ ๋จ์ ยท ์๋์
์ฐฐ ์ํ)
Svc->>DB: ์ด์ ์ต๊ณ ๊ฐ OUTBID, ์ ๊ท ์
์ฐฐ ์ ์ฅ, ์ต๊ณ ๊ฐ ๊ฐฑ์
Svc-->>WS: /topic/auction/{id} ์ ์
์ฐฐ ๋ธ๋ก๋์บ์คํธ
Svc-->>WS: /queue/user/{prev} ๊ฐ์ธ outbid ์๋ฆผ
WS-->>C: ์ค์๊ฐ ๊ฐฑ์ push
Svc-->>Ctrl: BidResponseDto
Ctrl-->>C: 200 OK
erDiagram
USER ||--o{ BID : places
AUCTION ||--o{ BID : receives
PRODUCT ||--|| AUCTION : "auctioned as"
SELLER ||--o{ PRODUCT : registers
PRODUCT ||--o{ PRODUCT_IMAGE : has
USER ||--o{ USER_PRODUCT_VIEW : views
PRODUCT ||--o{ PRODUCT_ASSOCIATION : "related to"
USER ||--o{ PRODUCT_WISHLIST : wishes
AUCTION {
Long id PK
AuctionStatus status
BigDecimal startPrice
BigDecimal currentHighestBid
BigDecimal buyItNowPrice
BigDecimal bidUnit
datetime scheduledStartTime
datetime scheduledEndTime
}
BID {
Long id PK
BigDecimal bidAmount
BidStatus status
boolean isAutoBid
datetime createdAt
}
PRODUCT {
Long id PK
String productName
ProductStatus status
Category category
}
USER {
Long id PK
String nickname
String phoneNumber
Rank rank
}
AuctionStatus:SCHEDULED โ IN_PROGRESS โ COMPLETED / FAILED / CANCELLEDยทBidStatus:WINNING / OUTBID / WON
| ๊ตฌ๋ถ | ๊ธฐ์ |
|---|---|
| Framework | Spring Boot 3.4.5 (Java 17), Spring Data JPA, Spring Security, Spring WebSocket(STOMP) |
| Database | MySQL 8.0 (AWS RDS) ยท H2 (ํ ์คํธ) |
| Realtime | STOMP over WebSocket ยท SockJS fallback |
| Auth | JWT (Access/Refresh) ยท CoolSMS ํด๋ํฐ ์ธ์ฆ |
| Storage | AWS S3 (์ํ ์ด๋ฏธ์ง) |
| Infra | AWS EC2 ยท Docker ยท Docker Compose |
| CI/CD | GitHub Actions โ Docker Hub โ EC2 |
| Docs | Swagger / OpenAPI 3.0 ยท Spring Actuator |
src/main/java/com/highlight/highlight_backend/
โโโ config/ # Security ยท WebSocket(STOMP) ยท Scheduling ยท CORS ยท Swagger
โโโ controller/ # REST API (19) + WebSocket ์ปจํธ๋กค๋ฌ
โโโ service/ # ๋น์ฆ๋์ค ๋ก์ง (18) โ ์
์ฐฐ ยท ๊ฒฝ๋งค ์ค์ผ์ค๋ฌ ยท ์ถ์ฒ ๋ฐฐ์น ๋ฑ
โโโ domain/ # JPA ์ํฐํฐ (12)
โโโ repository/ # Spring Data JPA + Specification (๋์ ๊ฒ์)
โโโ dto/ # ์์ฒญ/์๋ต ยท WebSocket ๋ฉ์์ง DTO
โโโ security/ # JWT ์ธ์ฆ ํํฐ
โโโ exception/ # ๋๋ฉ์ธ๋ณ ErrorCode + ์ ์ญ ์์ธ ์ฒ๋ฆฌ
โโโ validation/ # ์ปค์คํ
๊ฒ์ฆ (@ValidPassword)
โโโ util/ # JWT ยท ์ธ์ฆ ยท ์๋ต ์ ํธ
์๊ตฌ์ฌํญ: Java 17+ ยท Docker & Docker Compose
# 1) ํด๋ก
git clone https://github.com/blaybus-highlight/backend.git
cd backend
# 2) .env ์์ฑ (DB / JWT / AWS S3 / CoolSMS ๋ฑ)
# docker-compose.yml ์ฐธ๊ณ
# 3) ์ ์ฒด ์คํ ์คํ (MySQL + Backend)
docker-compose up -d
docker-compose logs -f highlight-backend
# โ ๋๋ ๋ก์ปฌ ๊ฐ๋ฐ ์คํ โ
docker-compose up -d highlight-db # DB๋ง ์ปจํ
์ด๋๋ก
./gradlew bootRun
# ํ
์คํธ
./gradlew test- Swagger UI:
http://<host>:8085/swagger-ui/index.html - OpenAPI JSON:
http://<host>:8085/api-docs
๋ํ ์๋ํฌ์ธํธ
| Method | Path | ์ค๋ช |
|---|---|---|
POST |
/api/auth/phone/send ยท /verify |
SMS ์ธ์ฆ์ฝ๋ ๋ฐ์ก/๊ฒ์ฆ |
POST |
/api/auth/signup ยท /login |
ํ์๊ฐ์ / ๋ก๊ทธ์ธ(JWT ๋ฐ๊ธ) |
GET |
/api/products/{id} |
์ํ ์์ธ (์ฐ๊ด ์ํ ํฌํจ) |
GET |
/api/auctions |
๊ฒฝ๋งค ๋ชฉ๋ก ์กฐํ |
GET |
/api/auctions/{id}/status |
์ค์๊ฐ ๊ฒฝ๋งค ์ํ |
POST |
/api/bids |
์ ์ฐฐํ๊ธฐ (๋ฝ ๊ธฐ๋ฐ ์ฒ๋ฆฌ) |
POST |
/api/buy-now |
์ฆ์๊ตฌ๋งค |
main ๋ธ๋์น push โ GitHub Actions๊ฐ Gradle ๋น๋ โ Docker ์ด๋ฏธ์ง ๋น๋ & Docker Hub push โ SSH๋ก EC2 ๋ฐฐํฌ๊น์ง ์๋ํ.
push(main) โ Gradle build โ Docker build&push โ Docker Hub โ EC2 pull&run