Skip to content

About

๐Ÿ HoneyRest ๊ฐ์„ฑ ์ˆ™์†Œ ์˜ˆ์•ฝ ํ”Œ๋žซํผ | ์‚ฌ์šฉ์ž(User) ์‹œ์Šคํ…œ (Spring Boot + React + Redis + Firebase)

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Latest commit

ย 

History

86 Commits

Folders and files

NameName
Last commit message
Last commit date
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

HoneyRest โ€“ ์ˆ™์†Œ ์˜ˆ์•ฝ ํ”Œ๋žซํผ ยท User API

CI Live Demo Admin Demo Java 17 Spring Boot 3.5 MySQL 8 Redis

HoneyRest๋Š” ์ˆ™์†Œ ๊ฒ€์ƒ‰ โ†’ ๊ฐ์‹ค ์„ ํƒ โ†’ ์ฟ ํฐยทํฌ์ธํŠธ ์ ์šฉ โ†’ ํ† ์Šค ๊ฒฐ์ œ โ†’ ๋ฆฌ๋ทฐ๊นŒ์ง€ ์ด์–ด์ง€๋Š” ์ˆ™์†Œ ์˜ˆ์•ฝ ํ”Œ๋žซํผ์ž…๋‹ˆ๋‹ค. ์ด ์ €์žฅ์†Œ๋Š” ์‚ฌ์šฉ์ž(React SPA)๊ฐ€ ํ˜ธ์ถœํ•˜๋Š” REST API ์„œ๋ฒ„๋กœ, ์ธ์ฆ(JWT + OAuth2), ์ˆ™์†Œ ๊ฒ€์ƒ‰, ์˜ˆ์•ฝยท์žฌ๊ณ , ๊ฒฐ์ œ, ๋ฆฌ๋ทฐ, ๋งˆ์ดํŽ˜์ด์ง€๋ฅผ ๋‹ด๋‹นํ•˜๊ณ  ๊ณต์œ  DB ์Šคํ‚ค๋งˆ(Flyway V1~V12)์˜ ์†Œ์œ ์ž์ž…๋‹ˆ๋‹ค. 4์ฃผ ํŒ€ ํ”„๋กœ์ ํŠธ(2025.08) ์ดํ›„ ์˜ˆ์•ฝ ๋™์‹œ์„ฑ, ๊ฒฐ์ œ ๋ณด์ƒ, ๊ถŒํ•œ ๊ฒฝ๊ณ„, ํ…Œ์ŠคํŠธยทCI๋ฅผ ๋‹จ๋…์œผ๋กœ ๋ณด๊ฐ•ํ–ˆ์Šต๋‹ˆ๋‹ค.

์ €์žฅ์†Œ ์—ญํ• 
honeyRest_user (ํ˜„์žฌ) ์‚ฌ์šฉ์ž REST API ยท Spring Boot ยท ์Šคํ‚ค๋งˆ(Flyway) ์†Œ์œ 
honeyrest_user_react ์‚ฌ์šฉ์ž ํ™”๋ฉด ยท React 19 SPA
honeyRest_host ์—…์ฒด ๊ด€๋ฆฌ์ž / ์ด๊ด€๋ฆฌ์ž ํ™”๋ฉด ยท Spring Boot + Thymeleaf

๋ผ์ด๋ธŒ ๋ฐ๋ชจ

Render ๋ฌด๋ฃŒ ํ”Œ๋žœ์˜ ๋ณด์—ฌ์ฃผ๊ธฐ์šฉ ๋ฐ๋ชจ์ž…๋‹ˆ๋‹ค. 15๋ถ„ ๋™์•ˆ ์ ‘์†์ด ์—†์œผ๋ฉด ์ž ๋“ค์–ด ์ฒซ ์ ‘์†์— 1~3๋ถ„ ๊ฑธ๋ฆฌ๊ณ (์ฝœ๋“œ ์Šคํƒ€ํŠธ), ๊นจ์–ด๋‚  ๋•Œ๋งˆ๋‹ค ๋ฐ์ดํ„ฐ๊ฐ€ ์ดˆ๊ธฐํ™”๋ฉ๋‹ˆ๋‹ค. ๊ฒฐ์ œ๋Š” "ํ…Œ์ŠคํŠธ ๊ฒฐ์ œ(๋ฐ๋ชจ)" ๋กœ ์ง„ํ–‰๋˜์–ด ์‹ค์ œ๋กœ ์ฒญ๊ตฌ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ฃผ์†Œ์— ์ ‘๋ฏธ์‚ฌ๊ฐ€ ๋ถ™์—ˆ๋‹ค๋ฉด ์•„๋ž˜ ๋งํฌ๋ฅผ ์‹ค์ œ ์ฃผ์†Œ๋กœ ๋ฐ”๊ฟ” ์ฃผ์„ธ์š”.

์‚ฌ์ดํŠธ ์ฃผ์†Œ ๋ฐ๋ชจ ๊ณ„์ • (๊ณต๊ฐœ์šฉ)
์‚ฌ์šฉ์ž https://honeyrest.onrender.com demo@honeyrest.app / Demo1234! โ€” ๋กœ๊ทธ์ธ ํ™”๋ฉด์˜ "๋ฐ๋ชจ ๊ณ„์ •์œผ๋กœ ๋กœ๊ทธ์ธ"
๊ด€๋ฆฌ์ž https://honeyrest-admin.onrender.com ์—…์ฒด ๊ด€๋ฆฌ์ž contact@honeyrest.com / Demo1234! ยท ์ตœ๊ณ  ๊ด€๋ฆฌ์ž admin@honeyrest.com / Demo1234!
  • ๋‘ ์‚ฌ์ดํŠธ๋Š” ๋ฐ๋ชจ์—์„œ DB ๊ฐ€ ๋ถ„๋ฆฌ๋˜์–ด ์žˆ์–ด(๊ฐ์ž H2 ์ธ๋ฉ”๋ชจ๋ฆฌ) ๊ด€๋ฆฌ์ž์—์„œ ๋ฐ”๊พผ ๋‚ด์šฉ์ด ์‚ฌ์šฉ์ž ์‚ฌ์ดํŠธ์— ๋ณด์ด์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • ๊ตฌ์„ฑยท๋ฐฐํฌ ์ ˆ์ฐจ: docs/DEPLOY_RENDER.md

์—ญํ• 

ํŒ€ ํ”„๋กœ์ ํŠธ (2025.08.04 ~ 2025.09.04, 4์ฃผ, 3๋ช…)

์ด๋ฆ„ ๋‹ด๋‹น
๋ฐ•์„ฑ์› (ํŒ€์žฅ) ์‚ฌ์šฉ์ž(User) ์˜์—ญ ๊ฐœ๋ฐœ ์ด๊ด„(API + React) & ๊ด‘๊ณ  ์˜์ƒ ์ œ์ž‘ / ์ „์ฒด DB ์„ค๊ณ„ ๋ฐ ERD ์ž‘์„ฑ / ๊ด€๋ฆฌ์ž ์‹œ์Šคํ…œ ๊ธฐ์ˆ  ๋ฐฉํ–ฅ ๊ฒฐ์ •(Thymeleaf) / ์ „์ฒด ์‹œ์Šคํ…œ ํ†ตํ•ฉ ๋ฐ ์ฝ”๋“œ ๋ฆฌ๋ทฐ / ๋ฐœํ‘œ ์ž๋ฃŒ ๊ธฐํšยท๋””์ž์ธ
๊น€๋ฏผ๊ฒฝ ์—…์ฒด ๊ด€๋ฆฌ์ž(Company Admin) ์‹œ์Šคํ…œ ๊ฐœ๋ฐœ
์„คํ˜„์˜ค ์ด ๊ด€๋ฆฌ์ž(Super Admin) ์‹œ์Šคํ…œ ๊ฐœ๋ฐœ

