Files
DongHyeonkaandClaude Opus 5 e9f6a93327 docs(TechLog): 도메인 규칙을 기록에 엮는다
§1.4 로 세운 도메인·비즈니스 규칙을 그것이 실제로 설명하는 기록에 넣었다.

  프로젝트가 문서 게시 파이프라인을 안 타는 이유 → 화면 다섯이 비어 있던 Case
  홈 focus 설정이 FK 없이 사는 설계 → 「열린 질문이 없습니다」 Case
  결정이 자기 화면을 안 갖는 이유 → 목록이 문서 전체를 실어야 했던 Case
  게시가 단계마다 다른 코드로 거절하는 설계 → 화면이 추측 셋을 출력한 Case (반대 사례)
  종류마다 애그리거트와 테이블이 다르다 → 매퍼가 종류를 판정해야 하는 Case
  축을 지우면 연결만 끊고 주제를 지우면 거절하는 이유 → 축 Concept
  개념이 문서 테이블에 얹힌다 → 열세 곳 Case
  화면 상태와 도메인 상태가 원래 갈려 있었다 → 이름을 두 번 바꾼 Case

Case 본문 중앙값 675 → 1,342 자. 검사 넷 전부 통과한다.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-07 19:37:13 +09:00

116 lines
5.6 KiB
Markdown

