# 설계서: Restaurant 임의 필드 업데이트 화이트리스트 (#332) > **상태**: Approved > **작성**: [AI] Architect · **최종수정**: 2026-06-15 > **추적성** — Redmine: #332 · 부모: #290 (식당 CRUD Reviewer 후속, 09-Done) > · 구현 파일: `backend-java/src/main/java/com/tasteby/controller/RestaurantController.java`, `backend-java/src/main/java/com/tasteby/service/RestaurantService.java` > · 테스트: 수동 (curl로 화이트리스트 외 필드 시도) ## 1. 목적 (Why) `PUT /api/restaurants/{id}` body가 `Map`로 임의 키를 받는다. SQL 측 `updateFields`는 컬럼별 `` 가드로 화이트리스트가 이미 적용되어 있어 임의 컬럼 갱신은 차단된다. 다만 Controller 레벨에서 화이트리스트가 명시되지 않아 — (a) 의도 모호, (b) 향후 SQL 측 가드가 무력화되거나 다른 매퍼로 확장되면 위험. 명시적 화이트리스트로 의도 강화. ## 2. 범위 (Scope) - **포함** - `RestaurantController.update(id, body)`에서 허용된 키만 통과시키는 `ALLOWED_UPDATE_FIELDS` set. - 허용되지 않은 키는 무시(silently drop) + 디버그 로그. 400 응답은 hard policy라 사용자 경험 영향 큼. - **제외 (out of scope)** - DTO 클래스 도입 (RestaurantUpdateDTO + Bean Validation) — 더 강한 표준화지만 코드 영향 큼. 후속. - DDG HTML 검색 → 정식 API 전환 (별도 후속, 비용/계약 결정 필요). - isNameSimilar 한국어 자모 알고리즘 (별도 후속). - UNIQUE(google_place_id) 제약 강화 (DB 마이그레이션 + 데이터 정리 필요, 별도). ## 3. 인수조건 - [ ] `ALLOWED_UPDATE_FIELDS` 상수 정의 (SQL `updateFields` 가드와 일치). - [ ] PUT 호출 시 body에서 허용 외 키는 자동 제거 + 디버그 로그. - [ ] 허용 키만 있는 정상 호출 → 200 정상 동작 회귀 없음. - [ ] 허용 외 키만 있는 호출 → 200 + 변경 없음 (또는 200 + 빈 업데이트). ## 4. 컨텍스트 & 제약 - Spring MVC. 변경 최소화. DTO 도입 없이도 가드 가능. - 운영 영향 없음 (어드민이 화면에서 호출하는 키는 모두 허용 set 안에). - 가정: 화이트리스트 set은 SQL `updateFields` 의 `` 키들과 1:1. ## 5. 아키텍처 개요 ``` PUT /api/restaurants/{id} body │ ▼ RestaurantController.update ├ requireAdmin ├ allowed = ALLOWED_UPDATE_FIELDS ├ filtered = body where key ∈ allowed ├ if (filtered.isEmpty()) → 200 + no-op └ restaurantService.update(id, filtered) → mapper.updateFields ``` ## 6. 데이터 모델 `Set ALLOWED_UPDATE_FIELDS = Set.of(name, address, region, cuisine_type, price_range, phone, website, tabling_url, catchtable_url, latitude, longitude, google_place_id, business_status, rating, rating_count)` — SQL `updateFields`의 키들과 일치. ## 7. 함수 명세 | 함수 | 책임 | 비고 | |------|------|------| | `RestaurantController.update(id, body)` (수정) | 화이트리스트 필터 후 위임 | filtered.isEmpty()면 no-op | ## 8. 흐름 1. body Map 수신. 2. `body.entrySet().stream().filter(e -> ALLOWED_UPDATE_FIELDS.contains(e.getKey())).collect(toMap)`. 3. 비어있으면 200 응답하고 끝. 4. 아니면 `restaurantService.update(id, filtered)` 호출. ## 9. 엣지케이스 - **빈 body**: 200 + no-op. - **허용 외 키만**: 200 + no-op + 디버그 로그. - **null 값을 포함한 허용 키**: SQL `updateFields`가 그대로 NULL 저장 — 의도된 동작 (좌표/주소 해제 등). ## 10. 테스트 - 수동: ``` curl -X PUT -H "Authorization: Bearer " -H "Content-Type: application/json" \ -d '{"name":"테스트", "is_admin":true}' /api/restaurants/ → name만 갱신, is_admin은 무시 ``` - 자동: 별도 후속(통합 테스트 인프라 도입 시). ## 11. 리스크 & 대안 - **선택**: Controller 화이트리스트 set + silent drop. - **대안 A**: DTO + Bean Validation — 표준화 깔끔하지만 변경 범위 큼. - **대안 B**: 허용 외 키 발견 시 400 — 사용자 경험 부담, 클라이언트가 잘못된 키 보내면 즉시 실패. 본 변경은 보수적으로 silent drop. ## 12. 미해결 질문 - DDG → 정식 검색 API 전환 — 별도 후속 (비용/계약 결정). - isNameSimilar 한국어 알고리즘 — 별도 후속. - DTO 도입 표준화 — 별도 후속.