ํ”„๋กœ์ ํŠธ ์ข…๋ฃŒ ํ›„ ๋‹จ๋… ๊ณ ๋„ํ™” (2025.09 ~ , ๋ฐ•์„ฑ์›)

  • ๋ณด์•ˆ ๊ฒฝ๊ณ„: ์š”์ฒญ ๋ณธ๋ฌธ์˜ userId ๋Œ€์‹  ์ธ์ฆ ์ฃผ์ฒด๋กœ ์†Œ์œ ๊ถŒ ํŒ๋‹จ(IDOR ์ฐจ๋‹จ), ๊ด€๋ฆฌ์ž ์“ฐ๊ธฐ API ์—ญํ•  ์ œํ•œ, ๋ฏผ๊ฐ์ •๋ณด ๋กœ๊ทธ ์ œ๊ฑฐ โ†’ API ๊ถŒํ•œ ๋งคํŠธ๋ฆญ์Šค
  • ํ† ํฐ ํ๊ธฐ: ๋กœ๊ทธ์•„์›ƒยท๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณ€๊ฒฝ ์‹œ ์ด์ „์— ๋ฐœ๊ธ‰๋œ access token ์ฆ‰์‹œ ๋ฌดํšจํ™”(tokenValidAfter, Flyway V9)
  • ์˜ˆ์•ฝ ์žฌ๊ณ : ๊ฐ์‹ค ํ–‰ ๋น„๊ด€์  ๋ฝ + ๊ธฐ๊ฐ„ ๊ฒน์นจ ์นด์šดํŠธ๋กœ ์ดˆ๊ณผ ์˜ˆ์•ฝ ์ฐจ๋‹จ, ์˜ˆ์•ฝ ์ƒํƒœ ์ƒ์ˆ˜๋ฅผ ๊ด€๋ฆฌ์ž ์ €์žฅ์†Œ์™€ ํ†ต์ผ
  • ๊ฒฐ์ œ ์ผ๊ด€์„ฑ: ์‚ฌ์ „ ๊ฒ€์ฆ โ†’ ํ† ์Šค ์Šน์ธ โ†’ ๋ฝ ํŠธ๋žœ์žญ์…˜ ์ €์žฅ โ†’ ์‹คํŒจ ์‹œ ์ž๋™ ๊ฒฐ์ œ ์ทจ์†Œ(๋ณด์ƒ)
  • ๊ฐ€๊ฒฉ ๊ณ„์‚ฐ ๋‹จ์ผํ™”: price_calendar ๊ธฐ๋ฐ˜ PriceCalculator ํ•˜๋‚˜๋กœ ํ™”๋ฉด ๊ธˆ์•ก๊ณผ ๊ฒฐ์ œ ๊ฒ€์ฆ ๊ธˆ์•ก ์ผ์น˜
  • ์šด์˜ ๊ธฐ๋ฐ˜: ํŒŒ์ผ ์ €์žฅ์†Œ ์ถ”์ƒํ™”(๋กœ์ปฌ/Firebase), ๋ฏธ์‚ฌ์šฉ ์˜์กด์„ฑ ์ œ๊ฑฐ, H2 ํ…Œ์ŠคํŠธ ํ”„๋กœํ•„ + GitHub Actions CI
  • ์ƒ์„ธ ๊ธฐ๋ก: IMPROVEMENTS.md ยท STABILIZATION.md

๊ธฐ์ˆ  ์Šคํƒ

๊ตฌ๋ถ„ ๊ธฐ์ˆ 
Language / Framework Java 17, Spring Boot 3.5.4 (Web, Validation, Actuator)
์ธ์ฆ / ์ธ๊ฐ€ Spring Security, JJWT 0.11.5 (access token), HttpOnly refresh token ์ฟ ํ‚ค, OAuth2 ์†Œ์…œ ๋กœ๊ทธ์ธ (Google, Kakao)
๋ฐ์ดํ„ฐ MySQL 8, Spring Data JPA, QueryDSL 5.0, Flyway (V1~V12, ddl-auto=validate)
์บ์‹œ Redis (Spring Data Redis: ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ์บ์‹œ, ZSet ์ธ๊ธฐ ์ง€์—ญ ๋žญํ‚น, ๋ฆฌ๋ทฐ ์ข‹์•„์š” ์นด์šดํ„ฐ), Spring Cache
๊ฒฐ์ œ Toss Payments ๊ฒฐ์ œ ์Šน์ธยท์ทจ์†Œ API (RestTemplate)
ํŒŒ์ผ ์ €์žฅ FileStorage ์ถ”์ƒํ™” โ€” ๋กœ์ปฌ ๋””์Šคํฌ(๊ธฐ๋ณธ) / Firebase Storage(์„ ํƒ), Thumbnailator
๋ฉ”์ผ Spring Mail (Gmail SMTP, @Async ๋ฐœ์†ก)
๋ฌธ์„œ / ๋กœ๊น… SpringDoc OpenAPI 2.2 (Swagger UI), Log4j2
ํ…Œ์ŠคํŠธ / CI JUnit 5, Mockito, Spring Security Test, H2 (MySQL ๋ชจ๋“œ), GitHub Actions

์•„ํ‚คํ…์ฒ˜

flowchart LR
    FE["React SPA<br/>honeyrest_user_react :5173"]
    HOST["honeyRest_host :8081<br/>๊ด€๋ฆฌ์ž ํ™”๋ฉด"]

    subgraph API["User API ยท Spring Boot :8080"]
        F["JwtAuthenticationFilter<br/>Bearer access token ๊ฒ€์ฆ<br/>tokenValidAfter ํ™•์ธ"]
        C["Controller"]
        S["Service<br/>ReserveService ยท PriceCalculator<br/>PaymentOrchestrationService"]
        Q["JPA / QueryDSL Repository"]
        FS["FileStorage<br/>Local ๋˜๋Š” Firebase"]
        F --> C --> S --> Q
        S --> FS
    end

    FE -- "REST /api/**<br/>refreshToken: HttpOnly ์ฟ ํ‚ค" --> F
    Q --> DB[("MySQL 8<br/>Flyway V1~V12")]
    S --> R[("Redis<br/>๊ฒ€์ƒ‰ ์บ์‹œ ยท ZSet ๋žญํ‚น")]
    S -- "์Šน์ธ / ๋ณด์ƒ ์ทจ์†Œ" --> TOSS["Toss Payments API"]
    S --> MAIL["Gmail SMTP"]
    HOST -. "๊ฐ™์€ ์Šคํ‚ค๋งˆ ๊ณต์œ <br/>validate๋งŒ ์ˆ˜ํ–‰" .-> DB
    DOM["honeyrest-domain<br/>๊ณต์œ  JPA ์—”ํ‹ฐํ‹ฐ ๋ชจ๋“ˆ"]
    Q -. "์—”ํ‹ฐํ‹ฐ" .-> DOM
    HOST -. "git submodule +<br/>composite build" .-> DOM
