Repository navigation
DTO 기본값·receiverIds nullable 제거 — 누락 계약을 빈 값으로 은폐 #790
Copy link
Copy link
Labels
area:timeletter타임레터 기능 영역타임레터 기능 영역refactor동작 변경 없이 코드 구조·책임·경계를 개선하는 작업동작 변경 없이 코드 구조·책임·경계를 개선하는 작업
Description
📜 Overview (Required)
origin/develop 62d5935c와 박경민 담당의 열린 PR #763·#767을 기준으로 feature/timeletter·feature/setting의 DTO 기본 파라미터와 전 계층 nullable을 전수 확인했다.
타임레터 DTO는 서버가 항상 키를 직렬화하고 모든 생성 호출부가 인자를 전달하는데도 = null·= emptyList()가 광범위하게 들어가 있다. 그 결과 키 누락이 명시적 null·빈 블록·빈 수신자 목록과 구분되지 않는다. 생성 경로의 receiverIds는 UI의 빈 목록을 굳이 null로 바꿨다가 repository에서 다시 빈 목록으로 바꾸고 있어 nullable 상태가 아무 의미도 갖지 않는다.
확인된 대상
-
DTO의 불필요한 기본 파라미터 제거
TimeLetterDto.kt: 블록 요청·응답, 생성·수정 요청, 단건 응답의 모든= null·= emptyList()를 생성 호출부와 응답 계약에 맞춰 제거한다.ReceivedTimeLetterDto.kt: 공개 전 숨김 값은 nullable을 유지하되= null을 제거하고,blocks의 빈 목록 기본값도 제거한다. 서버는 숨김 상태에서도 해당 키를 명시적null또는 빈 배열로 보낸다.- PR fix(timeletter) : 타임레터 API 동기화 및 임시저장 오류 처리 (#570, #568, #428, #693) [base: develop] #763·#767이 추가한 수정 요청
deliveryMode: TimeLetterDeliveryModeDto? = null도 repository가 항상 인자를 전달하므로 기본값을 두지 않는다.
-
DTO 밖까지 전파된 불필요한
receiverIdsnullable 제거TimeLetterWriteViewModel의state.recipientIds.ifEmpty { null }CreateTimeLetterUseCase.receiverIds: List<Long>?TimeLetterRepository.createTimeLetter(... receiverIds: List<Long>?)및 구현체- 구현체의
receiverIds ?: emptyList()
생성 API는 수신자 목록을 필수로 검증하고, FE 내부에서도
null은 즉시 빈 목록으로 합쳐진다. 전 경계를List<Long>으로 좁히고state.recipientIds를 그대로 전달한다. 수신자 미선택 허용 여부와 오류 문구는 #570의 제품 동작 문제이며, nullable 타입으로 우회할 문제가 아니다. -
유지할 nullable
- 임시저장 제목·발송일, 발송 전
deliveredAt, 블록 종류에 따른textContent/url/mimeType, 공개 전 수신 레터의 제목·발신자·작성 시각은 실제 계약상 조건부이므로 nullable을 유지한다. feature/setting에는 DTO가 없고, 확인된 nullable은 미선택 관계·선택 날짜·오류·연결 전 계정 정보처럼 실제 UI 상태를 나타내므로 제거 대상이 없다.
- 임시저장 제목·발송일, 발송 전
완료 기준
- 두 타임레터 DTO 파일에서 키 생략을 허용할 근거가 없는 기본 파라미터를 제거한다.
- 요청 mapper/repository는 nullable 값을 포함해 모든 생성 인자를 명시한다.
- 응답 키 누락은 명시적
null·빈 배열로 성공하지 않고 계약 테스트가 실패한다. - 생성
receiverIds는 ViewModel → use case → repository → request DTO까지 non-null 목록으로 이어진다. - 조건부 nullable의 명시적
null과 공개 전 빈blocks는 정상 역직렬화되는 테스트를 유지한다. - PR fix(timeletter) : 타임레터 API 동기화 및 임시저장 오류 처리 (#570, #568, #428, #693) [base: develop] #763·#767의 공통 변경도 같은 기준으로 정리한다.
📌 Child Issue(Optional)
No response
📍 Note (Optional)
- 관련 기준 이슈: refactor(dto): 응답 DTO 를 BE 실코드에 동기화 — 유령 필드 9건·누락 필드 21건 #676. 이 이슈는 박경민 담당 모듈의 확인된 적용 대상이라 별도 추적한다.
- #763과 #767은 현재 타임레터 data/domain 공통 변경을 동일하게 포함하므로, 어느 PR을 유지하든 이 이슈의 수정을 반영한다.
- 계약 근거: 현재 OpenAPI, Afternote-BE release
8bfe43d1.
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
area:timeletter타임레터 기능 영역타임레터 기능 영역refactor동작 변경 없이 코드 구조·책임·경계를 개선하는 작업동작 변경 없이 코드 구조·책임·경계를 개선하는 작업
Activity
OnlyReadOneBook commentedon Aug 23, 2026
#932 를 실측 대조하면서 이 이슈 범위에 얹으면 추가 조사 없이 끝나는 일 하나를 확인해 옮깁니다.
응답 DTO 15개 파일 중 어느 엔드포인트의 응답인지 · 그 판정 근거가 무엇인지 표기가 없는 파일이 8개인데,
그중 2개가 이 이슈가 이미 손대는 파일입니다.
TimeLetterDto.ktReceivedTimeLetterDto.kt이 이슈에서 "서버가 이 키를 항상 보내는가" 를 필드별로 판정하게 되는데, 그 판정 결과를 KDoc 으로 같이 남겨 주시면
됩니다. 판정을 새로 해야 하는 일이 아니라 이미 하는 판정을 코드에 적어 두는 것뿐입니다.
양식은 mindrecord 가 이미 쓰고 있는 것을 그대로 따르면 됩니다 —
DiaryDto.kt참조.별도 이슈로 찢지 않고 여기로 넘기는 이유는, 같은 두 파일을 두 이슈가 각각 열면 충돌이 나고
근거 판정이 이 이슈 안에서 이미 이뤄지기 때문입니다. #932 에는 koongmai 몫으로 이 항목을 적어 뒀습니다.
OnlyReadOneBook commentedon Aug 23, 2026
#676 을 BE 실코드 대조로 재정의하면서 timeletter 몫 1건이 나왔습니다.
TimeLetterDtoTimeLetterResponsedeliveredAtAfternote-BE0fb9e0b의TimeLetterResponse에deliveredAt이 없습니다. FE 는String? = null로 받고 있어파싱은 성공하지만 값이 영영 오지 않습니다.
이 이슈가 "발송 전
deliveredAt" 을 유지할 nullable 로 분류해 두셨는데, BE 가 그 키를 아예 안 보내는 것이라면"조건부로 null" 이 아니라 "존재하지 않는 필드" 입니다. 판단이 갈립니다.
이 이슈에서 기본값을 걷으며 실서버 응답을 확인하실 때
deliveredAt키의 존재도 같이 봐 주시면 됩니다.OnlyReadOneBook commentedon Aug 23, 2026
앞서 넘긴 "계약 근거 주석" 몫을 철회합니다. 하지 않으셔도 됩니다.
엔드포인트는 ApiService 와 Swagger 에, nullable 근거는 BE
@Schema(description)에 이미 있습니다.FE 가 KDoc 으로 복제하면 정본이 둘로 갈리고, BE 가 바뀔 때 FE 주석만 낡아 거짓이 됩니다.
실제로 그렇게 됐습니다 —
Detail.kt에 있던 계약 설명 주석(카테고리별 non-null 표,ReceiverRequest 는 이 필드 하나뿐(2026-08-02 Swagger 실측),name·relation·phone 은 실리지 않아 빈 문자열)이전부 현재 계약과 어긋나 #676 에서 삭제됐습니다. receivers 는 전 카테고리 공통이고, BE #81 이 닫히며
name·relation 은 이제 옵니다.
남길 가치가 있는 주석은 명세를 봐도 모르는 FE 판단뿐입니다 — wire 이름과 도메인 이름이 다른 이유,
실패 폭 때문에 명세와 달리 기본값을 두는 결정 같은 것. 그건 Swagger 어디에도 없으니까요.
#932 는 afternote 매퍼 명명 정리만 남기고 축소했습니다.
[-]refactor(timeletter): DTO 기본값·receiverIds nullable 제거 — 누락 계약을 빈 값으로 은폐[/-][+]DTO 기본값·receiverIds nullable 제거 — 누락 계약을 빈 값으로 은폐[/+]Merge pull request #1337 from Afternote/refactor/790-timeletter-contract
OnlyReadOneBook commentedon Aug 29, 2026
#1494 (PR #1495) 에서 전역
coerceInputValues를 걷어내며 이 이슈 범위에 걸리는 걸 하나 남겨 둡니다 — 담당 모듈이 달라 제 PR 에서 건드리지 않았습니다.feature/timeletter/data/src/test/kotlin/.../TimeLetterDtoContractTest.kt:28의 테스트 픽스처가 아직 프로덕션 설정을 복제합니다.그대로 두면 이 계약 테스트가 프로덕션에 없는 관용성을 기준으로 검증합니다.
core/network쪽 픽스처 5곳은 #1495 에서 함께 맞췄습니다.참고로 이 이슈 작업에 쓸 수 있는 실측 근거입니다.
blocks는 서버가null로 보내지 않습니다. BEReceivedTimeLetterResponse.java가 미공개 구간에List.of()를 채웁니다. 그래서= emptyList()기본값이 실제로 가리는 것은 «키 자체가 빠지는» 경우뿐입니다.isRead는 실제로null이 옵니다 — 같은 파일에서.isRead(isAvailable ? (…getReadAt() != null) : null). 현재 FE 선언은val isRead: Boolean이라 기본값이 없어, 플래그가 있던 시절에도 이 응답은 실패했습니다. 이 이슈의 xfail 테스트가assertNull(decoded.isRead)로 기대하는 것과 같은 방향입니다.:konsist:test허용목록에서 이미 해소된 13건은 refactor(core): 전역 coerceInputValues 를 걷어낸다 (#1494) #1495 가 지웠고,ReceivedTimeLetterDto.blocks만 남겨 뒀습니다. 이 이슈가 해소되면 그 한 줄과ci-expected-failures.json항목을 함께 걷으시면 됩니다.refactor(core): 계약 테스트도 프로덕션 Json 설정에 맞춘다 (#1494)
fix(ci): 주간 의존성 감사가 기대 실패 게이트를 제외하도록 init script 를 붙인다 (#1653) (#1660)
fix(ci): DTO 기본값 제거 구현이 끝났으니 기대 실패 등록을 뺀다
fix(ci): 기대 실패 목록 0건을 schemaVersion 검증으로 안전하게 허용한다