Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

🍽️ restaurant-service

식당 정보, 코스 메뉴, 현장 체크인 흐름을 관리하는 서비스입니다.
Miche-Let MSA 환경에서 동작하며, Gateway / Eureka / PostgreSQL / Redis / Kafka / reservation-service 와 함께 사용됩니다.


📌 Overview

restaurant-service는 다음 3가지 도메인을 담당합니다.

  • 🏪 Restaurant

    • 식당 등록 / 목록 조회 / 검색 / 상세 조회
    • 식당 운영 정보 관리
    • 예약 오픈 시각, 평균 식사 시간, 영업시간, 운영 상태 관리
  • 🍽️ Course

    • 식당별 코스 등록 / 조회
    • 코스 메뉴 파트와 정렬 순서 관리
    • menus[] 기반 menuComposition 자동 생성
    • 외부 사용자용 코스 목록과 내부 서비스용 코스 요약 조회 분리
  • ✅ Check-in

    • 점주/관리자 체크인 요청 처리
    • reservation-service와 Feign 동기 통신으로 예약 상태 검증
    • 체크인 로그 저장
    • Kafka reservation.checked-in 이벤트 consume
    • reservationId 기준 중복 이벤트 방어

🧱 Package Structure

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

⚙️ Tech Stack

  • 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

🧩 Domain Details

🏪 Restaurant

식당의 기본 정보를 관리합니다.

관리 항목

  • 식당명
  • 주소
  • 전화번호
  • 설명
  • 예약 오픈 시각
  • 평균 식사 시간
  • 운영 상태
  • 영업시간
  • 점주 ID

주요 기능

  • 식당 등록
  • 식당 목록/검색 조회
  • 식당 상세 조회
  • 내부 서비스용 식당 단건 조회
  • 점주 ID 기준 식당 ID 조회

상태

OPEN
CLOSED

🍽️ Course

식당별 코스와 코스 메뉴 구성을 관리합니다.

관리 항목

  • 코스명
  • 가격
  • 세션 타입
  • 판매 상태
  • 메뉴 구성 문자열
  • 구조화된 코스 메뉴 목록

주요 기능

  • 코스 등록
  • 외부 사용자용 코스 목록 조회
  • 내부 서비스용 코스 요약 목록 조회
  • menus[] 기반 menuComposition 생성

세션 타입

LUNCH
DINNER

코스 상태

AVAILABLE
UNAVAILABLE

코스 메뉴 파트

AMUSE_BOUCHE
APPETIZER
FISH
MAIN
DESSERT

내부 정책

  • 클라이언트는 menuComposition을 직접 전달하지 않습니다.
  • 서버가 menus[]의 coursePart, menuName, sortOrder를 기준으로 menuComposition을 생성합니다.
  • 코스 메뉴는 course_id + sort_order 조합이 유일해야 합니다.

✅ Check-in

점주의 현장 체크인 요청을 처리합니다.

체크인 처리 흐름

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_id unique constraint

🚀 Redis Cache

반복 조회 가능성이 높은 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}

Cache-Aside 흐름

조회 요청
↓
Redis 캐시 확인
↓
cache hit  → DB 조회 없이 반환
cache miss → DB 조회 후 Redis 저장

TTL 설정

cache:
  ttl:
    restaurant-detail: ${CACHE_TTL_RESTAURANT_DETAIL:300}
    restaurant-courses: ${CACHE_TTL_RESTAURANT_COURSES:300}

직렬화 정책

GenericJackson2JsonRedisSerializer를 사용합니다.
record DTO 역직렬화 문제를 방지하기 위해 ObjectMapper에 타입 정보를 포함하도록 설정하고, BasicPolymorphicTypeValidator로 허용 패키지를 제한합니다.


📩 Kafka Integration

reservation-service에서 발행한 체크인 완료 이벤트를 consume합니다.

Topic

reservation.checked-in

Consumer Group

restaurant-service-check-in-consumer

Event Payload

{
  "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"
}