Loading
  • ์‚ฌ์šฉ์ž API์™€ ๊ด€๋ฆฌ์ž ์•ฑ์€ ํ•˜๋‚˜์˜ MySQL ์Šคํ‚ค๋งˆ๋ฅผ ๊ณต์œ ํ•ฉ๋‹ˆ๋‹ค. ์Šคํ‚ค๋งˆ ๋ณ€๊ฒฝ์€ ์ด ์ €์žฅ์†Œ์˜ Flyway ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์œผ๋กœ๋งŒ ํ•˜๊ณ , ๊ด€๋ฆฌ์ž ์•ฑ์€ ddl-auto=validate๋กœ ๋งคํ•‘ ์ •ํ•ฉ์„ฑ๋งŒ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
  • ๊ณต์œ  ๋„๋ฉ”์ธ ๋ชจ๋“ˆ honeyrest-domain: ๋‘ ์•ฑ์ด ๊ฐ์ž ๋ณต์‚ฌํ•ด ์“ฐ๋˜ JPA ์—”ํ‹ฐํ‹ฐ(์„œ๋กœ ์กฐ๊ธˆ์”ฉ ๋‹ฌ๋ผ์ ธ ์žˆ๋˜ ์‚ฌ๋ณธ โ€” ๊ณต์œ  ์—”ํ‹ฐํ‹ฐ 30๊ฐœ + BaseEntity)๋ฅผ ์ด ์ €์žฅ์†Œ์˜ Gradle ์„œ๋ธŒํ”„๋กœ์ ํŠธ ํ•˜๋‚˜๋กœ ํ•ฉ์ณค์Šต๋‹ˆ๋‹ค. ํŒจํ‚ค์ง€๋Š” com.honeyrest.domain.entity(์—”ํ‹ฐํ‹ฐ)ยทcom.honeyrest.domain.type(ReservationStatus, BannerPosition)์ด๊ณ  Spring ์„œ๋น„์Šค/๋ฆฌํฌ์ง€ํ† ๋ฆฌ๋Š” ์—†์Šต๋‹ˆ๋‹ค(์˜์กด์„ฑ: jakarta.persistence-api, Lombok, Hibernate ์–ด๋…ธํ…Œ์ด์…˜, QueryDSL Q ํƒ€์ž… ์ƒ์„ฑ). ๊ด€๋ฆฌ์ž ์•ฑ์€ ์ด ์ €์žฅ์†Œ๋ฅผ libs/honeyrest-user git submodule๋กœ ๋‘๊ณ  Gradle composite build(includeBuild)๋กœ com.honeyrest:honeyrest-domain์„ ์ด ๋ชจ๋“ˆ๋กœ ์น˜ํ™˜ํ•ด ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์‚ฌ๋ณธ๋ณ„ ์ฐจ์ด์™€ ํ†ตํ•ฉ ๊ฒฐ์ •์€ DOMAIN_MODULE.md์— ์ •๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค.
    honeyRest_user/                  (rootProject: ์‚ฌ์šฉ์ž API, Flyway ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์†Œ์œ )
    โ”œโ”€โ”€ honeyrest-domain/            (:honeyrest-domain โ€” ๊ณต์œ  ์—”ํ‹ฐํ‹ฐ, ๋‘ ์•ฑ์ด @EntityScan("com.honeyrest.domain"))
    โ””โ”€โ”€ src/main/resources/db/migration/   (V1~V12, ๋„๋ฉ”์ธ ๋ชจ๋“ˆ์ด ์•„๋‹ˆ๋ผ API ๋ชจ๋“ˆ์— ๋‚จ์Œ)
    
  • ์—”ํ‹ฐํ‹ฐ๋ฅผ ๋ฐ”๊ฟ€ ๋•Œ๋Š” ์ด ์ €์žฅ์†Œ์—์„œ ์—”ํ‹ฐํ‹ฐ + Flyway ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ๊ฐ™์€ ์ปค๋ฐ‹์œผ๋กœ ๋ฐ”๊พธ๊ณ , ๊ด€๋ฆฌ์ž ์ €์žฅ์†Œ๋Š” ์„œ๋ธŒ๋ชจ๋“ˆ์„ ๊ทธ ์ปค๋ฐ‹์œผ๋กœ ์žฌ๊ณ ์ •(re-pin)ํ•ฉ๋‹ˆ๋‹ค.
  • ๋„๋ฉ”์ธยทERD: ARCHITECTURE.md ยท ์ปฌ๋Ÿผ ๋ช…์„ธ: DB_SCHEMA.md ยท ๊ธฐ๋Šฅ ๋ชฉ๋ก: FEATURES.md

ํ•ต์‹ฌ ์„ค๊ณ„ ๊ฒฐ์ • & ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ…

1. ๊ฒฐ์ œ๋Š” ์Šน์ธ๋๋Š”๋ฐ ์˜ˆ์•ฝ ์ €์žฅ์ด ์‹คํŒจํ•˜๋Š” ๋ฌธ์ œ โ€” ๋ณด์ƒ ํŠธ๋žœ์žญ์…˜

  • ๋ฌธ์ œ: ํ† ์Šค ์Šน์ธ(์™ธ๋ถ€ ๊ณผ๊ธˆ)๊ณผ ์˜ˆ์•ฝ INSERT(๋‚ด๋ถ€ DB)๋Š” ํ•˜๋‚˜์˜ ํŠธ๋žœ์žญ์…˜์œผ๋กœ ๋ฌถ์„ ์ˆ˜ ์—†์–ด, ์Šน์ธ ํ›„ DB ์˜ค๋ฅ˜ยท๋งค์ง„ ์‹œ "๋ˆ์€ ๋น ์กŒ๋Š”๋ฐ ์˜ˆ์•ฝ์€ ์—†๋Š”" ์ƒํƒœ๊ฐ€ ์ƒ๊ธธ ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.
  • ๊ฒฐ์ •: โ‘  ๊ณผ๊ธˆ ์ „ ์‚ฌ์ „ ๊ฒ€์ฆ(์ค‘๋ณต paymentKeyยท์˜ˆ์•ฝ๋ฒˆํ˜ธ, ์ฃผ๋ฌธ๋ฒˆํ˜ธ, ์„œ๋ฒ„ ์žฌ๊ณ„์‚ฐ ๊ธˆ์•ก, ์žฌ๊ณ ) โ†’ โ‘ก ํ† ์Šค ์Šน์ธ โ†’ โ‘ข TransactionTemplate ์•ˆ์—์„œ ๊ฐ์‹ค ๋ฝ + ์žฌ๊ณ  ์žฌํ™•์ธ ํ›„ ์ €์žฅ โ†’ โ‘ฃ โ‘ข ์‹คํŒจ ์‹œ TossService.cancelPayment๋กœ ์ž๋™ ์ทจ์†Œ. ๊ฐ™์€ paymentKey๊ฐ€ ๋™์‹œ ์š”์ฒญ์œผ๋กœ ์ด๋ฏธ ์ €์žฅ๋๋‹ค๋ฉด ์ •์ƒ ๊ฒฐ์ œ์ด๋ฏ€๋กœ ์ทจ์†Œํ•˜์ง€ ์•Š๊ณ , ์ทจ์†Œ๋งˆ์ € ์‹คํŒจํ•˜๋ฉด orderId์™€ ํ•จ๊ป˜ ์ˆ˜๋™ ํ™˜๋ถˆ ๋กœ๊ทธ๋ฅผ ๋‚จ๊น๋‹ˆ๋‹ค.
  • ๊ฒฐ๊ณผ: ์Šน์ธ ํ›„ ์‹คํŒจยท๋งค์ง„ยท์ค‘๋ณต ์š”์ฒญ ์‹œ๋‚˜๋ฆฌ์˜ค 7๊ฑด์„ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋กœ ๊ณ ์ •. ์˜ˆ์•ฝ๋ฒˆํ˜ธ UNIQUE ์ œ์•ฝ์ด ์ตœ์ข… ๋ฉฑ๋“ฑ์„ฑ ๋ฐฉ์–ด์„ ์ž…๋‹ˆ๋‹ค.
  • ์ฝ”๋“œ: PaymentOrchestrationService ยท ํ…Œ์ŠคํŠธ

2. ๋งˆ์ง€๋ง‰ 1์‹ค ๋™์‹œ ์˜ˆ์•ฝ โ€” ๋น„๊ด€์  ๋ฝ + ๊ธฐ๊ฐ„ ๊ฒน์นจ ์นด์šดํŠธ

  • ๋ฌธ์ œ: ์˜ˆ์•ฝ ์‹œ ์žฌ๊ณ  ๊ฒ€์‚ฌ๊ฐ€ ์—†์–ด ๊ฐ™์€ ๊ฐ์‹คยท๊ธฐ๊ฐ„์— total_rooms๋ฅผ ์ดˆ๊ณผํ•˜๋Š” ์˜ˆ์•ฝ์ด ์ €์žฅ๋  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.
  • ๊ฒฐ์ •: ์žฌ๊ณ  = room.total_rooms โˆ’ [์ฒดํฌ์ธ, ์ฒดํฌ์•„์›ƒ)๊ณผ ๊ฒน์น˜๋Š” ์ ์œ  ์ƒํƒœ ์˜ˆ์•ฝ ์ˆ˜. ์ €์žฅ ์ง์ „ RoomRepository.findByIdForUpdate(PESSIMISTIC_WRITE)๋กœ ๊ฐ์‹ค ํ–‰์„ ์ž ๊ทธ๊ณ  ReservationRepository.countOverlapping์œผ๋กœ ์žฌํ™•์ธ, ๋ถ€์กฑํ•˜๋ฉด 409. ๊ณต์œ  ์Šคํ‚ค๋งˆ์— version ์ปฌ๋Ÿผ์ด ์—†์–ด ๋‚™๊ด€์  ๋ฝ ๋Œ€์‹  ํ–‰ ๋ฝ์„ ํƒํ–ˆ๊ณ , ๊ด€๋ฆฌ์ž ํ™”๋ฉด์ด ์“ฐ๋Š” ์Šค๋ƒ…์ƒท ๊ฐ’ price_calendar.available_room์€ ํŒ์ •์— ์“ฐ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • ๊ฒฐ๊ณผ: ๊ฐ™์€ ๊ฐ์‹ค์˜ ์˜ˆ์•ฝ ํŠธ๋žœ์žญ์…˜์ด ์ง๋ ฌํ™”๋˜๊ณ , ๊ด€๋ฆฌ์ž ์ €์žฅ์†Œ๋„ ๋™์ผ ๊ทœ์น™(ReservationInventoryGuard)์„ ์ ์šฉํ•ด ๋‘ ์•ฑ์ด ๊ฐ™์€ ์žฌ๊ณ  ๊ธฐ์ค€์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
  • ์ฝ”๋“œ: ReserveService ยท ReservationRepository ยท ReservationStatus

