Repository navigation
refactor(dto): 응답 DTO 를 BE 실코드에 동기화 — 유령 필드 9건·누락 필드 21건 #676
Description
2026-08-24 재정의. 최초 전제("nullable 판단 기준이 없어 한 DTO 안에서 세 방식이 섞였다")는 BE 실코드 대조로 기각됐다.
actions·leaveMessage·credentials·playlist넷 모두 BE 가@Schema(nullable = true)로 명시한 조건부 필드고
FE 가 맞게 반영하고 있었다. 최초 근거였던 "어느 필드에도 nullable 표기가 없다" 는 2026-08-02 Swagger 기준인데,
그 뒤 BE #189 → PR #221 이 해당 응답의 명세를 정비했다.대신 필드 구성 자체가 어긋난 곳 30건이 나왔다. 이슈를 그 동기화로 재정의한다.
FE 응답 DTO 와 BE 응답 record 의 필드 구성이 어긋난 곳이 30건이다.
서버가 보내는 값을 화면이 못 쓰거나(누락 21), 영영 오지 않을 값을 기다린다(유령 9).
판정 방법
OpenAPI 문서는 근거가 못 된다. BE 응답 record 65개 중 requiredMode 를 명시한 건 2개뿐이다.
근거는 BE 실코드, 특히 배포본 origin/release 다.
git -C <BE> branch -r --contains <필드추가커밋> # release 에 들어갔나 = 배포됐나
git -C <BE> show origin/release:<파일> # 배포본 실물Jackson NON_NULL 설정이 없어 record 에 있으면 키가 항상 나가고, 없으면 절대 안 온다.
값이 null 일 수 있는지는 서비스 조립부로 판정한다 (Boolean.TRUE.equals(...) 면 null 불가).
dev 서버는 02시대 후반~12시(KST) 닫힌다. 서버가 닫혀 있어도 위 경로로 계약은 확정된다.
2026-08-24 13:21 배포 서버
/v3/api-docs실측으로 아래 판정을 교차 검증했다 (인증 불필요,curl -k).
AfternotedetailResponse.required = [afternoteId, category, isDraft, receivers, title, updatedAt]—
#972 의isDraft·receiversnon-null 화와createdAt제거가 배포 스키마와 일치한다.
AfternoteReceiverResponse는 3필드로phone이 없어 그 제거도 확인됐다.
⚠ 누락 필드를 전부 받는 것이 답이 아니다
"BE record 에 있으면 FE 가 받는다"로 일괄 적용했다가 두 건을 되돌렸다. 안 받는 것이 의도인 경우가 있다.
ReceiverEmailAuthVerifyDto.accessCode— 배포 스키마엔 아직 있지만(2026-08-24 실측) 계약 테스트가
"서버가 마스터 키와 동일한 accessCode 를 제거할 예정이라, 제거 배포가 언제 나가든 파싱이 깨지지 않아야 한다(refactor(data): 수신자 email/verify 응답의 accessCode 수신 제거 (BE 필드 제거 선행 가드) #454)" 로
의도적 미수신을 고정하고 있었다. non-null 로 받으면 BE 제거 배포 시 수신자 인증이 전면 실패한다.PresignedUrlDto.contentLength/maxContentLength— fix(core): presigned URL 요청에 필수 contentLength 를 싣는다 — 이미지 업로드 전면 실패 #953 이 같은 파일을 고치는 중이고 쓰는 데도 없다.
받을지 말지는 화면이 그 값을 쓰는지로 정한다. 안 쓸 값이면 "안 받는다"가 결론이고,
그 판단은 커밋 메시지에 남긴다 — 코드 주석에 계약을 복제하면 BE 가 바뀔 때 FE 주석만 낡아 거짓이 된다
(Detail.kt 의 계약 주석 3종이 실제로 그렇게 낡아 삭제됐다).
현황
core — 1hyok
| FE | 어긋남 | 상태 |
|---|---|---|
SocialLoginDto |
FE newUser ↔ 서버 isNewUser |
🔴 실해 확인 — 아래 |
PresignedUrlDto |
contentLength maxContentLength 미수신 |
보류 — 배포 스키마에 존재하나 쓰는 데 없음, #953 과 같은 파일 |
🔴 SocialLoginDto.isNewUser — 소셜 신규 가입자가 온보딩을 건너뛴다
배포 스키마 SocialLoginResponse 의 키는 isNewUser 인데 FE 는 @SerialName("newUser") 로 읽는다.
Boolean? = null 이라 파싱은 조용히 통과하고 값만 항상 null 이 된다.
// LoginUseCase.kt:61 — null == true → false
(session as? Session.SocialSession)?.isNewUser == true
// LoginViewModel.kt:112 — 그래서 이 분기가 영영 안 탄다
.onSuccess { isNewUser -> if (isNewUser) it.copy(shouldStartOnboarding = true) }BE 는 처음부터 isNewUser 였고(2aba678 이전), FE 가 29f409ba(2026-05-09)에 newUser 로 적었다.
약 3개월 반 동안 소셜 로그인 신규 가입자가 온보딩을 못 밟았다.
afternote — 1hyok
| FE | 어긋남 | 상태 |
|---|---|---|
AfternoteDetailDto |
유령 createdAt · 누락 isDraft · receivers nullable |
✅ #972 |
AfternoteDetailReceiverDto |
유령 phone |
✅ #972 |
AfternoteListItemDto |
누락 isDraft |
✅ #972 |
ReceivedAfternoteDetailDto |
유령 credentials |
판단 필요 — 아래 |
AfternoteSongDto |
유령 id |
미확인 — FE 내부 선택 키일 수 있다(#823) |
ReceiverAuthPresignedUrlDto |
contentLength maxContentLength 미수신 |
보류 — core 와 같은 사유 |
mindrecord — @Sadturtleman → #789 로 이관
receivers(5곳) · yearMonth · emotion · updatedAt 누락.
imageUrl 유령 4건과 deepThoughtAmount·emotionAnalysis 는 이미 판정됐거나 의도적이라 대상이 아니다.
→ #789 코멘트
timeletter — @koongmai → #790 으로 이관
TimeLetterDto.deliveredAt 유령 1건. → #790 코멘트
판단이 필요한 것 — ReceivedAfternoteDetailDto.credentials
배포 스키마 ReceivedAfternoteDetailResponse 는 8필드(id·category·title·actions·leaveMessage·senderName·createdAt·playlist)로
credentials 가 아예 없다(2026-08-24 실측). FE 는 기대하고 매퍼까지 연결해 뒀다.
단순 유령 제거로 지우면 수신자가 SOCIAL·BUSINESS 애프터노트의 계정 정보를 못 보는 상태가 고착된다.
제품 의도가 "수신자도 계정 정보를 열람한다"라면 이건 FE 결함이 아니라 BE 미구현이고, 서버에 요청할 사안이다.
449aaf31(상세 credentials 부분 응답 허용)이 이 값을 다룬 이력이 있어 의도 확인이 선행돼야 한다.
산출물 — 보정형 기본값 재발 가드
ResponseDtoContractKonsistTest (#972). 응답 DTO 의 non-null 프로퍼티에 보정형 기본값
(= "" · = 0 · = false · = emptyList())을 금지한다 — 서버가 키를 빼도 파싱이 성공해 계약 누락이 은폐되는 조합이고,
AfternoteDetailDto.createdAt = "" 이 그 실패 모양이었다.
실측 시점 잔여 38건은 담당 이슈별로 등재했다 — #789 26 · #957 11 · #790 1.
해소된 항목이 목록에 남아 있어도 실패시켜 목록이 썩지 않게 했다.
재현
FE 와이어 키(@SerialName·@JsonNames) ↔ BE record 필드(@JsonProperty)를 대조한다.
클래스명이 달라 필드 유사도로 매칭한 쌍은 대응 관계부터 확인할 것 —
AfternoteDetailReceiverDto ↔ UserPatchReceiverResponse 로 잘못 붙어 phone 유령을 놓치고 email 누락을 오탐한 전례가 있다.
BE 가 요청 record 를 응답에 재사용하는 곳도 있다(AfternotedetailResponse.playlist 의 타입이 AfternoteCreateRequest.PlaylistRequest).
Activity
OnlyReadOneBook commentedon Aug 3, 2026
적용 사례가 하나 더 생겼습니다 — #699 (일기 목록 DTO).
DiaryListItemDto가diaryId·createdAt·todayMood를 필수로 요구해서, 항목 하나가 어긋나면MissingFieldException으로 그 달 목록이 통째로 날아갑니다. #699 의 수정 방향은 "식별자 외에는 기본값을 두고todayMood는 nullable 로 내린다" 인데, 이 이슈가 세우려는 세 갈래 기준(항상 오는 값 → non-null / 없을 수 있는 값 → nullable / 빈 값으로 표현 가능 → 기본값)과 같은 판단입니다.기준이 나중에 다르게 정해지면 재작업이 생기므로, 이 이슈에서 기준을 확정할 때
DiaryListItemDto를 검증 사례로 함께 훑는 것이 좋겠습니다. #699 쪽에도 같은 내용을 남겨 두었습니다.docs(mindrecord): 일기 목록 DTO 의 nullable 근거를 실패 폭 기준으로 정정
docs(mindrecord): 일기 목록 DTO 의 nullable 근거를 실패 폭 기준으로 정정
[-]chore(core): 응답 DTO nullable 판단 기준 부재 — 같은 DTO 안에서 세 방식 혼재, 명세도 근거 아님[/-][+]refactor(dto): 응답 DTO 를 BE 실코드에 동기화 — 유령 필드 9건·누락 필드 21건[/+]