바로 앞 글인 URL 라우팅 네이밍 표준architectures: [ddd, layered]를 달았다. 경로 규칙이니 계층 구조와 무관하다고 봤고, 그건 맞다.

그런데 원칙 아홉 개 중 둘이 3계층에서 그대로 통하지 않는다.

원칙 1·2·3·5·7·8·9   복수 명사 · 버전 없음 · kebab-case …   양쪽 다 성립
원칙 4               DELETE는 애그리거트 루트 제거에만        3계층엔 애그리거트가 없다
원칙 6               동사는 Domain의 의도 기반 메서드 이름     3계층엔 Domain이 없다

원칙 4의 근거인 애그리거트 삭제 표준ddd 전용이다. ddd 전용 표준을 근거로 삼은 원칙이 layered 배지를 달고 3계층 탭에 올라가 있다. 3계층 프로젝트에서 그 원칙을 읽으면 “애그리거트 루트가 뭔데”에서 막힌다.

「아직 정하지 않은 것」 장부 16번이 이 자리였다 — “소속은 글 단위로 붙는데 성립 여부는 원칙 단위로 갈린다.” 장부에 적어두고도 바로 다음 글에서 똑같이 밟았다.


먼저: 이미 정해져 있어 다시 고르지 않은 것

소속은 글의 front matter가 든다. 검색으로 글에 바로 들어온 사람은 인덱스를 안 거치므로, 전제를 글 자신이 들고 있어야 한다. 이건 안 건드린다.

소속을 가르는 기준도 이미 있다. “이 글이 계층 구조를 전제하는가”다. Domain 패키지가 있든 없든 성립하면 양쪽에, 네 겹이 없으면 문장이 성립하지 않으면 ddd만 적는다.

_data/coding-standards.yml의 항목은 글 하나에 하나다. 아키텍처 안에 넣으면 양쪽에 걸치는 글은 항목을 복사해야 하고, 그 순간 같은 글의 요약이 두 곳에 생겨 어긋난다.

이 글이 새로 정하는 것은 그 기준을 글 전체에 한 번 적용할지, 원칙마다 적용할지뿐이다.


원칙 1. 성립 여부는 어휘가 아니라 규칙으로 판정한다

무엇을 고르기 전에 몇 건인지부터 셌다. 개수에 따라 답이 달라지는 문제라서다 — 절반이 어긋나 있으면 글을 쪼개야 하고, 몇 건뿐이면 표시로 충분하다.

먼저 dddlayered를 함께 단 글을 front matter에서만 모았다. 본문을 grep하지 않은 이유가 있다. 컨트롤러 패키지 분리 표준을 쓸 때 본문에서 layered를 grep해 15편을 얻었는데, 그게 layered-dto-naming-standard라는 링크 문자열까지 센 것이었다. 같은 실수를 반복하지 않으려고 파서를 썼다.

그다음 _ai/map.yml의 섹션 범위로 원칙을 하나씩 끊어, 3계층에 자리가 없는 어휘(애그리거트·Snapshot·Provider·`domain`·`infra`·UseCase 등)가 든 섹션을 셌다.

  개수
ddd+layered 22편
그 글들의 원칙 섹션 142
ddd 어휘가 든 섹션 17
실제로 성립하지 않는 원칙 3

17과 3의 차이가 이 원칙이다. 열넷은 어휘만 걸렸고 규칙은 멀쩡했다. 두 종류였다.

// 오탐 1 — 예시 코드의 타입 이름
orderUseCase.createOrder(command)   // 3계층이면 orderService 를 주입할 뿐, 규칙은 그대로다

// 오탐 2 — 버린 안의 근거
성공 응답 상태 표준 원칙 11 은 ETag 를 「응답 본문 해시」로 정했다.
`XxxDomain`·`XxxView`·`애그리거트` 는 전부 버린 안(안 C)을 버린 이유에 나온다.
고른 규칙은 3계층에서도 그대로 돈다.

버린 이유에 나온 ddd 어휘는 고칠 것이 아니다. 버린 이유는 실제로 인용한 것을 인용해야 한다. 그걸 지우면 6개월 뒤에 왜 버렸는지가 복원되지 않는다.