처리 정책

  1. 이벤트 타입이 CHECK_IN_COMPLETED인지 검증
  2. 필수 필드 누락 여부 검증
  3. reservationId 기준 체크인 로그 존재 여부 확인
  4. 이미 존재하면 중복 이벤트로 판단하고 skip
  5. 존재하지 않으면 식당 존재 여부 확인 후 check-in log 저장
  6. 동시 처리 중 unique constraint 충돌이 발생하면 중복 이벤트로 판단하고 skip

🔗 Feign Client

체크인 요청 시 reservation-service 내부 API를 호출합니다.

Reservation Client

PATCH /internal/reservations/check-in

전달 정보

  • reservationId
  • restaurantId
  • checkedInBy
  • X-User-Id
  • X-User-Role

실패 매핑

reservation-service 응답 restaurant-service 처리
404 예약 없음
400 예약 체크인 처리 실패
409 예약 체크인 충돌
기타 Feign 오류 잘못된 reservation-service 응답

🔐 Authentication / Authorization

외부 API는 Gateway를 통해 진입하며, 인증 컨텍스트는 common-auth 모듈을 통해 전달됩니다.

주요 Role

  • USER
  • OWNER
  • MASTER

권한 정책

기능 허용 Role
식당 등록 OWNER
식당 목록/검색 조회 USER, OWNER, MASTER
식당 상세 조회 USER, OWNER, MASTER
코스 등록 OWNER
코스 목록 조회 USER, OWNER
체크인 처리 OWNER, MASTER

🌐 External API

Restaurant API

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 확인

Search Parameters

Parameter Required Description
keyword false 식당명 검색어
region false 주소 기반 지역 검색어
status false 식당 상태 필터 (OPEN, CLOSED)
page false 페이지 번호
size false 페이지 크기
sort false 정렬 기준

Course API

Base Path: /api/v1/restaurants/{restaurantId}/courses

Method Path Description
POST /api/v1/restaurants/{restaurantId}/courses 코스 등록
GET /api/v1/restaurants/{restaurantId}/courses 외부 사용자용 코스 목록 조회

Check-in API

Base Path: /api/v1/restaurants

Method Path Description
PATCH /api/v1/restaurants/{restaurantId}/reservations/{reservationId}/check-in 예약 체크인 처리

🔗 Internal API

Restaurant Internal API

Base Path: /internal/v1/restaurants

Method Path Description
GET /internal/v1/restaurants/{restaurantId} 내부 식당 단건 조회
GET /internal/v1/restaurants/owners/{ownerId}/id 점주 ID 기준 식당 ID 조회

Course Internal API

Base Path: /internal/v1/restaurants/{restaurantId}/courses

Method Path Description
GET /internal/v1/restaurants/{restaurantId}/courses 내부 코스 요약 목록 조회

🧾 Request Examples

식당 등록

{
  "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
    }
  ]
}

🧪 Test

테스트는 아래 영역을 포함합니다.

  • ✅ 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 test

🧪 HTTP Scenario Test

HTTP 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를 사용해야 합니다.


📊 Load Test

k6 기반 조회 API 부하테스트 스크립트를 제공합니다.

load-tests/restaurant
├─ restaurant-read-as-is.js
└─ restaurant-read-to-be-cache.js

테스트 목적

  • Redis 캐시 적용 전/후 조회 성능 비교
  • 반복 조회 시 DB select 감소 여부 확인
  • cache hit/miss 지표 확인
  • p95 / p99 응답 시간 확인

📈 Monitoring

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 상태

🛠️ Local Development

1. 필수 선행 서비스

아래 서비스가 먼저 실행되어 있어야 합니다.

  • PostgreSQL
  • Redis
  • Kafka
  • Eureka Server 선택
  • API Gateway 선택
  • reservation-service 선택

체크인 API를 로컬에서 실제로 검증하려면 reservation-service가 필요합니다.


2. 환경 변수 준비

.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

3. application 설정

application.yaml

  • 서비스명: restaurant-service
  • 기본 server port: 19300
  • Redis cache 사용
  • Kafka consumer 기본 설정
  • Actuator / Prometheus endpoint 노출

application-local.yaml

  • local profile DB 연결
  • Redis host 기본값: localhost
  • JPA ddl-auto: update
  • SQL 로그 출력
  • Eureka client 기본 비활성화

