식당 정보, 코스 메뉴, 현장 체크인 흐름을 관리하는 서비스입니다.
Miche-Let MSA 환경에서 동작하며, Gateway / Eureka / PostgreSQL / Redis / Kafka / reservation-service 와 함께 사용됩니다.
restaurant-service는 다음 3가지 도메인을 담당합니다.
-
🏪 Restaurant
- 식당 등록 / 목록 조회 / 검색 / 상세 조회
- 식당 운영 정보 관리
- 예약 오픈 시각, 평균 식사 시간, 영업시간, 운영 상태 관리
-
🍽️ Course
- 식당별 코스 등록 / 조회
- 코스 메뉴 파트와 정렬 순서 관리
menus[]기반menuComposition자동 생성- 외부 사용자용 코스 목록과 내부 서비스용 코스 요약 조회 분리
-
✅ Check-in
- 점주/관리자 체크인 요청 처리
- reservation-service와 Feign 동기 통신으로 예약 상태 검증
- 체크인 로그 저장
- Kafka
reservation.checked-in이벤트 consume reservationId기준 중복 이벤트 방어
src/main/java/com/michelet/restaurantservice
├─ restaurant
│ ├─ application
│ │ ├─ command
│ │ ├─ query
│ │ ├─ result
│ │ └─ service
│ ├─ domain
│ │ ├─ exception
│ │ ├─ model
│ │ │ └─ vo
│ │ └─ repository
│ ├─ infrastructure
│ │ └─ persistence
│ │ └─ query
│ └─ presentation
│ ├─ controller.external
│ ├─ controller.internal
│ └─ dto
├─ course
│ ├─ application
│ │ ├─ command
│ │ ├─ result
│ │ └─ service
│ ├─ domain
│ │ ├─ exception
│ │ ├─ model
│ │ └─ repository
│ ├─ infrastructure
│ │ └─ persistence
│ └─ presentation
│ ├─ controller.external
│ ├─ controller.internal
│ └─ dto
├─ checkin
│ ├─ application
│ │ ├─ command
│ │ ├─ result
│ │ └─ service
│ ├─ domain
│ │ ├─ exception
│ │ ├─ model
│ │ └─ repository
│ ├─ infrastructure
│ │ ├─ client
│ │ ├─ kafka
│ │ └─ persistence
│ └─ presentation
│ ├─ controller.external
│ └─ dto
├─ global
│ ├─ config
│ └─ exception
├─ health
│ ├─ application
│ └─ presentation
└─ RestaurantServiceApplication
- Java 17
- Spring Boot 3.5.14
- Spring Cloud 2025.0.1
- Spring Data JPA
- QueryDSL
- PostgreSQL
- Redis / Spring Cache
- Kafka
- OpenFeign
- Eureka Client
- Spring Boot Actuator
- Micrometer Prometheus
- Docker
- GitHub Actions
- AWS ECR / ECS
식당의 기본 정보를 관리합니다.
- 식당명
- 주소
- 전화번호
- 설명
- 예약 오픈 시각
- 평균 식사 시간
- 운영 상태
- 영업시간
- 점주 ID
- 식당 등록
- 식당 목록/검색 조회
- 식당 상세 조회
- 내부 서비스용 식당 단건 조회
- 점주 ID 기준 식당 ID 조회
OPEN
CLOSED
식당별 코스와 코스 메뉴 구성을 관리합니다.
- 코스명
- 가격
- 세션 타입
- 판매 상태
- 메뉴 구성 문자열
- 구조화된 코스 메뉴 목록
- 코스 등록
- 외부 사용자용 코스 목록 조회
- 내부 서비스용 코스 요약 목록 조회
menus[]기반menuComposition생성
LUNCH
DINNER
AVAILABLE
UNAVAILABLE
AMUSE_BOUCHE
APPETIZER
FISH
MAIN
DESSERT
- 클라이언트는
menuComposition을 직접 전달하지 않습니다. - 서버가
menus[]의coursePart,menuName,sortOrder를 기준으로menuComposition을 생성합니다. - 코스 메뉴는
course_id + sort_order조합이 유일해야 합니다.
점주의 현장 체크인 요청을 처리합니다.
OWNER / MASTER 체크인 요청
↓
restaurant-service
↓ Feign
reservation-service 예약 검증 및 상태 변경
↓
restaurant-service check-in log 저장
체크인 가능 여부는 reservation-service가 판단합니다.
- 예약 존재 여부
- 예약 식당 일치 여부
- 예약 상태
- 체크인 가능 시간
restaurant-service는 식당 존재 여부와 점주 권한을 확인하고, 예약 상태 변경은 reservation-service에 위임합니다.
p_restaurant_checkin_log에 체크인 이력을 저장합니다.
- restaurantId
- reservationId
- visitDate
- status
- checkedInBy
- checkedInAt
- 애플리케이션 레벨:
reservationId기준 기존 체크인 로그 확인 - DB 레벨:
reservation_idunique constraint
반복 조회 가능성이 높은 API에 Redis Cache-Aside 패턴을 적용합니다.
| Cache Name | API | Key |
|---|---|---|
restaurantDetail |
GET /api/v1/restaurants/{restaurantId} |
restaurant:detail:{restaurantId} |
restaurantCourses |
GET /api/v1/restaurants/{restaurantId}/courses |
restaurant:courses:{restaurantId} |
조회 요청
↓
Redis 캐시 확인
↓
cache hit → DB 조회 없이 반환
cache miss → DB 조회 후 Redis 저장
cache:
ttl:
restaurant-detail: ${CACHE_TTL_RESTAURANT_DETAIL:300}
restaurant-courses: ${CACHE_TTL_RESTAURANT_COURSES:300}GenericJackson2JsonRedisSerializer를 사용합니다.
record DTO 역직렬화 문제를 방지하기 위해 ObjectMapper에 타입 정보를 포함하도록 설정하고, BasicPolymorphicTypeValidator로 허용 패키지를 제한합니다.
reservation-service에서 발행한 체크인 완료 이벤트를 consume합니다.
reservation.checked-in
restaurant-service-check-in-consumer
{
"eventId": "uuid",
"eventType": "CHECK_IN_COMPLETED",
"reservationId": "uuid",
"restaurantId": "uuid",
"visitDate": "yyyy-MM-dd",
"checkedInBy": "uuid",
"checkedInAt": "yyyy-MM-dd'T'HH:mm:ss",
"eventCreatedAt": "yyyy-MM-dd'T'HH:mm:ss"
}- 이벤트 타입이
CHECK_IN_COMPLETED인지 검증 - 필수 필드 누락 여부 검증
reservationId기준 체크인 로그 존재 여부 확인- 이미 존재하면 중복 이벤트로 판단하고 skip
- 존재하지 않으면 식당 존재 여부 확인 후 check-in log 저장
- 동시 처리 중 unique constraint 충돌이 발생하면 중복 이벤트로 판단하고 skip
체크인 요청 시 reservation-service 내부 API를 호출합니다.
PATCH /internal/reservations/check-in
- reservationId
- restaurantId
- checkedInBy
- X-User-Id
- X-User-Role
| reservation-service 응답 | restaurant-service 처리 |
|---|---|
| 404 | 예약 없음 |
| 400 | 예약 체크인 처리 실패 |
| 409 | 예약 체크인 충돌 |
| 기타 Feign 오류 | 잘못된 reservation-service 응답 |
외부 API는 Gateway를 통해 진입하며, 인증 컨텍스트는 common-auth 모듈을 통해 전달됩니다.
USEROWNERMASTER
| 기능 | 허용 Role |
|---|---|
| 식당 등록 | OWNER |
| 식당 목록/검색 조회 | USER, OWNER, MASTER |
| 식당 상세 조회 | USER, OWNER, MASTER |
| 코스 등록 | OWNER |
| 코스 목록 조회 | USER, OWNER |
| 체크인 처리 | OWNER, MASTER |
Base Path: /api/v1/restaurants
| Method | Path | Description |
|---|---|---|
| POST | /api/v1/restaurants |
식당 등록 |
| GET | /api/v1/restaurants |
식당 목록/검색 조회 |
| GET | /api/v1/restaurants/{restaurantId} |
식당 상세 조회 |
| GET | /api/v1/restaurants/health |
restaurant-service health 확인 |
| Parameter | Required | Description |
|---|---|---|
keyword |
false | 식당명 검색어 |
region |
false | 주소 기반 지역 검색어 |
status |
false | 식당 상태 필터 (OPEN, CLOSED) |
page |
false | 페이지 번호 |
size |
false | 페이지 크기 |
sort |
false | 정렬 기준 |
Base Path: /api/v1/restaurants/{restaurantId}/courses
| Method | Path | Description |
|---|---|---|
| POST | /api/v1/restaurants/{restaurantId}/courses |
코스 등록 |
| GET | /api/v1/restaurants/{restaurantId}/courses |
외부 사용자용 코스 목록 조회 |
Base Path: /api/v1/restaurants
| Method | Path | Description |
|---|---|---|
| PATCH | /api/v1/restaurants/{restaurantId}/reservations/{reservationId}/check-in |
예약 체크인 처리 |
Base Path: /internal/v1/restaurants
| Method | Path | Description |
|---|---|---|
| GET | /internal/v1/restaurants/{restaurantId} |
내부 식당 단건 조회 |
| GET | /internal/v1/restaurants/owners/{ownerId}/id |
점주 ID 기준 식당 ID 조회 |
Base Path: /internal/v1/restaurants/{restaurantId}/courses
| Method | Path | Description |
|---|---|---|
| GET | /internal/v1/restaurants/{restaurantId}/courses |
내부 코스 요약 목록 조회 |
{
"name": "MicheLet Dining",
"address": "서울특별시 강남구 테헤란로 123",
"phone": "02-1234-5678",
"description": "파인다이닝 레스토랑",
"reservationOpenAt": "10:00:00",
"avgMealDurationMin": 90,
"status": "OPEN",
"businessHours": "MON-FRI 11:00-20:00 / SAT,SUN CLOSED"
}{
"name": "Dinner Course",
"price": 150000,
"sessionType": "DINNER",
"status": "AVAILABLE",
"menus": [
{
"coursePart": "AMUSE_BOUCHE",
"menuName": "한우 타르타르",
"sortOrder": 1
},
{
"coursePart": "APPETIZER",
"menuName": "제철 샐러드",
"sortOrder": 2
},
{
"coursePart": "FISH",
"menuName": "제철 생선 구이",
"sortOrder": 3
},
{
"coursePart": "MAIN",
"menuName": "양갈비",
"sortOrder": 4
},
{
"coursePart": "DESSERT",
"menuName": "바닐라 무스",
"sortOrder": 5
}
]
}테스트는 아래 영역을 포함합니다.
- ✅ Restaurant Query Service Test
- ✅ Restaurant Controller Test
- ✅ Restaurant Internal Controller Test
- ✅ Course Command Service Test
- ✅ Course Query Service Test
- ✅ Course Controller Test
- ✅ Course Internal Controller Test
- ✅ CheckInCompletedEventHandler Test
- ✅ CheckInCompletedEventConsumer Test
- ✅ Redis Cache Test
- ✅ QueryDSL Repository Test
./gradlew clean testHTTP Client 기반 MVP / 배포환경 테스트 파일을 제공합니다.
src/test/http
├─ checkin-event-outbox-run.http
├─ checkin-event-outbox-setup.http
├─ restaurant-cache.http
└─ restaurant-internal.http
src/test/mvp
├─ owner.http
├─ user.http
├─ owner-checkin.http
├─ prod-restaurant-api-scenario.http
├─ prod-full-checkin-e2e.http
└─ sql
├─ seed_mvp_data.sql
└─ reset_mvp_data.sql
- OWNER 회원가입 / 로그인
- 식당 등록
- 코스 등록
- USER 회원가입 / 로그인
- 식당 목록 조회
- 식당 상세 조회
- 코스 목록 조회
- 타임슬롯 생성 / 조회
- 대기열 입장
- 예약 생성
- 체크인 처리
- 중복 체크인 확인
체크인은 예약 시작 시각 기준 제한된 시간 안에서만 가능합니다.
배포환경 테스트에서는 현재 한국 시간 기준으로 slotStartTime을 설정하고, 타임슬롯 조회 결과에서 정확히 일치하는 timeSlotId를 사용해야 합니다.
k6 기반 조회 API 부하테스트 스크립트를 제공합니다.
load-tests/restaurant
├─ restaurant-read-as-is.js
└─ restaurant-read-to-be-cache.js
- Redis 캐시 적용 전/후 조회 성능 비교
- 반복 조회 시 DB select 감소 여부 확인
- cache hit/miss 지표 확인
- p95 / p99 응답 시간 확인
Actuator와 Prometheus endpoint를 노출합니다.
/actuator/health
/actuator/metrics
/actuator/prometheus
Prometheus / Grafana에서 아래 지표를 확인할 수 있습니다.
- HTTP request count
- JVM heap / non-heap memory
- CPU
- HikariCP connection pool
- Redis cache hit/miss
- Kafka consumer 상태
아래 서비스가 먼저 실행되어 있어야 합니다.
- PostgreSQL
- Redis
- Kafka
- Eureka Server 선택
- API Gateway 선택
- reservation-service 선택
체크인 API를 로컬에서 실제로 검증하려면 reservation-service가 필요합니다.
.env.example 기준으로 필요한 값입니다.
SPRING_PROFILES_ACTIVE=local
SERVER_PORT=19300
DB_URL=jdbc:postgresql://localhost:5432/michelet_db
DB_USERNAME=your_db_username
DB_PASSWORD=your_db_password
POSTGRES_HOST=localhost
POSTGRES_PORT=5432
POSTGRES_DB=michelet_db
POSTGRES_USER=your_db_username
POSTGRES_PASSWORD=your_db_password
REDIS_HOST=localhost
REDIS_PORT=6379
EUREKA_CLIENT_ENABLED=false
EUREKA_DEFAULT_ZONE=http://localhost:8761/eureka/
EUREKA_ENABLED=false
EUREKA_HOST=localhost
EUREKA_PORT=8761
FEIGN_RESERVATION_SERVICE_URL=http://localhost:19500
INTERNAL_AUTH_SECRET=your_internal_auth_secret_at_least_32_bytes
JWT_SECRET=your_jwt_secret_at_least_32_bytes
KAFKA_BOOTSTRAP_SERVERS=localhost:9092
KAFKA_CONSUMER_GROUP_ID=restaurant-service-check-in-consumer
KAFKA_TOPIC_RESERVATION_CHECK_IN_COMPLETED=reservation.checked-in
KAFKA_LISTENER_AUTO_STARTUP=true
CACHE_TTL_RESTAURANT_DETAIL=300
CACHE_TTL_RESTAURANT_COURSES=300| 변수명 | 설명 |
|---|---|
SPRING_PROFILES_ACTIVE |
활성 profile (local, docker, prod) |
SERVER_PORT |
restaurant-service 실행 포트 |
DB_URL |
local/docker profile DB URL |
DB_USERNAME |
local/docker profile DB 사용자명 |
DB_PASSWORD |
local/docker profile DB 비밀번호 |
POSTGRES_HOST |
prod profile PostgreSQL host |
POSTGRES_PORT |
prod profile PostgreSQL port |
POSTGRES_DB |
prod profile PostgreSQL database |
POSTGRES_USER |
prod profile PostgreSQL 사용자명 |
POSTGRES_PASSWORD |
prod profile PostgreSQL 비밀번호 |
REDIS_HOST |
Redis host |
REDIS_PORT |
Redis port |
EUREKA_CLIENT_ENABLED |
local/docker profile Eureka client 활성화 여부 |
EUREKA_DEFAULT_ZONE |
local/docker profile Eureka server URL |
EUREKA_ENABLED |
prod profile Eureka client 활성화 여부 |
EUREKA_HOST |
prod profile Eureka host |
EUREKA_PORT |
prod profile Eureka port |
FEIGN_RESERVATION_SERVICE_URL |
reservation-service 직접 호출 URL. 비워두면 Eureka name 기반 호출 |
INTERNAL_AUTH_SECRET |
내부 API 인증 secret |
JWT_SECRET |
JWT secret |
KAFKA_BOOTSTRAP_SERVERS |
Kafka bootstrap servers |
KAFKA_CONSUMER_GROUP_ID |
Kafka consumer group id |
KAFKA_TOPIC_RESERVATION_CHECK_IN_COMPLETED |
체크인 완료 이벤트 topic |
KAFKA_LISTENER_AUTO_STARTUP |
Kafka listener 자동 시작 여부 |
CACHE_TTL_RESTAURANT_DETAIL |
식당 상세 캐시 TTL seconds |
CACHE_TTL_RESTAURANT_COURSES |
코스 목록 캐시 TTL seconds |
- 서비스명:
restaurant-service - 기본 server port:
19300 - Redis cache 사용
- Kafka consumer 기본 설정
- Actuator / Prometheus endpoint 노출
- local profile DB 연결
- Redis host 기본값:
localhost - JPA ddl-auto:
update - SQL 로그 출력
- Eureka client 기본 비활성화
- prod profile DB URL을
POSTGRES_HOST,POSTGRES_PORT,POSTGRES_DB로 조합 - HikariCP pool size 환경변수 기반 설정
- Redis / Kafka / Eureka / Feign / Cache TTL 환경변수 기반 설정
- Prometheus metrics 노출
./gradlew bootRunSPRING_PROFILES_ACTIVE=local ./gradlew bootRun또는 IntelliJ에서 RestaurantServiceApplication을 실행합니다.
docker build -t restaurant-service:local .docker run --rm -p 19300:19300 \
--env-file .env.example \
restaurant-service:localDockerfile은 multi-stage build를 사용합니다.
Gradle JDK 17 builder stage
↓
bootJar 생성
↓
Eclipse Temurin JRE 17 runtime stage
↓
app.jar 실행
GitHub Actions 기반 CI/CD를 제공합니다.
Pull Request / Push
↓
JDK 17 설정
↓
Gradle test
↓
Docker image build
↓
ECR push
- 이미지 태그는 commit SHA 앞 7자리를 사용합니다.
- AWS 인증은 OIDC Role 기반으로 처리합니다.
CI-PROD 성공
↓
ECR image 존재 확인
↓
현재 ECS Task Definition 조회
↓
새 Task Definition revision 생성
↓
ECS Service update
↓
services-stable 대기
배포 시 container environment에 DB, Redis, Kafka, Eureka, cache TTL 값을 주입합니다.
restaurant-service는 식당/코스/체크인 로그를 소유합니다.
예약 상태의 source of truth는 reservation-service가 소유합니다.
체크인 요청은 restaurant-service에서 받지만, 예약 상태 검증과 상태 변경은 reservation-service에 위임합니다.
즉시 응답이 필요한 체크인 검증은 Feign 동기 통신으로 처리합니다.
체크인 완료 이벤트 기반 로그 저장은 Kafka 비동기 consume으로 처리합니다.
즉시 검증: Feign
이벤트 처리: Kafka
중복 방어: reservationId 기준 idempotency
식당 상세 / 코스 목록 조회는 반복 조회 가능성이 높기 때문에 Redis Cache-Aside 패턴을 적용합니다.
- cache hit: DB 접근 없이 응답
- cache miss: DB 조회 후 Redis 저장
record DTO 역직렬화 이슈를 방지하기 위해 Redis value serializer의 타입 정보 설정을 명시합니다.
공통 BaseEntity를 사용하며, 주요 조회에서는 삭제되지 않은 데이터 기준으로 처리합니다.
하나의 예약에 대해 체크인 로그는 한 번만 저장되어야 합니다.
uk_checkin_log_reservation_id
Kafka 재전달이나 동시 consume 상황에서도 reservationId 기준으로 중복 저장을 방어합니다.
.
├─ build.gradle
├─ settings.gradle
├─ Dockerfile
├─ .env.example
├─ load-tests
│ └─ restaurant
├─ src
│ ├─ main
│ │ ├─ java
│ │ └─ resources
│ └─ test
│ ├─ http
│ ├─ java
│ ├─ mvp
│ └─ resources
└─ gradle
이 서비스는 다음을 책임집니다.
- 🏪 식당 기본 정보 관리
- 🍽️ 식당 코스 및 코스 메뉴 관리
- 🔍 사용자용 식당/코스 조회
- 🔗 내부 서비스용 식당/코스 조회
- ✅ 예약 연동 체크인 처리
- 📩 Kafka 체크인 완료 이벤트 consume
- 🧱
reservationId기준 체크인 로그 중복 방어 - 🚀 Redis Cache-Aside 기반 조회 성능 개선
- 📊 Prometheus/Grafana 기반 모니터링 지표 노출
- 🧪 HTTP Client 기반 배포환경 API 시나리오 검증