그래서 판정은 이 한 줄이다 — 이 원칙이 시키는 대로 하려 할 때, 3계층 프로젝트에서 손이 멈추는가. 어휘가 몇 개 들었는지가 아니다.


원칙 2. 소속은 글이 들고, 그 때문에 글을 쪼개지 않는다

3이라는 수가 나오자 선택지 하나가 먼저 지워졌다.

안 A — 지금처럼 글 단위로만 판정한다. 얻는 것은 판정이 글마다 한 번으로 끝난다는 것이고, 아무것도 안 바꿔도 된다.

버린 이유는 그 3개가 조용히 틀린 채 남는다는 것이다. 어제 내가 라우팅 표준에서 정확히 그렇게 했다 — 원칙별로 따지지 않고 통째로 layered를 달았다. 3계층 프로젝트가 원칙 4를 읽으면 애그리거트가 없어서 막히는데, 막히면 그나마 낫고 안 막히는 쪽이 더 나쁘다. 「애그리거트 루트 제거」를 자기 나름대로 해석해 아무 데나 DELETE를 붙이면 아무 신호도 안 난다.

안 B — 3계층에도 통하는 원칙만 모아 별도 글로 분리한다. 얻는 것은 소속이 글 단위로 깨끗해진다는 점이다. 기계가 판정할 수 있고 예외가 없다.

버린 이유는 둘이다. 하나는 3개 때문에 글 3편을 쪼개는 비용이다. 다른 하나가 더 크다 — 글의 흐름이 끊긴다. 라우팅 표준의 원칙 4가 DELETE를 루트 제거로 좁혔기 때문에 흔적이 남는 동작이 전부 원칙 6으로 넘어온다. 둘은 한 흐름이라 떼면 그 연결이 사라지고, “처음부터 끝까지 한 번에 읽히는 글”이라는 이 저장소의 대원칙을 정면으로 어긴다.

골랐다 — 안 C, 글은 글 단위로 소속을 달고 안 맞는 원칙에만 표시를 단다. 안 A가 못 지켜주는 것은 읽는 사람이 안다는 것이고, 안 B가 못 지켜주는 것은 글의 흐름이다. 142개 중 3개라서 예외 쪽을 표시하는 편이 싸다.

대가는 기계가 소속을 원칙 단위로 못 읽는다는 것이다. front matter는 여전히 글 단위라, “3계층에 통하는 원칙만 뽑아줘”는 사람이 본문을 읽어야 답한다. 2.1%를 위해 기계 판독을 사는 값이 더 비싸다고 봤다.


원칙 3. 표시는 원칙 첫 줄에 문장으로 단다

표시를 달기로 했으니 어디에 다는지가 남았다.

안 A — 헤딩에 붙인다. ## 원칙 4. (도메인 드리븐 전용) DELETE는 애그리거트 루트 제거에만 쓴다.

얻는 것은 색인에 그대로 드러난다는 점이다. _ai/map.yml의 헤딩 칸에 실리므로 훑기만 해도 보인다.

버린 이유는 헤딩이 grep 과녁이라서다. 이 저장소는 헤딩 한 단어로 grep해 글 하나로 좁혀지는 것을 검문으로 삼는데, 앞에 괄호가 붙으면 과녁이 흐려진다. 그리고 같은 접두어가 여러 글의 헤딩에 반복되면 그 말 자체가 검색어로 못 쓰이게 된다 — 「설계 의도」가 44편에 있어서 쓸모없어진 것과 같은 모양이다.

안 B — front matter에 원칙 번호로 적는다. ddd_only: [4, 6] 같은 모양. 기계가 읽을 수 있다.

버린 이유는 오늘 직접 겪었다. 라우팅 표준의 원칙 3이 1,000 토큰을 넘어 둘로 쪼개면서 뒤 원칙 번호가 5부터 9까지 전부 한 칸씩 밀렸다. 번호를 본문과 front matter 두 곳에 적으면 쪼갤 때마다 한쪽만 고쳐지고, 그 순간 표시가 엉뚱한 원칙을 가리킨다. 「틀린 색인은 색인이 없는 것보다 나쁘다」와 같은 실패다 — 없으면 본문을 읽지만, 틀리면 틀린 걸 읽고 확신한다.