application-prod.yml

  • prod profile DB URL을 POSTGRES_HOST, POSTGRES_PORT, POSTGRES_DB로 조합
  • HikariCP pool size 환경변수 기반 설정
  • Redis / Kafka / Eureka / Feign / Cache TTL 환경변수 기반 설정
  • Prometheus metrics 노출

▶️ Run

Gradle 실행

./gradlew bootRun

Profile 지정 실행

SPRING_PROFILES_ACTIVE=local ./gradlew bootRun

또는 IntelliJ에서 RestaurantServiceApplication을 실행합니다.


🐳 Docker

Build

docker build -t restaurant-service:local .

Run

docker run --rm -p 19300:19300 \
  --env-file .env.example \
  restaurant-service:local

Dockerfile은 multi-stage build를 사용합니다.

Gradle JDK 17 builder stage
↓
bootJar 생성
↓
Eclipse Temurin JRE 17 runtime stage
↓
app.jar 실행

🚀 CI/CD

GitHub Actions 기반 CI/CD를 제공합니다.

CI-PROD

Pull Request / Push
↓
JDK 17 설정
↓
Gradle test
↓
Docker image build
↓
ECR push
  • 이미지 태그는 commit SHA 앞 7자리를 사용합니다.
  • AWS 인증은 OIDC Role 기반으로 처리합니다.

CD-PROD

CI-PROD 성공
↓
ECR image 존재 확인
↓
현재 ECS Task Definition 조회
↓
새 Task Definition revision 생성
↓
ECS Service update
↓
services-stable 대기

배포 시 container environment에 DB, Redis, Kafka, Eureka, cache TTL 값을 주입합니다.


🧠 Design Notes

MSA Boundary

restaurant-service는 식당/코스/체크인 로그를 소유합니다.
예약 상태의 source of truth는 reservation-service가 소유합니다.

체크인 요청은 restaurant-service에서 받지만, 예약 상태 검증과 상태 변경은 reservation-service에 위임합니다.


Feign + Kafka 역할 분리

즉시 응답이 필요한 체크인 검증은 Feign 동기 통신으로 처리합니다.
체크인 완료 이벤트 기반 로그 저장은 Kafka 비동기 consume으로 처리합니다.

즉시 검증: Feign
이벤트 처리: Kafka
중복 방어: reservationId 기준 idempotency

Redis Cache-Aside

식당 상세 / 코스 목록 조회는 반복 조회 가능성이 높기 때문에 Redis Cache-Aside 패턴을 적용합니다.

  • cache hit: DB 접근 없이 응답
  • cache miss: DB 조회 후 Redis 저장

record DTO 역직렬화 이슈를 방지하기 위해 Redis value serializer의 타입 정보 설정을 명시합니다.


Soft Delete

공통 BaseEntity를 사용하며, 주요 조회에서는 삭제되지 않은 데이터 기준으로 처리합니다.


Check-in Log Unique Constraint

하나의 예약에 대해 체크인 로그는 한 번만 저장되어야 합니다.

uk_checkin_log_reservation_id

Kafka 재전달이나 동시 consume 상황에서도 reservationId 기준으로 중복 저장을 방어합니다.


📂 Project Files

.
├─ build.gradle
├─ settings.gradle
├─ Dockerfile
├─ .env.example
├─ load-tests
│  └─ restaurant
├─ src
│  ├─ main
│  │  ├─ java
│  │  └─ resources
│  └─ test
│     ├─ http
│     ├─ java
│     ├─ mvp
│     └─ resources
└─ gradle

✅ Summary

이 서비스는 다음을 책임집니다.

  • 🏪 식당 기본 정보 관리
  • 🍽️ 식당 코스 및 코스 메뉴 관리
  • 🔍 사용자용 식당/코스 조회
  • 🔗 내부 서비스용 식당/코스 조회
  • ✅ 예약 연동 체크인 처리
  • 📩 Kafka 체크인 완료 이벤트 consume
  • 🧱 reservationId 기준 체크인 로그 중복 방어
  • 🚀 Redis Cache-Aside 기반 조회 성능 개선
  • 📊 Prometheus/Grafana 기반 모니터링 지표 노출
  • 🧪 HTTP Client 기반 배포환경 API 시나리오 검증

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages