# 설계서: RestaurantUpdateDTO + @Valid 표준화 (#358)

> **상태**: Approved
> **작성**: [AI] Architect · **최종수정**: 2026-06-15
> **추적성** — Redmine: #358 · 부모: #348(09-Done) · 관련: #332(화이트리스트 1차)
> · 구현 파일: `backend-java/src/main/java/com/tasteby/dto/RestaurantUpdateDTO.java`(신규), `backend-java/src/main/java/com/tasteby/controller/RestaurantController.java`
> · 테스트: 본 범위 밖 (수동 — 어드민 식당 편집 동작 확인)

## 1. 목적 (Why)

`#332`에서 Set 화이트리스트로 1차 가드 적용했지만, 타입 안전성·validation·API 명세는 여전히 `Map<String, Object>`로 흐릿함. 본격 DTO 표준화로 잘못된 입력 자동 거부 + 명세 명확화.

## 2. 범위 (Scope)

- **포함**
  - `RestaurantUpdateDTO` record — 화이트리스트 14필드 모두 Optional(null 시 미변경).
  - `@Valid` + Bean Validation 어노테이션 적용 (`@Size`, `@Pattern`, `@DecimalMin/Max`, `@Min`).
  - Controller `PUT /api/restaurants/{id}` 시그니처: `Map → RestaurantUpdateDTO`.
  - DTO → Map 변환(`toFieldMap()`) — Service 계층은 그대로 (재작업 0).
  - 잘못된 입력 시 400 자동 응답 (Spring 기본 `MethodArgumentNotValidException`).
- **제외 (별도 후속)**
  - `tabling-url` / `catchtable-url` PUT 엔드포인트 — 단일 필드라 현행 유지.
  - PATCH 시멘틱 (부분 업데이트) — 현재 PUT이 부분 업데이트 의미로 사용 중.

## 3. 인수조건

- [ ] 모든 화이트리스트 필드 record에 등재 + null 가능.
- [ ] `name`: `@Size(min=1, max=200)`.
- [ ] `website`/`tabling_url`/`catchtable_url`: `@Pattern(http(s)://... | "NONE" | "")`.
- [ ] `latitude`: `@DecimalMin("-90.0") @DecimalMax("90.0")`.
- [ ] `longitude`: `@DecimalMin("-180.0") @DecimalMax("180.0")`.
- [ ] `rating`: `@DecimalMin("0.0") @DecimalMax("5.0")`.
- [ ] `rating_count`: `@Min(0)`.
- [ ] `price_range`: `@Min(1) @Max(5)`.
- [ ] 잘못된 입력 → HTTP 400 자동 응답.
- [ ] 기존 동작 회귀 없음 (geocode/cache flush 흐름 동일).

## 4. 컨텍스트 & 제약

- `spring-boot-starter-validation` 이미 의존성 등록됨.
- record + Bean Validation: 컴파일 시 어노테이션 인식 OK.
- Jackson SNAKE_CASE 매핑 유지: `cuisine_type`, `tabling_url` 등.
- `null`은 "변경 없음" 시그널 — `toFieldMap()`에서 제외.

## 5. 함수 명세

| 함수 | 책임 | 비고 |
|---|---|---|
| `RestaurantUpdateDTO` (record) | 입력 표면 | 14 필드, 모두 nullable |
| `RestaurantUpdateDTO.toFieldMap()` | null 제외 Map 변환 | Service `update` 시그니처 유지 |
| `RestaurantController.update(...)` | DTO 받음 + geocode 분기 | `@Valid @RequestBody RestaurantUpdateDTO` |

## 6. 흐름

1. 클라이언트 → `PUT /api/restaurants/{id}` JSON.
2. Spring 역직렬화 + Bean Validation. 실패 시 400 자동.
3. `dto.toFieldMap()` → null 제외.
4. 기존 geocode 분기 + `restaurantService.update(id, fieldMap)`.

## 7. 엣지케이스

- **모든 필드 null**: `toFieldMap()` 빈 Map → no-op (현행 유지).
- **`tabling_url = "NONE"` / 빈 문자열**: Pattern에 포함 → 통과.
- **숫자 범위 위반**: 400.
- **알 수 없는 필드 (예: `xxx`)**: Jackson 기본은 무시 (mapper 설정 유지) → 안전.

## 8. 리스크 & 대안

- **선택**: record + Bean Validation. 코드 최소.
- **대안 A**: class + setter. 보일러플레이트 다수.
- **대안 B**: 개별 PATCH endpoint per 필드. 표면 폭증.

## 9. 미해결 질문

- bulkUpdate (batch) 도입 시 별도 DTO — 후속.