골랐다 — 안 C, 해당 원칙의 첫 줄에 문장으로 적는다. 안 A가 흐리는 것은 grep 과녁이고, 안 B가 못 지켜주는 것은 번호가 밀려도 안 어긋나는 것이다. 문장은 원칙 본문 안에 있으므로 원칙과 함께 움직인다. 쪼개든 순서를 바꾸든 표시가 따라간다.

대가는 기계가 못 읽는다는 것이고, 이건 원칙 2에서 이미 감수한 것과 같은 대가다. 그리고 하나 더 있다 — 사람이 표시를 빼먹어도 아무도 안 막는다. 어제 내가 빠뜨린 그 상태가 그대로 재발할 수 있다. 검사로 잡을 방법을 지금은 모르고, 안다고 지어내지 않는다.


원칙 4. 표시에는 무엇이 없어서 안 되는지를 적는다

문장으로 적기로 했으니 그 문장에 무엇을 담을지가 남는다.

안 A — 소속만 적는다. “이 원칙은 도메인 드리븐 전용이다.”

버린 이유는 왜인지가 복원되지 않는다는 것이다. 6개월 뒤에 3계층 프로젝트에서 이 줄을 읽으면 “그럼 우리는 어떻게 하지”에 답이 없고, 더 나쁘게는 정말 안 되는 건지 그냥 안 써본 건지 구별할 수 없다. 표시를 뗄 수 있는 조건도 안 남는다.

골랐다 — 안 B, 무엇이 없어서 안 되는지를 함께 적는다.

> 이 원칙은 도메인 드리븐에만 성립한다 — 3계층에는 애그리거트가 없다.

“없는 것”을 지목하면 조건이 남는다. 3계층에 애그리거트 개념이 생기면 그때 표시를 떼면 되고, 안 생기면 영영 붙어 있는 게 맞다. 안 A는 그 판단을 다시 처음부터 하게 만든다.

인용문(>)으로 두는 이유는 원칙 본문과 시각적으로 갈라지기 때문이다. 규칙이 아니라 규칙의 적용 범위라 한 단 물러나 있어야 한다.

그리고 원칙 전체가 아니라 안 되는 부분만 지목한다. 이건 셋에 실제로 달아보고 알았다 — 하나도 원칙 전체가 막히지 않았다.

route-naming 원칙 6    「상태 전이는 동사 세그먼트로」  → 3계층도 성립
                       「동사는 Domain 메서드 이름」    → 여기만 막힌다

list-request 원칙 4    「화이트리스트를 쓰고 api가 검증」 → 3계층도 성립
                       「소유는 domain 패키지」          → 여기만 막힌다

원칙을 통째로 “도메인 드리븐 전용”이라 적었으면 3계층에서 쓸 수 있는 규칙까지 같이 버려졌을 것이다. 표시가 과하면 없느니만 못하다 — 읽는 쪽이 통째로 건너뛴다.


원칙 5. 표시를 다는 순간 3계층 쪽 빈틈이 늘어난 것으로 센다

표시는 “여기는 안 통한다”까지만 말하고 3계층이 무엇을 해야 하는지는 말하지 않는다. 그 자리가 비어 있다는 뜻이다.

이걸 어디에 적을지가 문제였다.

안 A — 표시마다 장부에 열린 항목을 하나씩 만든다. 정확하다. 버린 이유는 같은 말이 세 줄로 늘어난다는 것이다. 지금 표시가 셋인데 셋 다 “3계층에서 이건 어떻게 하나”이고, 장부에는 이미 그 문장이 1번으로 있다 — “3계층에서만 의미가 있는 규칙은 0개다.”

골랐다 — 안 B, 장부 1번이 받는다. 표시를 달면 1번의 목록이 길어지는 것으로 센다. 항목을 늘리지 않는 이유는 닫히는 방식이 같기 때문이다 — 3계층 프로젝트가 돌기 시작하면 셋이 한꺼번에 닫힌다. 따로 열어두면 닫을 때 세 줄을 각각 옮겨야 하고, 하나를 빠뜨리면 이미 닫힌 것이 열려 있게 된다.