---
kind: CASE
slug: an-error-message-that-guessed
title: 서버는 하나를 답했는데 화면은 추측 셋을 출력했다
topic: one-thing-many-names
topicName: 같은 것이 화면마다 다른 이름
project: TechLog
status: 게시 전
lastVerifiedOn: 2026-09-04
sourceRevision: tech-log@2026-09-02
source:
- final/document.md#§13.4
---
# 서버는 하나를 답했는데 화면은 추측 셋을 출력했다
작업본 삭제가 실패하면 화면이 「게시됐거나, 참조하는 곳이 있거나, 누가 먼저 고쳤을 수 있습니다」라고 적었다. 세 가지 추측이다. 서버는 정확히 하나를 답하고 있었다.
## 관계
- **삭제를 막는 이유 다섯 가지가 전부 같은 한 문장으로 나온다**
이 화면 문구를 고친 뒤에도 남은 문제다.
- **화면은 못 읽은 것을 없다고 말하지 않는다**
화면이 모르는 것을 말할 때의 짝이 되는 규칙이다.
- **한 화면에 종류 이름이 아홉 개 떠 있었다 — 표가 여섯 벌이었다**
같은 시기에 문구를 한 곳으로 모은 사건이다.
## 문제
작업본 삭제가 실패하면 화면이 세 가지 추측을 나열했다.
서버는 정확히 하나를 답하고 있었다 — 「이 기록을 참조하는 곳이 있어 삭제할 수 없습니다」.
## 결론
화면이 서버의 답을 쓰지 않고 자기가 가능한 원인을 나열하고 있었다.
그래서 버전 충돌이 「사용 중」으로 읽혔다. 어떤 삭제는 되고 어떤 삭제는 안 되는 것을 지켜보는 작성자는 둘을 구분할 방법이 없었다.
게이트웨이가 서버의 클라이언트 안전 메시지를 실어 나르고 화면이 그것을 보이게 했다.
## 검증 환경
tech-log-frontend : 1801414
tech-log-backend : 857e6a9 — 삭제 거절 사유를 클라이언트 안전 메시지로 답함
확인 방식 : 실패하는 삭제를 실제로 시도해 화면 문구와 서버 응답을 대조
## 재현 조건
1. 다른 기록이 참조하는 작업본을 지운다 — 서버가 참조 사유를 답한다
2. 버전이 어긋난 상태로 작업본을 지운다 — 서버가 다른 사유를 답한다
3. 두 경우의 화면 문구가 다른지 본다
## 본문
<!-- body:start -->
## 화면이 세 가지를 추측했다
작업본 삭제가 실패하면 화면이 「게시됐거나, 참조하는 곳이 있거나, 누가 먼저 고쳤을 수 있습니다」를 적었다. 세 가지 원인을 나열하고 어느 것인지는 말하지 않는다.
서버는 하나를 답하고 있었다. 게이트웨이가 그 답을 버리고 화면이 자기 목록을 그렸다.
## 서버는 코드로 답한다
서버는 오류 코드마다 고정 문구를 갖고 있다. 삭제와 관련된 것만 여섯이다.
```java
case VERSION_CONFLICT -> "다른 곳에서 먼저 수정되었습니다. 새로 불러온 뒤 다시 시도해 주세요";
case DOCUMENT_PUBLISHED -> "공개된 기록은 삭제할 수 없습니다. 먼저 공개를 취소해 주세요";
case DOCUMENT_IN_USE -> "이 기록을 참조하는 곳이 있어 삭제할 수 없습니다";
case QUESTION_IN_USE -> "이 질문을 참조하는 곳이 있어 삭제할 수 없습니다";
case DECISION_IN_USE -> "이 결정을 참조하는 곳이 있어 삭제할 수 없습니다";
case TOPIC_IN_USE -> "이 주제를 쓰는 기록이 있어 삭제할 수 없습니다";
```
화면이 추측하던 셋이 이 중 셋에 그대로 있다. 코드는 카테고리와 재시도 가능 여부도 함께 답한다.
## 왜 원문 메시지를 안 내보내나
고정 문구를 두는 이유가 그 클래스의 javadoc 에 있다.
> code 별 고정 문구. 예외의 원문 메시지는 진단용이라 그대로 내보내지 않는다 — 저장소 제약 이름이나 SQL 조각이 새어 나갈 수 있고, 그건 클라이언트가 분기할 값도 아니다.
그래서 화면이 실어 나를 수 있는 것은 이 고정 문구다. 서버 안의 진단 메시지는 서버 로그에만 남는다.
## 같은 저장소가 반대로 만든 경로
게시 경로는 정반대로 설계돼 있다. 단계마다 다른 계약 코드로 거절하고, 그 이유가 use case 의 javadoc 에 적혀 있다.
> 단계별 실패가 서로 다른 계약 코드로 나가는 것이 이 use case 의 핵심이다. `DOCUMENT_VALIDATION_FAILED`(지금 검증하면 실패)와 `VALIDATION_STALE`(통과했으나 전제가 바뀜)은 다른 사건이고, 작성자가 해야 할 일도 다르다 — 전자는 고치는 것이고 후자는 다시 검증하는 것이다.
미리보기도 만료된 것과 이 버전을 설명하지 않는 것이 다른 코드로 갈린다. 삭제 실패만 그 갈래를 화면에서 다시 뭉갰다.
## 무엇이 구분되지 않았나
세 원인은 해야 할 일이 다르다.
| 무엇이 막았나 | 작성자가 할 일 |
|---|---|
| 버전 충돌 | 다시 받아서 지운다 |
| 참조가 있음 | 그 참조를 먼저 푼다 |
| 이미 게시됨 | 게시를 취소한다 |
문구가 셋을 함께 적으므로 작성자는 어느 것인지 모른 채 세 가지를 다 시도하게 된다. 어떤 삭제는 되고 어떤 삭제는 안 되는 것을 지켜보면서도 둘을 구분할 방법이 없었다.
## 고친 것
게이트웨이가 서버의 클라이언트 안전 메시지를 그대로 싣고 화면이 그것을 보인다. 서버가 답하지 않은 것을 화면이 만들지 않는다.
## 확인하지 못한 것
코드 하나에 문구 하나라는 구조는 참조가 어느 표에서 왔는지까지는 말하지 못한다. 다섯 참조가 전부 같은 코드로 나가므로, 그 안에서 어느 것이 막았는지는 아직 열려 있다.
<!-- body:end -->