3. ํ™”๋ฉด ๊ธˆ์•ก๊ณผ ๊ฒฐ์ œ ๊ธˆ์•ก ๋ถˆ์ผ์น˜ โ€” ๊ฐ€๊ฒฉ ๊ณ„์‚ฐ๊ธฐ ๋‹จ์ผํ™”

  • ๋ฌธ์ œ: ์˜ˆ์•ฝ ํผ๊ณผ ๊ฒฐ์ œ ๊ฒ€์ฆ์ด ๊ฐ์ž ๊ธˆ์•ก์„ ๊ณ„์‚ฐํ•ด ๋‚ ์งœ๋ณ„ ์š”๊ธˆ(price_calendar)ยท์ถ”๊ฐ€ ์ธ์› ์š”๊ธˆ ๋ฐ˜์˜ ์—ฌ๋ถ€๊ฐ€ ๋‹ฌ๋ž๊ณ , ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๋ณด๋‚ธ ๊ธˆ์•ก์„ ๊ทธ๋Œ€๋กœ ๋ฏฟ๋Š” ๊ฒฝ๋กœ๋„ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.
  • ๊ฒฐ์ •: 1๋ฐ• ์š”๊ธˆ(price_calendar ์šฐ์„ , ์—†์œผ๋ฉด ๊ธฐ๋ณธ๊ฐ€), ๊ณผ๊ธˆ ์ผ์ž(์ฒดํฌ์•„์›ƒ ๋‹น์ผ ์ œ์™ธ), ์ถ”๊ฐ€ ์ธ์› ์š”๊ธˆ ๊ทœ์น™์„ PriceCalculator ํ•œ ๊ณณ์— ๋‘๊ณ  ์˜ˆ์•ฝ ํผ ์กฐํšŒ์™€ ๊ฒฐ์ œ ๊ฒ€์ฆ์ด ๋ชจ๋‘ ์ด๋ฅผ ํ˜ธ์ถœ. ์ฟ ํฐยทํฌ์ธํŠธ๋„ ์„œ๋ฒ„์—์„œ ์†Œ์œ ์žยท๊ธฐ๊ฐ„ยท์ตœ์†Œ ๊ธˆ์•ก์„ ์žฌ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค.
  • ๊ฒฐ๊ณผ: ํ™”๋ฉด ํ‘œ์‹œ ๊ธˆ์•ก = ์˜ˆ์•ฝ ์ €์žฅ ๊ธˆ์•ก = ํ† ์Šค ์š”์ฒญ ๊ธˆ์•ก. ๊ธˆ์•ก์ด ๋‹ค๋ฅด๋ฉด ํ† ์Šค ํ˜ธ์ถœ ์ „์— ๊ฑฐ์ ˆ๋ฉ๋‹ˆ๋‹ค.
  • ์ฝ”๋“œ: PriceCalculator ยท ํ…Œ์ŠคํŠธ

4. ๋กœ๊ทธ์•„์›ƒ ํ›„์—๋„ ์‚ด์•„ ์žˆ๋Š” access token โ€” tokenValidAfter

  • ๋ฌธ์ œ: ๋ฌด์ƒํƒœ JWT๋ผ ๋กœ๊ทธ์•„์›ƒยท๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณ€๊ฒฝ ํ›„์—๋„ ๊ธฐ์กด access token์ด ๋งŒ๋ฃŒ ์‹œ๊ฐ๊นŒ์ง€ ์œ ํšจํ–ˆ์Šต๋‹ˆ๋‹ค.
  • ๊ฒฐ์ •: refresh token์€ HttpOnlyยทSameSite ์ฟ ํ‚ค๋กœ๋งŒ ์ „๋‹ฌํ•˜๊ณ (DB ์ €์žฅ, ๋กœ๊ทธ์•„์›ƒ ์‹œ ์‚ญ์ œ), user.token_valid_after ์ปฌ๋Ÿผ(Flyway V9)์„ ์ถ”๊ฐ€ํ•ด ๋กœ๊ทธ์•„์›ƒยท๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณ€๊ฒฝ ์‹œ ๊ฐฑ์‹ . ์ธ์ฆ ํ•„ํ„ฐ๊ฐ€ ์ด๋ฏธ ์กฐํšŒํ•˜๋Š” ์‚ฌ์šฉ์ž ํ–‰์—์„œ iat < tokenValidAfter์ด๋ฉด ๊ฑฐ๋ถ€ํ•˜๋ฏ€๋กœ Redis ๋ธ”๋ž™๋ฆฌ์ŠคํŠธ ๊ฐ™์€ ์ถ”๊ฐ€ ์ €์žฅ์†Œ๊ฐ€ ํ•„์š” ์—†์Šต๋‹ˆ๋‹ค.
  • ๊ฒฐ๊ณผ: ํ๊ธฐ ์ฆ‰์‹œ ๊ธฐ์กด ํ† ํฐ์ด 401. JWT iat๊ฐ€ ์ดˆ ๋‹จ์œ„์ธ ์ ์„ ๊ณ ๋ คํ•ด tokenValidAfter๋ฅผ "ํ๊ธฐ ์‹œ๊ฐ ์ดˆ ๋‚ด๋ฆผ + 1์ดˆ"๋กœ ์ €์žฅํ•˜๊ณ , ์ƒˆ ํ† ํฐ์€ iat = max(now, tokenValidAfter)๋กœ ๋ฐœ๊ธ‰ํ•ฉ๋‹ˆ๋‹ค. ๋น„๊ต๋„ epoch ์ดˆ ๋‹จ์œ„(iat < tokenValidAfter โ†’ ๊ฑฐ๋ถ€)๋ผ์„œ ํ๊ธฐ ์ง์ „(๊ฐ™์€ ์ดˆ ํฌํ•จ) ํ† ํฐ์€ ํ•ญ์ƒ ๊ฑฐ๋ถ€, ๋กœ๊ทธ์•„์›ƒ ์งํ›„ ๊ฐ™์€ ์ดˆ์˜ ์žฌ๋กœ๊ทธ์ธ ํ† ํฐ์€ ํ•ญ์ƒ ํ—ˆ์šฉ์œผ๋กœ ๊ฒฐ์ •์ ์œผ๋กœ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค. ๋Œ€๊ฐ€๋กœ ํ๊ธฐ ์งํ›„ ๋ฐœ๊ธ‰ ํ† ํฐ์˜ iat/๋งŒ๋ฃŒ๊ฐ€ ์ตœ๋Œ€ 1์ดˆ ๋’ค๋กœ ๋ฐ€๋ฆฝ๋‹ˆ๋‹ค.
  • ์ฝ”๋“œ: JwtTokenProvider ยท RefreshTokenCookieManager ยท V9

