Skip to content

refactor(receiver): 수신자 상세 애프터노트 제목의 근거 없는 nullable 을 걷는다 (closes 1177) [base: develop] - #1178

Merged
OnlyReadOneBook merged 1 commit into
developfrom
refactor/1177
Aug 26, 2026
Merged

OnlyReadOneBook merged 1 commit into
developfrom
refactor/1177

Conversation

@OnlyReadOneBook

Copy link
Copy Markdown
Collaborator

Closes #1177

수신자 상세의 애프터노트 제목만 String? 이었다. 서버가 비워 둘 수 없게 강제하는 값이고 FE 의 다른 모든 애프터노트 제목은 이미 non-null 인데, 이 한 곳의 nullable 이 매퍼의 title.orEmpty() 를 낳아 도달 불가능한 «제목 없는 상세 화면» 을 코드로 만들어 두고 있었다.

계약 근거 (Afternote-BE origin/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:271 request.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 는 기본값이 있어야 동작하므로 이 필드를 구해 주지 않는다
  • 딸려 나온 픽스처 보정 — 매퍼 테스트 5곳, LeaveMessageBlockContractTest 의 JSON 1곳. androidTest 3곳은 이미 title 을 명시하고 있어 손대지 않았다

리뷰어에게

와이어까지 좁힌 것은 상세 한정이다. #1010 이 세운 «와이어 관용 · 도메인 엄격» 방침의 논거는 페이징 목록의 실패 폭(ReceiverAfternotePagingSource.load() 가 항목 하나 때문에 페이지 전체를 날림)이고, #1010 자신이 "결정할 것 2. 상세 경로의 기각 의미" 를 미해결로 남겼다. 상세는 응답 하나가 화면 하나라 «항목만 떨어뜨린다» 가 성립하지 않고, 제목 없는 상세는 어차피 못 그린다. 수신 목록 DTO(ReceivedAfternoteDto)는 손대지 않았다.

인접 작업과의 경계:

범위 밖: 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/develop e1b307eb 기준)

명령 결과
:feature:receiver:data:testDebugUnitTest (--rerun-tasks) ✅ 신설 계약 테스트 3건 포함 전량 통과
:app:compileDebugUnitTestKotlin · :app:compileDebugAndroidTestKotlin ✅ BUILD SUCCESSFUL — 새 경고 없음(잔여 경고는 develop 에서 물려받은 것)
:feature:{receiver:data,receiver:domain,receiver:presentation,afternote:data}:ktlintCheck ✅
:feature:afternote:data:testDebugUnitTest · :feature:receiver:presentation:testDebugUnitTest ✅

ReceivedAfternoteDetail·ReceivedAfternoteDetailDto 사용처는 androidTest 포함 전수 조사했다.

QA·시안 대조: 해당 없음. 화면에 그려지는 문자열이 바뀌지 않는다 — 계약상 title 은 늘 채워져 있었고, 이 PR 은 «올 수 없는 값» 을 타입에서 지웠을 뿐이라 정상 응답의 렌더 결과가 동일하다. 달라지는 것은 계약이 깨졌을 때의 거동(빈 제목 화면 → 파싱 실패)뿐이다.

제목 없는 수신 상세는 서버에서 나올 수 없다. BE 가 세 겹으로 막는다 — 생성 요청이
@notblank(AfternoteCreateRequest), 컬럼이 nullable = false(Afternote), 수정 경로는
null 이면 기존 제목을 유지한다(AfternoteService). 응답 조립부 3종이 그 값을 그대로
싣고, 임시저장은 수신자 조회 쿼리가 isDraft = false 로 걸러 낸다.

그런데 상세 DTO·도메인만 String? 이라, 매퍼가 title.orEmpty() 로 도달 불가능한
«제목 없는 화면» 을 그리고 있었다. 발신자 쪽 제목 7곳도, 같은 파일의 수신 목록·곡
제목도 이미 non-null 이다. 검토된 결정이 아니라 손대지 않은 초기값이었다.

와이어까지 좁힌 것은 상세 한정이다. #1010 이 세운 «와이어 관용» 의 논거는 페이징
목록의 실패 폭(항목 하나가 페이지 전체를 날림)인데, 상세는 응답 하나가 화면 하나라
«항목만 떨어뜨린다» 가 성립하지 않고 제목 없는 상세는 어차피 못 그린다. 수신 목록
DTO 는 손대지 않았다.

계약이 깨졌을 때의 실패 방향을 테스트로 고정했다 — 키가 빠지거나 명시적 null 이면
빈 제목으로 성공하지 않고, 어느 키 때문인지 남기며 파싱이 실패한다.
coerceInputValues 는 기본값이 있어야 동작하므로 이 필드를 구해 주지 않는다.
@OnlyReadOneBook
OnlyReadOneBook merged commit 4a2ccb8 into develop Aug 26, 2026
16 checks passed
@OnlyReadOneBook
OnlyReadOneBook deleted the refactor/1177 branch August 26, 2026 08:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

refactor(receiver): 수신자 상세 애프터노트 제목의 근거 없는 nullable 을 걷는다 — 도달 불가능한 «제목 없는 화면»

2 participants