대가는 1번이 점점 커진다는 것이다. 그 안에서 무엇이 비었는지 세려면 1번 한 줄로는 부족해질 수 있다. 그때 쪼개는 것으로 하고, 지금 미리 쪼개지 않는다.


판단 기준 정리

질문
성립 여부를 무엇으로 판정하나 규칙이 3계층에서 손을 멈추게 하는가 어휘로 세면 17개, 규칙으로 세면 3개다
예시 코드의 UseCase는? 오탐이다 3계층이면 Service를 주입할 뿐 규칙은 같다
버린 안의 근거에 나온 ddd 어휘는? 오탐이고 고치지 않는다 버린 이유는 실제로 인용한 것을 인용해야 한다
소속을 다는 단위 원칙 단위로 쪼개면 글의 흐름이 끊긴다
안 맞는 원칙은 그 자리에 표시를 단다 142개 중 3개라 예외 쪽을 표시하는 게 싸다
표시를 어디에 원칙 첫 줄에 인용문으로 원칙과 함께 움직여 번호가 밀려도 안 어긋난다
헤딩에 붙이지 않는 이유 grep 과녁이 흐려진다 같은 접두어가 반복되면 검색어로 못 쓴다
front matter에 안 적는 이유 원칙 번호는 밀린다 오늘 실제로 5~9가 한 칸씩 밀렸다
표시에 무엇을 적나 무엇이 없어서 안 되는지 표시를 뗄 조건이 남는다
원칙 전체를 막나 아니다. 막히는 부분만 지목한다 셋 다 일부만 막혔다. 과하면 읽는 쪽이 통째로 건너뛴다
표시가 늘면 장부 1번의 목록이 길어진 것으로 센다 셋이 한꺼번에 닫히므로 따로 열지 않는다

이 표준을 정하기까지

바로 앞 글에서 밟고 나서야 열었다. 라우팅 표준을 쓰면서 architectures: [ddd, layered]를 달 때 원칙별로 따지지 않았다. 장부 16번에 그 문제가 적혀 있었는데도 그랬다. 장부에 적어두는 것만으로는 안 밟게 되지 않는다는 걸 확인한 셈이다.

개수부터 셌고, 그 수가 답을 골랐다. “글을 쪼갤까 표시를 달까”를 감으로 정할 수도 있었지만, 142개 중 3개라는 수가 나오자 안 B(분리)는 비용 이야기를 꺼낼 필요도 없이 지워졌다. 절반이 어긋나 있었으면 반대로 갔을 것이다.

세는 방법에서 한 번 조심했다. 컨트롤러 분리 표준을 쓸 때 본문 grep으로 layered를 세다가 링크 문자열까지 세서 15편을 얻은 적이 있다. 그래서 이번엔 front matter만 파싱했고, 22편이 나왔다.

17과 3의 차이가 이 글의 실질이었다. 처음엔 어휘로 세고 17건이라고 생각했다. 하나씩 열어보니 열넷이 오탐이었고, 그중 절반은 버린 안의 근거에 ddd 어휘가 나온 것이었다. http-success-response 원칙 11이 그렇다 — ETag를 version으로 만들자는 안을 버리면서 애그리거트와 XxxDomain을 인용했는데, 고른 규칙(응답 본문 해시)은 3계층에서도 그대로 돈다. 이걸 “어긋난 원칙”으로 셌으면 없는 문제를 고치려 들었을 것이다.

표시를 어디에 달지에서는 오늘 겪은 일이 그대로 근거가 됐다. front matter에 원칙 번호를 적는 안을 잠깐 봤는데, 몇 시간 전에 라우팅 표준의 원칙 3을 쪼개면서 번호 5~9를 전부 밀어본 뒤였다. 번호를 두 곳에 적으면 어긋난다는 걸 추측이 아니라 경험으로 알고 버렸다.

표시와 장부의 관계도 정했다. 표시는 “안 통한다”까지만 말하므로 3계층 쪽에 빈자리가 생긴다. 셋을 각각 장부에 올리려다, 셋 다 닫히는 조건이 같다(3계층 프로젝트가 돈다)는 걸 보고 1번이 받게 했다.