5. ์ˆ™์†Œ ๊ฒ€์ƒ‰ โ€” N+1 ํšŒํ”ผ ์กฐํšŒ ๊ตฌ์กฐ์™€ Redis ์บ์‹œ ํ‚ค ์ •ํ•ฉ์„ฑ

  • ๋ฌธ์ œ: ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๋งˆ๋‹ค ๊ฐ์‹ค ์žฌ๊ณ ยทํƒœ๊ทธ๋ฅผ ๋ถ™์—ฌ์•ผ ํ•ด ์—”ํ‹ฐํ‹ฐ ์—ฐ๊ด€์„ ๋”ฐ๋ผ๊ฐ€๋ฉด N+1์ด ๋˜๊ธฐ ์‰ฌ์šด ํ™”๋ฉด์ž…๋‹ˆ๋‹ค. ๋˜ ๊ธฐ์กด Redis ์บ์‹œ ํ‚ค์— ๋‚ ์งœยท์ธ์›ยท์‚ฌ์šฉ์žยท์ตœ๋Œ€๊ฐ€๊ฒฉ์ด ๋น ์ ธ ์˜ˆ์•ฝ ๊ฐ€๋Šฅ ์—ฌ๋ถ€ยท์ฐœ ์ƒํƒœ๊ฐ€ ๋‹ค๋ฅธ ์š”์ฒญ์ด ๊ฐ™์€ ์บ์‹œ๋ฅผ ๊ณต์œ ํ–ˆ๊ณ , ํ˜„์žฌ ํŽ˜์ด์ง€ ๋ชฉ๋ก ํฌ๊ธฐ๋ฅผ ์ „์ฒด ๊ฑด์ˆ˜๋กœ ์จ์„œ ์ˆ™์†Œ 33๊ฐœ๊ฐ€ 20๊ฐœยท1ํŽ˜์ด์ง€๋กœ ์ถ•์†Œ๋˜๋Š” ๋ฒ„๊ทธ๊ฐ€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.
  • ๊ฒฐ์ •: ์ˆ™์†Œ ๋ชฉ๋ก์€ QueryDSL Projections.constructor๋กœ DTO๋ฅผ ํ•œ ๋ฒˆ์— ์กฐํšŒ(ํ‰์ ยท๋ฆฌ๋ทฐ ์ˆ˜ยท์ฐœ ์—ฌ๋ถ€ ์ง‘๊ณ„ ํฌํ•จ)ํ•˜๊ณ , ์˜ˆ์•ฝ ์ˆ˜ยท๊ฐ์‹คยทํƒœ๊ทธ๋Š” ์ˆ™์†Œ ID IN ์กฐํšŒ 3ํšŒ๋กœ ๋ฌถ์–ด ๋ฉ”๋ชจ๋ฆฌ์—์„œ ๋งคํ•‘. ํƒœ๊ทธ ํ•„ํ„ฐ๋Š” groupBy + having countDistinct ์„œ๋ธŒ์ฟผ๋ฆฌ. ์บ์‹œ ํ‚ค๋Š” ์œ„์น˜ยท์ขŒํ‘œยท๋‚ ์งœยท์ธ์›ยท์‚ฌ์šฉ์žยท์ •๋ ฌยท์นดํ…Œ๊ณ ๋ฆฌ/ํƒœ๊ทธ(์ •๋ ฌ)ยท๊ฐ€๊ฒฉยทํŽ˜์ด์ง€๋ฅผ ์ •๊ทœํ™”ํ•ด search:recommend:v2 ์ ‘๋‘์‚ฌ๋กœ ๊ตฌ์„ฑํ•˜๊ณ , ์ „์ฒด ๊ฑด์ˆ˜๋„ ๋ณ„๋„ ํ‚ค๋กœ ์ €์žฅ(TTL 6์‹œ๊ฐ„)ํ•ด ๋‘˜ ๋‹ค ์žˆ์„ ๋•Œ๋งŒ ์ ์ค‘์œผ๋กœ ์ธ์ •ํ•ฉ๋‹ˆ๋‹ค.
  • ๊ฒฐ๊ณผ: ํŽ˜์ด์ง€ ํฌ๊ธฐ์™€ ๋ฌด๊ด€ํ•˜๊ฒŒ ์ฟผ๋ฆฌ ์ˆ˜๊ฐ€ ์ผ์ •ํ•˜๊ณ , ์บ์‹œ ์ ์ค‘ ์‹œ์—๋„ ํŽ˜์ด์ง€ ์ •๋ณด๊ฐ€ ๋ณด์กด๋ฉ๋‹ˆ๋‹ค. ์บ์‹œ ํ‚ค ๊ทœ์น™์€ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋กœ ๊ณ ์ •ํ–ˆ์Šต๋‹ˆ๋‹ค.
  • ์ฝ”๋“œ: AccommodationSearchImpl ยท ์บ์‹œ ํ‚ค ํ…Œ์ŠคํŠธ ยท ZSet ์ธ๊ธฐ ์ง€์—ญยท์ธ๊ธฐ ์ˆ™์†Œ ๋žญํ‚น: RegionService, AccommodationService

6. ๋‘ ์•ฑ์ด ๊ณต์œ ํ•˜๋Š” ์Šคํ‚ค๋งˆ โ€” Flyway ์†Œ์œ ๊ถŒ๊ณผ ์ƒํƒœ ๊ฐ’ ํ†ต์ผ

  • ๋ฌธ์ œ: ๊ด€๋ฆฌ์ž ์•ฑ์ด ddl-auto=validate๋กœ ๊ธฐ๋™ํ•˜๋Š”๋ฐ, ๊ด€๋ฆฌ์ž ์—”ํ‹ฐํ‹ฐ์—๋งŒ ์žˆ๋Š” ์ปฌ๋Ÿผ(reservation.accommodation_name)๊ณผ ์ €์žฅ์†Œ๋งˆ๋‹ค ๋‹ค๋ฅธ ์ƒํƒœ ์ฒ ์ž(CANCELED/CANCELLED)๋กœ ๊ธฐ๋™ ์‹คํŒจยท์ทจ์†Œ ์ง‘๊ณ„ 0๊ฑด ๊ฐ™์€ ๋ฌธ์ œ๊ฐ€ ์ƒ๊ฒผ์Šต๋‹ˆ๋‹ค.
  • ๊ฒฐ์ •: ์Šคํ‚ค๋งˆ ๋ณ€๊ฒฝ์€ ์ด ์ €์žฅ์†Œ Flyway์—์„œ๋งŒ ํ•˜๊ณ (์ด๋ฏธ ์ ์šฉ๋œ ํŒŒ์ผ์€ ์ˆ˜์ •ํ•˜์ง€ ์•Š๊ณ  ๊ต์ • ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ถ”๊ฐ€), V10์œผ๋กœ ๊ด€๋ฆฌ์ž ๋งคํ•‘์— ๋งž์ถ˜ ์ปฌ๋Ÿผ์„, V11๋กœ ๊ด€๋ฆฌ์ž ์ „์šฉ error_log ํ…Œ์ด๋ธ”๊ณผ ๊ตฌ์„ธ๋Œ€ NOT NULL ์ปฌ๋Ÿผ ์™„ํ™”๋ฅผ ์ถ”๊ฐ€. ์˜ˆ์•ฝ ์ƒํƒœยท์žฌ๊ณ  ์ ์œ  ์ƒํƒœ ๋ชฉ๋ก์€ ReservationStatus ์ƒ์ˆ˜๋กœ ์ •์˜ํ–ˆ๊ณ , ์ดํ›„ ์—”ํ‹ฐํ‹ฐ์™€ ํ•จ๊ป˜ ๊ณต์œ  ๋„๋ฉ”์ธ ๋ชจ๋“ˆ(honeyrest-domain)๋กœ ์˜ฎ๊ฒจ ๋‘ ์•ฑ์ด ๊ฐ™์€ ํด๋ž˜์Šค๋ฅผ ์”๋‹ˆ๋‹ค.
  • ๊ฒฐ๊ณผ: ๋‘ ์ €์žฅ์†Œ ๋ชจ๋‘ integrationTest(Testcontainers MySQL 8.0)๋กœ "Flyway๋กœ ์ƒˆ๋กœ ๋งŒ๋“  DB์—์„œ ์Šคํ‚ค๋งˆ ๊ฒ€์ฆ ํ†ต๊ณผ"๋ฅผ CI์—์„œ ์ž๋™ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. ์ฐจ์ด ๋ชฉ๋ก์€ DB_SCHEMA.md ยง6 ์ฐธ๊ณ .
  • ์ฝ”๋“œ: db/migration ยท V10 ยท V11

7. Firebase ํ‚ค ์—†์ด๋Š” ์‹คํ–‰์กฐ์ฐจ ์•ˆ ๋˜๋˜ ๊ตฌ์กฐ โ€” FileStorage ์ถ”์ƒํ™”

  • ๋ฌธ์ œ: ์—…๋กœ๋“œ๊ฐ€ Firebase SDK์— ์ง์ ‘ ๋ฌถ์—ฌ ์žˆ์–ด ์„œ๋น„์Šค ๊ณ„์ • ํ‚ค๊ฐ€ ์—†์œผ๋ฉด ์•ฑ๊ณผ ํ…Œ์ŠคํŠธ๊ฐ€ ๊ธฐ๋™๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.
  • ๊ฒฐ์ •: FileStorage ์ธํ„ฐํŽ˜์ด์Šค์— LocalFileStorage(๊ธฐ๋ณธ)์™€ FirebaseFileStorage๋ฅผ ๋‘๊ณ  @ConditionalOnProperty(app.storage.type)์œผ๋กœ ์„ ํƒ. ํด๋”๋ช…์€ ์ •๊ทœ์‹์œผ๋กœ ๊ฒ€์ฆํ•ด ๊ฒฝ๋กœ ์กฐ์ž‘์„ ๋ง‰๊ณ , ์‚ญ์ œ๋Š” ์ง€์ • ํด๋” ํ•˜์œ„ URL๋งŒ ํ—ˆ์šฉํ•ฉ๋‹ˆ๋‹ค.
  • ๊ฒฐ๊ณผ: ์™ธ๋ถ€ ํ‚ค ์—†์ด ๋กœ์ปฌ ์‹คํ–‰ยทCI๊ฐ€ ๊ฐ€๋Šฅํ•ด์กŒ๊ณ , ๊ธฐ์กด Firebase URL์€ ์‹œ๋“œ ์Šคํฌ๋ฆฝํŠธ๋กœ ๋กœ์ปฌ ํ”Œ๋ ˆ์ด์Šคํ™€๋”๋กœ ๋ฐ”๊ฟ€ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์ฝ”๋“œ: storage/ ยท LocalFileStorageTest

