Repository navigation
refactor(receiver): 수신자 상세 애프터노트 제목의 근거 없는 nullable 을 걷는다 (closes 1177) [base: develop] - #1178
Merged
Merged
Conversation
제목 없는 수신 상세는 서버에서 나올 수 없다. BE 가 세 겹으로 막는다 — 생성 요청이 @notblank(AfternoteCreateRequest), 컬럼이 nullable = false(Afternote), 수정 경로는 null 이면 기존 제목을 유지한다(AfternoteService). 응답 조립부 3종이 그 값을 그대로 싣고, 임시저장은 수신자 조회 쿼리가 isDraft = false 로 걸러 낸다. 그런데 상세 DTO·도메인만 String? 이라, 매퍼가 title.orEmpty() 로 도달 불가능한 «제목 없는 화면» 을 그리고 있었다. 발신자 쪽 제목 7곳도, 같은 파일의 수신 목록·곡 제목도 이미 non-null 이다. 검토된 결정이 아니라 손대지 않은 초기값이었다. 와이어까지 좁힌 것은 상세 한정이다. #1010 이 세운 «와이어 관용» 의 논거는 페이징 목록의 실패 폭(항목 하나가 페이지 전체를 날림)인데, 상세는 응답 하나가 화면 하나라 «항목만 떨어뜨린다» 가 성립하지 않고 제목 없는 상세는 어차피 못 그린다. 수신 목록 DTO 는 손대지 않았다. 계약이 깨졌을 때의 실패 방향을 테스트로 고정했다 — 키가 빠지거나 명시적 null 이면 빈 제목으로 성공하지 않고, 어느 키 때문인지 남기며 파싱이 실패한다. coerceInputValues 는 기본값이 있어야 동작하므로 이 필드를 구해 주지 않는다.
Sadturtleman
approved these changes
Aug 26, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #1177
수신자 상세의 애프터노트 제목만
String?이었다. 서버가 비워 둘 수 없게 강제하는 값이고 FE 의 다른 모든 애프터노트 제목은 이미 non-null 인데, 이 한 곳의 nullable 이 매퍼의title.orEmpty()를 낳아 도달 불가능한 «제목 없는 상세 화면» 을 코드로 만들어 두고 있었다.계약 근거 (
Afternote-BEorigin/release실코드, 2026-08-26 확인)AfternoteCreateRequest.java:18-21@NotBlank String title—AfternoteController.java:87·102가@Valid로 발효Afternote.java:34-35@Column(nullable = false, length = 100)AfternoteService.java:271request.getTitle() != null ? request.getTitle() : afternote.getTitle()— null 로 덮이는 경로가 없다PlaylistRelationStrategy.java:115가atmosphere없으면 기본 제목으로 반드시 채운다응답은 그 NOT NULL 컬럼 값을 그대로 싣는다 —
ReceivedAfternoteDetailResponse.java의 조립부 3곳(fromSocial:110·fromGallery:122·fromPlaylist:167)이 전부.title(afternote.getTitle())이고, record 의title에@Schema(nullable = true)가 없다.임시저장 예외는 논쟁 자체가 성립하지 않는다 —
AfternoteReceiverRepository.java:20·30의 조회 쿼리가AND a.isDraft = false로 임시저장을 수신자에게서 아예 배제한다. FE 도AfternoteSaveState.kt:26의TITLE_REQUIRED로 제목 없는 저장을 에디터에서 거절한다.dev 서버는 작업 시각(03:40 KST)에 닫혀 있어 실응답 확인은 못 했다. 계약은 위 실코드로 확정된다.
변경
ReceivedAfternoteDetailDto.title·ReceivedAfternoteDetail.title→String(기본값 없음)title.orEmpty()2곳 제거ReceivedAfternoteDetailTitleContractTest신설 — 키가 빠지거나 명시적null이면 빈 제목으로 성공하지 않고 어느 키 때문인지 남기며 실패함을 고정.NetworkModule.provideJson의coerceInputValues는 기본값이 있어야 동작하므로 이 필드를 구해 주지 않는다LeaveMessageBlockContractTest의 JSON 1곳. androidTest 3곳은 이미title을 명시하고 있어 손대지 않았다리뷰어에게
와이어까지 좁힌 것은 상세 한정이다. #1010 이 세운 «와이어 관용 · 도메인 엄격» 방침의 논거는 페이징 목록의 실패 폭(
ReceiverAfternotePagingSource.load()가 항목 하나 때문에 페이지 전체를 날림)이고, #1010 자신이 "결정할 것 2. 상세 경로의 기각 의미" 를 미해결로 남겼다. 상세는 응답 하나가 화면 하나라 «항목만 떨어뜨린다» 가 성립하지 않고, 제목 없는 상세는 어차피 못 그린다. 수신 목록 DTO(ReceivedAfternoteDto)는 손대지 않았다.인접 작업과의 경계:
ReceiverAfternoteDto.kt의 보정형 기본값 4건(emptyList()·0)을 다룬다.title: String? = null은 그 목록에 없다(보정형 기본값이 아니라 불필요한 nullable)credentials유령 여부만 «판단 필요» 로 남아 있다credentials쪽orEmpty()를 걷어내며serviceName = title.orEmpty()바로 아랫줄을 고친다. 텍스트 충돌이 예상되고, 어느 쪽이 먼저 들어가든 해소는 한 줄이다범위 밖:
ReceivedDetailContentUiModel.kt:36·46의serviceName: String = ""등 UI 표시 모델의 보정형 기본값. 같은 파일의finalWriteDate·accountId·password가 같은 패턴이라serviceName만 떼면 오히려 어긋나고, 그중 둘은 #1169 가 이미 손대는 중이다.타임레터와 같게 다루지 말 것:
ReceivedTimeLetterResponse.java:92는isAvailable ? timeLetter.getTitle() : null로 진짜 조건부 null 이다(senderName·createdAt·isRead도 같은 게이트). 그쪽 nullable 은 근거가 있다.검증 (로컬,
origin/develope1b307eb기준):feature:receiver:data:testDebugUnitTest(--rerun-tasks):app:compileDebugUnitTestKotlin·:app:compileDebugAndroidTestKotlin:feature:{receiver:data,receiver:domain,receiver:presentation,afternote:data}:ktlintCheck:feature:afternote:data:testDebugUnitTest·:feature:receiver:presentation:testDebugUnitTestReceivedAfternoteDetail·ReceivedAfternoteDetailDto사용처는 androidTest 포함 전수 조사했다.QA·시안 대조: 해당 없음. 화면에 그려지는 문자열이 바뀌지 않는다 — 계약상
title은 늘 채워져 있었고, 이 PR 은 «올 수 없는 값» 을 타입에서 지웠을 뿐이라 정상 응답의 렌더 결과가 동일하다. 달라지는 것은 계약이 깨졌을 때의 거동(빈 제목 화면 → 파싱 실패)뿐이다.