마지막 발견은 실제로 달아보고 나왔다. 규칙을 다 정한 뒤 셋에 표시를 붙이는데, 하나도 원칙 전체가 막히지 않았다. route-naming 원칙 6은 “동사 세그먼트를 쓴다”까지는 3계층도 되고 “동사를 Domain 메서드에서 가져온다”만 막힌다. list-request 원칙 4도 화이트리스트와 검증은 되고 소유 자리만 막힌다. 원칙을 통째로 전용이라 적었으면 3계층이 쓸 수 있는 규칙까지 같이 버려졌을 것이다. 세는 단위를 원칙까지 내렸는데 실제 경계는 그보다 한 칸 더 안쪽에 있었다.


정리

  • 성립 여부는 어휘가 아니라 규칙으로 판정한다. 어휘로 세면 17개, 규칙으로 세면 3개다
  • 예시 코드의 타입 이름과 버린 안의 근거에 나온 ddd 어휘는 오탐이다. 고치지 않는다
  • 소속은 글이 들고, 그 때문에 글을 쪼개지 않는다. 원칙을 떼면 글의 흐름이 끊긴다
  • 안 맞는 원칙에는 첫 줄에 인용문으로 표시한다. 원칙과 함께 움직여 번호가 밀려도 안 어긋난다
  • 헤딩과 front matter에는 적지 않는다. 하나는 grep 과녁을 흐리고, 하나는 번호가 밀린다
  • 표시에는 무엇이 없어서 안 되는지를 적고, 원칙 전체가 아니라 막히는 부분만 지목한다. 셋에 달아보니 하나도 통째로 막히지 않았다 — 과한 표시는 쓸 수 있는 규칙까지 버리게 한다
  • 표시가 늘면 장부 1번의 목록이 길어진 것으로 센다. 셋이 한꺼번에 닫히므로 따로 열지 않는다
  • 기계가 원칙 단위 소속을 못 읽는 것과, 사람이 표시를 빼먹는 것은 감수한다. 2.1%에 그 값을 치르지 않는다

AI 코드 어시스턴트에 바로 적용하기

이 글은 자기 스킬을 갖지 않는다. 여기서 정한 것은 표준을 쓰는 쪽의 규칙이라 설치본을 쓰는 프로젝트가 할 일이 아니다. 다른 프로젝트는 표준을 읽지 쓰지 않는다. 배포 자동화 표준이 스킬을 갖지 않는 것과 같은 이유다.

규칙이 들어가는 자리는 이 저장소의 CLAUDE.md 「표준은 아키텍처에 속한다」절이고, 거기에 세 줄이 붙었다.

- **소속은 글 단위로 달되, 그 안에서 성립하지 않는 원칙에는 그 자리에 표시를 단다.** 판정은 어휘가 아니라 규칙으로 한다 — 「이 원칙대로 하려 할 때 3계층에서 손이 멈추는가」다. 예시 코드의 `UseCase` 같은 타입 이름이나 **버린 안의 근거**에 나온 ddd 어휘는 오탐이다. 버린 이유는 실제로 인용한 것을 인용해야 하므로 고치지 않는다
- **표시는 그 원칙의 첫 줄에 인용문 한 줄로 적고, 무엇이 없어서 안 되는지를 함께 적는다. 원칙 전체가 아니라 막히는 부분만 지목한다.** `> 이 원칙은 도메인 드리븐에만 성립한다 — 3계층에는 애그리거트가 없다.` 헤딩에 붙이면 grep 과녁이 흐려지고, front matter에 원칙 번호로 적으면 원칙을 쪼갤 때 조용히 어긋난다
- **표시를 달았으면 3계층 쪽 빈틈이 하나 는 것이다.** 장부에 새 항목을 만들지 않고 1번(「3계층만의 표준」)이 받는다. 셋 다 3계층 프로젝트가 돌기 시작하면 한꺼번에 닫히기 때문이다

자신만의 철학을 만들어가는 중입니다.
최상단으로 이동했습니다!
확대 이미지

댓글남기기