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
- ์ฌ์ฉ์ 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-usergit 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
- ๋ฌธ์ : ํ ์ค ์น์ธ(์ธ๋ถ ๊ณผ๊ธ)๊ณผ ์์ฝ INSERT(๋ด๋ถ DB)๋ ํ๋์ ํธ๋์ญ์ ์ผ๋ก ๋ฌถ์ ์ ์์ด, ์น์ธ ํ DB ์ค๋ฅยท๋งค์ง ์ "๋์ ๋น ์ก๋๋ฐ ์์ฝ์ ์๋" ์ํ๊ฐ ์๊ธธ ์ ์์์ต๋๋ค.
- ๊ฒฐ์ : โ ๊ณผ๊ธ ์ ์ฌ์ ๊ฒ์ฆ(์ค๋ณต
paymentKeyยท์์ฝ๋ฒํธ, ์ฃผ๋ฌธ๋ฒํธ, ์๋ฒ ์ฌ๊ณ์ฐ ๊ธ์ก, ์ฌ๊ณ ) โ โก ํ ์ค ์น์ธ โ โขTransactionTemplate์์์ ๊ฐ์ค ๋ฝ + ์ฌ๊ณ ์ฌํ์ธ ํ ์ ์ฅ โ โฃ โข ์คํจ ์TossService.cancelPayment๋ก ์๋ ์ทจ์. ๊ฐ์paymentKey๊ฐ ๋์ ์์ฒญ์ผ๋ก ์ด๋ฏธ ์ ์ฅ๋๋ค๋ฉด ์ ์ ๊ฒฐ์ ์ด๋ฏ๋ก ์ทจ์ํ์ง ์๊ณ , ์ทจ์๋ง์ ์คํจํ๋ฉดorderId์ ํจ๊ป ์๋ ํ๋ถ ๋ก๊ทธ๋ฅผ ๋จ๊น๋๋ค. - ๊ฒฐ๊ณผ: ์น์ธ ํ ์คํจยท๋งค์งยท์ค๋ณต ์์ฒญ ์๋๋ฆฌ์ค 7๊ฑด์ ๋จ์ ํ
์คํธ๋ก ๊ณ ์ . ์์ฝ๋ฒํธ
UNIQUE์ ์ฝ์ด ์ต์ข ๋ฉฑ๋ฑ์ฑ ๋ฐฉ์ด์ ์ ๋๋ค. - ์ฝ๋:
PaymentOrchestrationServiceยท ํ ์คํธ
- ๋ฌธ์ : ์์ฝ ์ ์ฌ๊ณ ๊ฒ์ฌ๊ฐ ์์ด ๊ฐ์ ๊ฐ์คยท๊ธฐ๊ฐ์
total_rooms๋ฅผ ์ด๊ณผํ๋ ์์ฝ์ด ์ ์ฅ๋ ์ ์์์ต๋๋ค. - ๊ฒฐ์ : ์ฌ๊ณ =
room.total_rooms โ [์ฒดํฌ์ธ, ์ฒดํฌ์์)๊ณผ ๊ฒน์น๋ ์ ์ ์ํ ์์ฝ ์. ์ ์ฅ ์ง์ RoomRepository.findByIdForUpdate(PESSIMISTIC_WRITE)๋ก ๊ฐ์ค ํ์ ์ ๊ทธ๊ณReservationRepository.countOverlapping์ผ๋ก ์ฌํ์ธ, ๋ถ์กฑํ๋ฉด 409. ๊ณต์ ์คํค๋ง์version์ปฌ๋ผ์ด ์์ด ๋๊ด์ ๋ฝ ๋์ ํ ๋ฝ์ ํํ๊ณ , ๊ด๋ฆฌ์ ํ๋ฉด์ด ์ฐ๋ ์ค๋ ์ท ๊ฐprice_calendar.available_room์ ํ์ ์ ์ฐ์ง ์์ต๋๋ค. - ๊ฒฐ๊ณผ: ๊ฐ์ ๊ฐ์ค์ ์์ฝ ํธ๋์ญ์
์ด ์ง๋ ฌํ๋๊ณ , ๊ด๋ฆฌ์ ์ ์ฅ์๋ ๋์ผ ๊ท์น(
ReservationInventoryGuard)์ ์ ์ฉํด ๋ ์ฑ์ด ๊ฐ์ ์ฌ๊ณ ๊ธฐ์ค์ ์ฌ์ฉํฉ๋๋ค. - ์ฝ๋:
ReserveServiceยทReservationRepositoryยทReservationStatus
- ๋ฌธ์ : ์์ฝ ํผ๊ณผ ๊ฒฐ์ ๊ฒ์ฆ์ด ๊ฐ์ ๊ธ์ก์ ๊ณ์ฐํด ๋ ์ง๋ณ ์๊ธ(
price_calendar)ยท์ถ๊ฐ ์ธ์ ์๊ธ ๋ฐ์ ์ฌ๋ถ๊ฐ ๋ฌ๋๊ณ , ํด๋ผ์ด์ธํธ๊ฐ ๋ณด๋ธ ๊ธ์ก์ ๊ทธ๋๋ก ๋ฏฟ๋ ๊ฒฝ๋ก๋ ์์์ต๋๋ค. - ๊ฒฐ์ : 1๋ฐ ์๊ธ(
price_calendar์ฐ์ , ์์ผ๋ฉด ๊ธฐ๋ณธ๊ฐ), ๊ณผ๊ธ ์ผ์(์ฒดํฌ์์ ๋น์ผ ์ ์ธ), ์ถ๊ฐ ์ธ์ ์๊ธ ๊ท์น์PriceCalculatorํ ๊ณณ์ ๋๊ณ ์์ฝ ํผ ์กฐํ์ ๊ฒฐ์ ๊ฒ์ฆ์ด ๋ชจ๋ ์ด๋ฅผ ํธ์ถ. ์ฟ ํฐยทํฌ์ธํธ๋ ์๋ฒ์์ ์์ ์ยท๊ธฐ๊ฐยท์ต์ ๊ธ์ก์ ์ฌ๊ฒ์ฆํฉ๋๋ค. - ๊ฒฐ๊ณผ: ํ๋ฉด ํ์ ๊ธ์ก = ์์ฝ ์ ์ฅ ๊ธ์ก = ํ ์ค ์์ฒญ ๊ธ์ก. ๊ธ์ก์ด ๋ค๋ฅด๋ฉด ํ ์ค ํธ์ถ ์ ์ ๊ฑฐ์ ๋ฉ๋๋ค.
- ์ฝ๋:
PriceCalculatorยท ํ ์คํธ
- ๋ฌธ์ : ๋ฌด์ํ 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
- ๋ฌธ์ : ๊ฒ์ ๊ฒฐ๊ณผ๋ง๋ค ๊ฐ์ค ์ฌ๊ณ ยทํ๊ทธ๋ฅผ ๋ถ์ฌ์ผ ํด ์ํฐํฐ ์ฐ๊ด์ ๋ฐ๋ผ๊ฐ๋ฉด N+1์ด ๋๊ธฐ ์ฌ์ด ํ๋ฉด์ ๋๋ค. ๋ ๊ธฐ์กด Redis ์บ์ ํค์ ๋ ์งยท์ธ์ยท์ฌ์ฉ์ยท์ต๋๊ฐ๊ฒฉ์ด ๋น ์ ธ ์์ฝ ๊ฐ๋ฅ ์ฌ๋ถยท์ฐ ์ํ๊ฐ ๋ค๋ฅธ ์์ฒญ์ด ๊ฐ์ ์บ์๋ฅผ ๊ณต์ ํ๊ณ , ํ์ฌ ํ์ด์ง ๋ชฉ๋ก ํฌ๊ธฐ๋ฅผ ์ ์ฒด ๊ฑด์๋ก ์จ์ ์์ 33๊ฐ๊ฐ 20๊ฐยท1ํ์ด์ง๋ก ์ถ์๋๋ ๋ฒ๊ทธ๊ฐ ์์์ต๋๋ค.
- ๊ฒฐ์ : ์์ ๋ชฉ๋ก์ QueryDSL
Projections.constructor๋ก DTO๋ฅผ ํ ๋ฒ์ ์กฐํ(ํ์ ยท๋ฆฌ๋ทฐ ์ยท์ฐ ์ฌ๋ถ ์ง๊ณ ํฌํจ)ํ๊ณ , ์์ฝ ์ยท๊ฐ์คยทํ๊ทธ๋ ์์ IDIN์กฐํ 3ํ๋ก ๋ฌถ์ด ๋ฉ๋ชจ๋ฆฌ์์ ๋งคํ. ํ๊ทธ ํํฐ๋groupBy + having countDistinct์๋ธ์ฟผ๋ฆฌ. ์บ์ ํค๋ ์์นยท์ขํยท๋ ์งยท์ธ์ยท์ฌ์ฉ์ยท์ ๋ ฌยท์นดํ ๊ณ ๋ฆฌ/ํ๊ทธ(์ ๋ ฌ)ยท๊ฐ๊ฒฉยทํ์ด์ง๋ฅผ ์ ๊ทํํดsearch:recommend:v2์ ๋์ฌ๋ก ๊ตฌ์ฑํ๊ณ , ์ ์ฒด ๊ฑด์๋ ๋ณ๋ ํค๋ก ์ ์ฅ(TTL 6์๊ฐ)ํด ๋ ๋ค ์์ ๋๋ง ์ ์ค์ผ๋ก ์ธ์ ํฉ๋๋ค. - ๊ฒฐ๊ณผ: ํ์ด์ง ํฌ๊ธฐ์ ๋ฌด๊ดํ๊ฒ ์ฟผ๋ฆฌ ์๊ฐ ์ผ์ ํ๊ณ , ์บ์ ์ ์ค ์์๋ ํ์ด์ง ์ ๋ณด๊ฐ ๋ณด์กด๋ฉ๋๋ค. ์บ์ ํค ๊ท์น์ ๋จ์ ํ ์คํธ๋ก ๊ณ ์ ํ์ต๋๋ค.
- ์ฝ๋:
AccommodationSearchImplยท ์บ์ ํค ํ ์คํธ ยท ZSet ์ธ๊ธฐ ์ง์ญยท์ธ๊ธฐ ์์ ๋ญํน:RegionService,AccommodationService
- ๋ฌธ์ : ๊ด๋ฆฌ์ ์ฑ์ด
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
- ๋ฌธ์ : ์ ๋ก๋๊ฐ 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.yamlBlueprint ๋ก ์ฌ์ฉ์(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)
-
ํด๋ก : ์ด ์ ์ฅ์ ์์ฒด๋ ์๋ธ๋ชจ๋์ด ์์ง๋ง, ๊ด๋ฆฌ์ ์ ์ฅ์์ ๊ฐ์ ๋ฐฉ์์ผ๋ก ๋ฐ๋ ๊ฒ์ ๊ถ์ฅํฉ๋๋ค.
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์ ๋น๋ ์ ํจ๊ป ์ปดํ์ผ๋ฉ๋๋ค. -
์ํฌ๋ฆฟ ํ์ผ:
src/main/resources/application_security_ex.properties๋ฅผ ๋ณต์ฌํด ๊ฐ์ ํด๋์application_security.properties๋ฅผ ๋ง๋ค๊ณ ๊ฐ์ ์ฑ์๋๋ค(gitignore ๋์). DB ๋น๋ฐ๋ฒํธ,jwt.secret-key-value, Toss ์์ ฏ ํค, Google/Kakao OAuth, Gmail, OpenWeather ํค๊ฐ ๋ค์ด๊ฐ๋๋ค. -
ํ์ผ ์ ์ฅ์: ๊ธฐ๋ณธ๊ฐ
app.storage.type=local(์ ๋ก๋๋./uploads,/uploads/**๋ก ์๋น) โ Firebase ํค๊ฐ ํ์ ์์ต๋๋ค.ํ๊ฒฝ๋ณ์ ํ๋กํผํฐ ๊ธฐ๋ณธ๊ฐ APP_STORAGE_TYPEapp.storage.typelocal(firebase์ ํ ๊ฐ๋ฅ)APP_STORAGE_LOCAL_DIRapp.storage.local.dir./uploadsFIREBASE_STORAGE_BUCKET/FIREBASE_PROJECT_IDapp.storage.firebase.*firebase ๋ชจ๋์์๋ง ์ฌ์ฉ -
์คํ: ๊ธฐ๋ ์ Flyway๊ฐ
honeyrest_db์ V1~V12๋ฅผ ์ ์ฉํฉ๋๋ค../gradlew bootRun # http://localhost:8080 ./gradlew bootRun --args='--spring.profiles.active=local' # SQL ๋ก๊ทธ ์ถ๋ ฅ
-
๋ฐ์ดํฐ ์๋ (๊ถ์ฅ ์์)
- ๊ธฐ์ด ๋ฐ์ดํฐ(์ง์ญยท์นดํ
๊ณ ๋ฆฌยท์
์ฒดยท์์ยท๊ฐ์ค): ๊ด๋ฆฌ์ ์ ์ฅ์์
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)
- ๊ธฐ์ด ๋ฐ์ดํฐ(์ง์ญยท์นดํ
๊ณ ๋ฆฌยท์
์ฒดยท์์ยท๊ฐ์ค): ๊ด๋ฆฌ์ ์ ์ฅ์์
-
ํฌํธ / ์ฐ๋: User API
8080ยท ๊ด๋ฆฌ์ ์ฑ8081ยท React ๊ฐ๋ฐ ์๋ฒ5173(/api,/uploads๋ฅผ 8080์ผ๋ก ํ๋ก์) -
๋ฐ๋ชจ ๊ณ์ : ์ฌ์ฉ์๋ ํ์๊ฐ์ ์ผ๋ก ์์ฑํฉ๋๋ค. ๊ด๋ฆฌ์ ๊ณ์ ์ ๊ด๋ฆฌ์ ์ ์ฅ์๋ฅผ
local-demoํ๋กํ๋ก ์คํํ๋ฉดDataInitializer๊ฐ ์์ฑํ๋ฉฐ, ๊ณ์ ์ ๋ณด๋ ํด๋น ํ์ผ์ ์ ์๋์ด ์์ต๋๋ค.
Swagger UI: http://localhost:8080/swagger-ui.html ยท ์์ธ ์ค์ : SETUP.md
./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_nameNOT 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 โ
mainpush/PR๋ง๋ค JDK 17๋ก./gradlew buildโ./gradlew integrationTest(INTEGRATION_REQUIRE_DOCKER=true๋ก Docker ๋ถ์ฌ ์ skip ๋์ ์คํจ), ์คํจ ์ ํ ์คํธ ๋ฆฌํฌํธ ์ ๋ก๋
./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 ยท ํ๋ฉด
| ๋ฉ์ธ | ์์ ์์ธ ยท ๊ฐ์ค ์ ํ |
|---|---|
![]() |
![]() |
๊ด๊ณ ์์ (ํ ํ๋ก์ ํธ ๋น์ ์ ์)
- ์์ฐ ์์: ์ถํ GitHub Release์ ์ฒจ๋ถ ์์
- ๋ฐํ ์๋ฃ: HoneyRest.pdf
- ํ๋ก์ ํธ ํ๊ณ (RETROSPECTIVE.md)
- ํ์ ํ๊ณ : honeyRest_host ยท ํ๊ณ
๋ฐ์ฑ์ (Seongwon Park) ยท ํ์ฅ / ์ฌ์ฉ์ ์์ญ ์ด๊ด ยท GitHub