๋ฐฐํฌ

  • ๋ผ์ด๋ธŒ: ์‚ฌ์šฉ์ž https://<IP>.sslip.io ยท ๊ด€๋ฆฌ์ž https://admin.<IP>.sslip.io (๋ฐฐํฌ ํ›„ ์‹ค์ œ ์ฃผ์†Œ๋กœ ๊ต์ฒด)
  • ๊ตฌ์„ฑ: Oracle Cloud Always Free ARM VM ํ•œ ๋Œ€์— Docker Compose โ€” Caddy(์ž๋™ HTTPS + SPA ์ •์  ํŒŒ์ผ) ยท ์‚ฌ์šฉ์ž API ยท ๊ด€๋ฆฌ์ž ์•ฑ ยท MySQL 8.0 ยท Redis 7. SPA ์™€ API ๊ฐ€ ๊ฐ™์€ Origin ์ด๋ผ CORS ์—†์ด ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค.
  • ์„ค์น˜: SSH ์ ‘์† ํ›„ deploy/setup.sh ํ•œ ๋ฒˆ (Docker ์„ค์น˜ยท์Šค์™‘ยท๋ฐฉํ™”๋ฒฝยท.env ๋น„๋ฐ€๊ฐ’ ์ƒ์„ฑยท๋นŒ๋“œยท์‹œ๋“œ) โ†’ ์—…๋ฐ์ดํŠธ deploy/update.sh ยท ๋กค๋ฐฑ deploy/rollback.sh ยท ๋ฐฑ์—… deploy/backup.sh
  • ์šด์˜ ํ”„๋กœํ•„: application-prod.properties โ€” ๋น„๋ฐ€๊ฐ’์€ ์ „๋ถ€ ํ™˜๊ฒฝ๋ณ€์ˆ˜, ๋ฉ”์ผยท๊ฒฐ์ œยท์†Œ์…œ ๋กœ๊ทธ์ธ ํ‚ค๊ฐ€ ์—†์–ด๋„ ๊ธฐ๋™ (๋ฉ”์ผ์€ no-op ๋ฐœ์†ก๊ธฐ๋กœ ๋Œ€์ฒด). ProdProfileStartupTest๊ฐ€ ํ•„์ˆ˜ ํ™˜๊ฒฝ๋ณ€์ˆ˜๋งŒ์œผ๋กœ ์ปจํ…์ŠคํŠธ๊ฐ€ ๋œจ๋Š”์ง€ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค.
  • ์ƒ์„ธ ์ ˆ์ฐจยท๋ฌธ์ œ ํ•ด๊ฒฐ: docs/DEPLOY.md
  • ๋ฌด๋ฃŒ ๋ฐ๋ชจ(Render): render.yaml Blueprint ๋กœ ์‚ฌ์šฉ์ž(SPA+API ํ•œ ์ปจํ…Œ์ด๋„ˆ, Dockerfile.render)ยท๊ด€๋ฆฌ์ž ๋‘ ์„œ๋น„์Šค๋ฅผ ๋„์šด๋‹ค. demo ํ”„๋กœํ•„(application-demo.properties) = H2 ์ธ๋ฉ”๋ชจ๋ฆฌ + db/demo-seed.sql + e2e ์™€ ๊ฐ™์€ ์Šคํ…(ํ† ์Šคยท๋ฉ”์ผยท์ธ๋ฉ”๋ชจ๋ฆฌ Redis), /e2e/** ๋Š” ์—†์Œ โ†’ docs/DEPLOY_RENDER.md

์‹คํ–‰ ๋ฐฉ๋ฒ•

ํ•„์š” ํ™˜๊ฒฝ: JDK 17, MySQL 8, Redis (๊ธฐ๋ณธ ํฌํŠธ 6379)

  1. ํด๋ก : ์ด ์ €์žฅ์†Œ ์ž์ฒด๋Š” ์„œ๋ธŒ๋ชจ๋“ˆ์ด ์—†์ง€๋งŒ, ๊ด€๋ฆฌ์ž ์ €์žฅ์†Œ์™€ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ๋ฐ›๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค.

    git clone --recurse-submodules https://github.com/Seongwonp/honeyRest_user.git
    git clone --recurse-submodules https://github.com/Seongwonp/honeyRest_host.git   # ๊ด€๋ฆฌ์ž ์•ฑ: libs/honeyrest-user ์— ์ด ์ €์žฅ์†Œ๊ฐ€ ์„œ๋ธŒ๋ชจ๋“ˆ๋กœ ๋“ค์–ด์˜ด

    ๋ฃจํŠธ ํ”„๋กœ์ ํŠธ(./gradlew bootRun)๊ฐ€ ์‚ฌ์šฉ์ž API์ด๊ณ , :honeyrest-domain์€ ๋นŒ๋“œ ์‹œ ํ•จ๊ป˜ ์ปดํŒŒ์ผ๋ฉ๋‹ˆ๋‹ค.

  2. ์‹œํฌ๋ฆฟ ํŒŒ์ผ: src/main/resources/application_security_ex.properties๋ฅผ ๋ณต์‚ฌํ•ด ๊ฐ™์€ ํด๋”์— application_security.properties๋ฅผ ๋งŒ๋“ค๊ณ  ๊ฐ’์„ ์ฑ„์›๋‹ˆ๋‹ค(gitignore ๋Œ€์ƒ). DB ๋น„๋ฐ€๋ฒˆํ˜ธ, jwt.secret-key-value, Toss ์œ„์ ฏ ํ‚ค, Google/Kakao OAuth, Gmail, OpenWeather ํ‚ค๊ฐ€ ๋“ค์–ด๊ฐ‘๋‹ˆ๋‹ค.

  3. ํŒŒ์ผ ์ €์žฅ์†Œ: ๊ธฐ๋ณธ๊ฐ’ app.storage.type=local(์—…๋กœ๋“œ๋Š” ./uploads, /uploads/**๋กœ ์„œ๋น™) โ€” Firebase ํ‚ค๊ฐ€ ํ•„์š” ์—†์Šต๋‹ˆ๋‹ค.

    ํ™˜๊ฒฝ๋ณ€์ˆ˜ ํ”„๋กœํผํ‹ฐ ๊ธฐ๋ณธ๊ฐ’
    APP_STORAGE_TYPE app.storage.type local (firebase ์„ ํƒ ๊ฐ€๋Šฅ)
    APP_STORAGE_LOCAL_DIR app.storage.local.dir ./uploads
    FIREBASE_STORAGE_BUCKET / FIREBASE_PROJECT_ID app.storage.firebase.* firebase ๋ชจ๋“œ์—์„œ๋งŒ ์‚ฌ์šฉ
  4. ์‹คํ–‰: ๊ธฐ๋™ ์‹œ Flyway๊ฐ€ honeyrest_db์— V1~V12๋ฅผ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค.

    ./gradlew bootRun                                            # http://localhost:8080
    ./gradlew bootRun --args='--spring.profiles.active=local'    # SQL ๋กœ๊ทธ ์ถœ๋ ฅ
  5. ๋ฐ์ดํ„ฐ ์‹œ๋“œ (๊ถŒ์žฅ ์ˆœ์„œ)

    • ๊ธฐ์ดˆ ๋ฐ์ดํ„ฐ(์ง€์—ญยท์นดํ…Œ๊ณ ๋ฆฌยท์—…์ฒดยท์ˆ™์†Œยท๊ฐ์‹ค): ๊ด€๋ฆฌ์ž ์ €์žฅ์†Œ์˜ db/seed/ SQL (insert.sql โ†’ insert_pk_1-20.sql โ†’ insert_pk_21-30.sql โ†’ insert_pk_31-33.sql)
    • ๋ฐ๋ชจ ๋ฆฌ๋ทฐ ์‚ฌ์šฉ์žยท๋ฆฌ๋ทฐ: scripts/seed-demo-data.sql (๋ฐ˜๋ณต ์‹คํ–‰ ์•ˆ์ „)
    • ๋กœ์ปฌ ์ €์žฅ์†Œ ๋ชจ๋“œ ์ด๋ฏธ์ง€: scripts/seed-local-images.sql (Firebase URL โ†’ /uploads/placeholder/*.svg)
  6. ํฌํŠธ / ์—ฐ๋™: User API 8080 ยท ๊ด€๋ฆฌ์ž ์•ฑ 8081 ยท React ๊ฐœ๋ฐœ ์„œ๋ฒ„ 5173(/api, /uploads๋ฅผ 8080์œผ๋กœ ํ”„๋ก์‹œ)

  7. ๋ฐ๋ชจ ๊ณ„์ •: ์‚ฌ์šฉ์ž๋Š” ํšŒ์›๊ฐ€์ž…์œผ๋กœ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ๊ด€๋ฆฌ์ž ๊ณ„์ •์€ ๊ด€๋ฆฌ์ž ์ €์žฅ์†Œ๋ฅผ local-demo ํ”„๋กœํ•„๋กœ ์‹คํ–‰ํ•˜๋ฉด DataInitializer๊ฐ€ ์ƒ์„ฑํ•˜๋ฉฐ, ๊ณ„์ • ์ •๋ณด๋Š” ํ•ด๋‹น ํŒŒ์ผ์— ์ •์˜๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

Swagger UI: http://localhost:8080/swagger-ui.html ยท ์ƒ์„ธ ์„ค์ •: SETUP.md


ํ…Œ์ŠคํŠธ & CI

./gradlew test               # MySQLยทRedisยท์‹œํฌ๋ฆฟ ํŒŒ์ผ ์—†์ด ์‹คํ–‰ (H2) โ€” ๋ฃจํŠธ(API) + :honeyrest-domain ๋ชจ๋‘ ์‹คํ–‰
./gradlew build              # CI 1๋‹จ๊ณ„์™€ ๋™์ผ
./gradlew integrationTest    # Docker ํ•„์š”: ์‹ค์ œ MySQL 8.0 ์œผ๋กœ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ยท์Šคํ‚ค๋งˆ ๊ฒ€์ฆ (CI 2๋‹จ๊ณ„)
  • 143๊ฐœ ํ…Œ์ŠคํŠธ (API 137 + ๋„๋ฉ”์ธ ๋ชจ๋“ˆ 6) โ€” ๊ฒฐ์ œ ๋ณด์ƒ(PaymentOrchestrationServiceTest), ์žฌ๊ณ  ๊ฒน์นจ(ReserveServiceTest), ๊ฐ€๊ฒฉ ๊ณ„์‚ฐ(PriceCalculatorTest), ํ† ํฐ ํ๊ธฐ(JwtTokenProviderTest), ๊ด€๋ฆฌ์ž ์“ฐ๊ธฐ API ์ธ๊ฐ€(AdminWriteApiSecurityTest), ๊ฒ€์ƒ‰ ์บ์‹œ ํ‚ค, ๋กœ์ปฌ ํŒŒ์ผ ์ €์žฅ์†Œ, ์šด์˜(prod) ํ”„๋กœํ•„ ๊ธฐ๋™(ProdProfileStartupTest) ๋“ฑ
  • test ํ”„๋กœํ•„: H2 ์ธ๋ฉ”๋ชจ๋ฆฌ(MySQL ๋ชจ๋“œ) + ๋”๋ฏธ ์‹œํฌ๋ฆฟ + app.storage.type=local + spring.cache.type=simple โ†’ ์™ธ๋ถ€ ์„œ๋น„์Šค ์—†์ด ์–ด๋””์„œ๋“  ๋™์ผํ•˜๊ฒŒ ๋™์ž‘ํ•˜๊ณ  ๊ฐœ๋ฐœ DB๋ฅผ ๊ฑด๋“œ๋ฆฌ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • ํŠธ๋ ˆ์ด๋“œ์˜คํ”„: V3ยทV10 ๋“ฑ ์ผ๋ถ€ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์ด MySQL ์ „์šฉ ๊ตฌ๋ฌธ(information_schema ์กฐํšŒ + PREPARE)์„ ์จ์„œ ํ…Œ์ŠคํŠธ์—์„œ๋Š” Flyway๋ฅผ ๋„๊ณ  ์—”ํ‹ฐํ‹ฐ๋กœ ์Šคํ‚ค๋งˆ๋ฅผ ์ƒ์„ฑ(create-drop)ํ•ฉ๋‹ˆ๋‹ค. ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ SQL ์ž์ฒด๋Š” ์•„๋ž˜ ์Šคํ‚ค๋งˆ ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ๊ฐ€ ๋”ฐ๋กœ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค. ๋™์‹œ์„ฑ ์—ญ์‹œ ๋ฝ ํ˜ธ์ถœยท๊ฒน์นจ ํŒ์ •์„ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋กœ ๊ฒ€์ฆํ–ˆ๊ณ , ์‹ค์ œ ๋ณ‘๋ ฌ ๋ถ€ํ•˜ ํ…Œ์ŠคํŠธ๋Š” ์•„์ง ์—†์Šต๋‹ˆ๋‹ค.
  • ์Šคํ‚ค๋งˆ ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ (@Tag("integration"), ๊ธฐ๋ณธ test์—์„œ๋Š” ์ œ์™ธ): FlywayMigrationMySqlIntegrationTest๊ฐ€ Testcontainers๋กœ mysql:8.0์„ ๋„์›Œ ๋นˆ DB์— V1~์ตœ์‹ ์„ ์ ์šฉํ•˜๊ณ , โ‘  ๋ชจ๋“  ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์„ฑ๊ณต โ‘ก reservation.accommodation_name NOT NULL โ‘ข ์‚ฌ์šฉ์ž ์—”ํ‹ฐํ‹ฐ ddl-auto=validate ํ†ต๊ณผ โ‘ฃ V10ยทV11 ์žฌ์‹คํ–‰ ์•ˆ์ „์„ฑ์„ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. ๊ด€๋ฆฌ์ž ์•ฑ ์ชฝ์€ honeyRest_host์˜ integrationTest๊ฐ€ ์„œ๋ธŒ๋ชจ๋“ˆ(libs/honeyrest-user)์— ๋“ค์–ด ์žˆ๋Š” ์ด ์ €์žฅ์†Œ์˜ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์œผ๋กœ ๊ณต์œ  ์—”ํ‹ฐํ‹ฐ + ๊ด€๋ฆฌ์ž ์ „์šฉ ์—”ํ‹ฐํ‹ฐ๋ฅผ ๊ต์ฐจ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค.
  • ๋กœ์ปฌ ์‹คํ–‰: Docker(Desktop/Engine)๋ฅผ ์ผ  ๋’ค ./gradlew integrationTest. ์ฒซ ์‹คํ–‰์€ mysql:8.0 ์ด๋ฏธ์ง€ pull๋กœ 1~2๋ถ„ ๊ฑธ๋ฆฝ๋‹ˆ๋‹ค. Docker๊ฐ€ ์—†์œผ๋ฉด ์‹คํŒจ๊ฐ€ ์•„๋‹ˆ๋ผ skip ๋ฉ๋‹ˆ๋‹ค.
  • CI: GitHub Actions โ€” main push/PR๋งˆ๋‹ค JDK 17๋กœ ./gradlew build โ†’ ./gradlew integrationTest(INTEGRATION_REQUIRE_DOCKER=true๋กœ Docker ๋ถ€์žฌ ์‹œ skip ๋Œ€์‹  ์‹คํŒจ), ์‹คํŒจ ์‹œ ํ…Œ์ŠคํŠธ ๋ฆฌํฌํŠธ ์—…๋กœ๋“œ

e2e ํ”„๋กœํ•„ โ€” ํ”„๋ก ํŠธ์—”๋“œ ์‚ฌ์šฉ์ž ์—ฌ์ • E2E ์šฉ ๋ฐฑ์—”๋“œ

./gradlew bootRun --args='--spring.profiles.active=e2e'    # http://localhost:8080, MySQLยทRedisยทFirebaseยท์‹œํฌ๋ฆฟ ํŒŒ์ผ ๋ถˆํ•„์š”

ํ”„๋ก ํŠธ์—”๋“œ ์ €์žฅ์†Œ(honeyrest_user_react)์˜ Playwright ์Šค์œ„ํŠธ(npm run test:e2e)๊ฐ€ ์ด ํ”„๋กœํ•„๋กœ API ๋ฅผ ์ง์ ‘ ๋„์šด๋‹ค. ํ”„๋ก ํŠธ CI ์˜ e2e ์žก์€ ์ด ์ €์žฅ์†Œ main ์„ ์˜†์— ์ฒดํฌ์•„์›ƒํ•ด ๊ฐ™์€ ๋ช…๋ น์œผ๋กœ ์‹คํ–‰ํ•œ๋‹ค.

๊ตฌ์„ฑ e2e ํ”„๋กœํ•„์—์„œ์˜ ๋™์ž‘ ์œ„์น˜
DB H2 ์ธ๋ฉ”๋ชจ๋ฆฌ(MySQL ๋ชจ๋“œ), create-drop, Flyway ๋” โ€” ๊ธฐ๋™ํ•  ๋•Œ๋งˆ๋‹ค ์ƒˆ๋กœ ๋งŒ๋“  ๋’ค ์‹œ๋“œ ์ ์žฌ(spring.sql.init + defer-datasource-initialization) application-e2e.properties, db/e2e-seed.sql
์‹œ๋“œ ์ง€์—ญ 4, ์ˆ™์†Œ 2(๊ฐ์‹ค 3 โ€” ๊ทธ์ค‘ total_rooms=1 ๊ฐ์‹ค 1๊ฐœ), ํƒœ๊ทธ 3, ์ธ์ฆ ์™„๋ฃŒ ํšŒ์› e2e.user@honeyrest.test / Honey1234!, ์ฟ ํฐ 1 db/e2e-seed.sql (BCrypt ํ•ด์‹œ ์žฌ์ƒ์„ฑ ๋ฐฉ๋ฒ•์€ ํŒŒ์ผ ์ฃผ์„)
Redis ์ž๋™ ์„ค์ • ์ œ์™ธ + ๊ฐ™์€ ์ด๋ฆ„์˜ ์ธ๋ฉ”๋ชจ๋ฆฌ RedisTemplate (TTLยท๊ฐ’/๋ฆฌ์ŠคํŠธ/์ง‘ํ•ฉ/์ •๋ ฌ ์ง‘ํ•ฉ/scan) e2e/E2eRedisConfig, e2e/InMemoryRedisTemplate
๋ฉ”์ผ EmailService ํฌํŠธ์˜ ๊ตฌํ˜„์œผ๋กœ SMTP(SmtpEmailService, @Profile("!e2e & !demo")) ๋Œ€์‹  ๋ฐœ์†กํ•˜์ง€ ์•Š๋Š” E2eNoOpEmailService e2e/E2eNoOpEmailService
ํ† ์Šค ๊ฒฐ์ œ TossService ๊ฐ€ ์“ฐ๋Š” TossClient ์ธํ„ฐํŽ˜์ด์Šค์˜ ์Šคํ…: ์–ด๋–ค paymentKey/orderId/amount ๋“  DONE ์Šน์ธ, ์กฐํšŒ๋Š” DONE, ์ทจ์†Œ๋Š” ๊ธฐ๋ก. ๊ธˆ์•กยท์žฌ๊ณ ยท์ค‘๋ณต ๊ฒ€์ฆ์€ ์‹ค์ œ์™€ ๋™์ผ service/payment/TossClient, e2e/E2eTossClient
ํ…Œ์ŠคํŠธ ๋ณด์กฐ API GET /e2e/verification-token?email= (์ตœ๊ทผ ๊ฐ€์ž… ์ธ์ฆ ํ† ํฐ), POST /e2e/reservations/{id}/complete (COMPLETED ์ „ํ™˜), GET /e2e/toss/cancellations e2e/E2eSupportController
  • ์šด์˜ ๋ฐฐ์„ ์€ ๊ทธ๋Œ€๋กœ: ๊ธฐ๋ณธยทlocalยทtest ํ”„๋กœํ•„์€ ์‹ค์ œ HTTP ๊ตฌํ˜„ HttpTossClient(@Profile("!e2e & !demo")), ๋ฉ”์ผ์€ SmtpEmailService(๋ฉ”์ผ ์„ค์ •์ด ์—†์œผ๋ฉด NoOpEmailService), Redis ๋Š” RedisConfig ๋ฅผ ์“ด๋‹ค. H2 ๋Š” developmentOnly(bootRun ์ „์šฉ)๋กœ ์ถ”๊ฐ€ํ•ด bootJar ์—๋Š” ํฌํ•จ๋˜์ง€ ์•Š๋Š”๋‹ค(๋ฐ๋ชจ ์ด๋ฏธ์ง€๋งŒ bootJar -Pdemo ๋กœ ๋„ฃ๋Š”๋‹ค).
  • demo ํ”„๋กœํ•„๊ณผ ๊ณต์œ : ํ† ์Šค ์Šคํ…ยท๋ฉ”์ผ no-opยท์ธ๋ฉ”๋ชจ๋ฆฌ Redis ๋นˆ์€ @Profile({"e2e", "demo"}) ๋กœ Render ๋ฌด๋ฃŒ ๋ฐ๋ชจ(demo)์™€ ํ•จ๊ป˜ ์“ฐ๊ณ , /e2e/** ์ปจํŠธ๋กค๋Ÿฌ๋งŒ @Profile("e2e") ๋กœ ๋‚จ๊ฒจ ๋ฐ๋ชจ์—๋Š” ๋…ธ์ถœํ•˜์ง€ ์•Š๋Š”๋‹ค.
  • /e2e/** ๋Š” e2e ํ”„๋กœํ•„์—๋งŒ ์กด์žฌํ•œ๋‹ค: ์ปจํŠธ๋กค๋Ÿฌ๊ฐ€ @Profile("e2e") ๋ผ ๋‹ค๋ฅธ ํ”„๋กœํ•„์—์„œ๋Š” ๋นˆ ์ž์ฒด๊ฐ€ ์—†์–ด ๊ฒฝ๋กœ๊ฐ€ ๋งคํ•‘๋˜์ง€ ์•Š๋Š”๋‹ค. E2eProfileIsolationTest๊ฐ€ test ํ”„๋กœํ•„์—์„œ e2e ์ „์šฉ ๋นˆ์ด ํ•˜๋‚˜๋„ ์—†๋Š”์ง€์™€ ์šด์˜ ๋ฐฐ์„ ์„ ๊ฒ€์ฆํ•œ๋‹ค. ์šด์˜ DBยท๋น„๋ฐ€๊ฐ’๊ณผ ํ•จ๊ป˜ e2e ํ”„๋กœํ•„์„ ์ผœ์ง€ ๋ง ๊ฒƒ.

ํ™”๋ฉด

์‚ฌ์šฉ์ž ํ™”๋ฉด์€ React ์ €์žฅ์†Œ์—์„œ ์บก์ฒ˜ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ „์ฒด ํ™”๋ฉด: honeyrest_user_react ยท ํ™”๋ฉด

๋ฉ”์ธ ์ˆ™์†Œ ์ƒ์„ธ ยท ๊ฐ์‹ค ์„ ํƒ
๋ฉ”์ธ ์ˆ™์†Œ ์ƒ์„ธ

๊ด‘๊ณ  ์˜์ƒ (ํŒ€ ํ”„๋กœ์ ํŠธ ๋‹น์‹œ ์ œ์ž‘)

HoneyRest ๊ด‘๊ณ  ์˜์ƒ

  • ์‹œ์—ฐ ์˜์ƒ: ์ถ”ํ›„ GitHub Release์— ์ฒจ๋ถ€ ์˜ˆ์ •
  • ๋ฐœํ‘œ ์ž๋ฃŒ: HoneyRest.pdf

ํšŒ๊ณ 


๋ฐ•์„ฑ์› (Seongwon Park) ยท ํŒ€์žฅ / ์‚ฌ์šฉ์ž ์˜์—ญ ์ด๊ด„ ยท GitHub

About

๐Ÿ HoneyRest ๊ฐ์„ฑ ์ˆ™์†Œ ์˜ˆ์•ฝ ํ”Œ๋žซํผ | ์‚ฌ์šฉ์ž(User) ์‹œ์Šคํ…œ (Spring Boot + React + Redis + Firebase)

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages