Skip to content

응답 nullable 필드를 키 생략에서 required + nullable 로 통일 #951

Description

@sevineleven

같은 "아이템" 개념이 엔드포인트마다 다른 모양으로 나간다. 클라이언트가 이를 발견해 물어봤다.

응답 클라가 보는 것
GET /tournaments/{id}/items/{itemId} name?: string키 생략
POST /tournaments/{id}/start name: string | null
GET /wishes name: string | null

토너먼트를 시작하면 null 로 받고, 같은 아이템을 상세 조회하면 키가 사라진다. 클라가 같은 데이터를 두 가지 방식으로 파싱해야 한다.

키가 없으면 클라는 "서버가 검증한 빈 값"인지 "필드가 누락된 것"인지 구분할 수 없다. null 은 그 자체가 정보지만 부재는 정보가 아니다. 그래서 required + nullable 이 낫다.

원인

@JsonInclude(NON_NULL)tournament 5개 DTO 13곳에만 붙어 있고, 전역 설정이 없다. 작업자마다 판단이 갈릴 수밖에 없는 구조였고, 실제로 같은 tournament 도메인 안에서도 TournamentStartResponse 만 빠져 있어 시작 응답과 상세 응답의 모양이 다르다.

방식 대상
키 생략 (NON_NULL) tournament 5개 DTO — 13곳
null 로 내려감 (Jackson 기본) 위시 2 · auth 4 · user 1 · notification · PageResponse · TournamentStartResponse

소수파가 tournament 쪽이고, 다수파가 이미 required + nullable 이라 통일 비용도 그쪽이 싸다.

이 판단을 되돌리는 이력

#835 에서 CodeRabbit 이 같은 불일치를 지적했을 때, "레포 전체 응답 DTO 가 NON_NULL 규약" 이라는 잘못된 근거로 생략을 계약으로 확정했다. 실제로는 tournament 5개 DTO 뿐이었다. 그때 박은 "레포 공통 규약" 주석(RecordMatchResponse.kt)도 근거 없는 서술이므로 함께 걷어낸다.

무엇을

  • @JsonInclude(NON_NULL) 13곳 제거RankedItemResponse · RecordMatchResponse · TournamentItemDetailResponse · TournamentDetailResponse(중첩 7곳 포함) · GroupResultResponse
  • 생략을 전제한 통합 테스트 단언 교체 — 제거 후 테스트 실패 목록으로 대상을 확정한다. doesNotExist 가 64건 있지만 JSONPath 필터([?(@.name == ...)])·쿠키·$.code 처럼 무관한 것이 섞여 있어 미리 분류하지 않고 실측으로 가른다
  • [P9] 토너먼트 매치 브래킷 서버 이관 - 페어 조합 강제와 2의 거듭제곱 정규화 #835 에서 박은 생략 계약 서술 되돌리기 — KDoc · 주석 · TournamentApi 의 OpenAPI 설명 · TournamentApiExamples 의 example
  • 재발 방지 — 응답 DTO 의 nullable 처리 방식이 작업자 판단에 맡겨지지 않도록 한 곳에 고정한다

범위 밖

클라이언트 타입 변경(TeamPiKi/client). 응답 계약이 바뀌므로 배포 순서를 함께 잡아야 한다. name?: string 을 전제한 타입은 name: string | null 로 바뀌어야 하고, 옵셔널 체이닝으로 접근하던 코드는 대체로 무해하지만 확인이 필요하다.

Metadata

Metadata

Assignees

Labels

fix외부 가시적 결함 수정

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions