Files
tasteby/docs/design/358-restaurant-update-dto
joungmin c1050f3abd feat(backend): #358 RestaurantUpdateDTO + @Valid 표준화
- dto/RestaurantUpdateDTO record 신규 (15 필드, 모두 nullable)
- @Size/@Pattern(URL or NONE)/@DecimalMin·Max/@Min·Max
- RestaurantController.update 시그니처 Map → @Valid DTO 교체
- toFieldMap()으로 null 제외 후 기존 Service.update 호출 (회귀 0)
- #332 ALLOWED_UPDATE_FIELDS Set 제거 (DTO 필드 자체가 화이트리스트)

Refs: #358 (close)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-15 20:20:51 +09:00
..

설계서: 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 — 후속.