aster®

에이전트가 따를 DDD 구조와 의존성 규칙 정하기

마무리 · 2026-09

위쪽에서는 같은 사각형 셋이 서로 다른 상자로 흩어져 들어가고, 아래쪽에서는 같은 셋이 한 상자로 모인다. 그 상자 아래 가로로 긴 규칙 막대가 놓여 있다.

에이전트에게 "DDD 로 짜줘"라고 요청했더니 같은 성격의 파일을 매번 다른 위치에 만들었다. DDD라는 이름만으로는 우리 프로젝트에서 쓸 폴더 구조를 전달하기 어려웠다.

앞선 글 FSD 에서 DDD 아키텍처로 전환 끝에 후순위로 미뤄 둔 것을 하나 적어 뒀다. 폴더 나누는 기준을 FSD 에서 DDD 로 바꾼 뒤에도 그 규칙을 도구로 강제하지는 못했다. 사람하고 일할 때는 지난주 리뷰에서 나눈 말을 서로 기억하니 그럭저럭 됐다. 앞 글에서는 재사용 빈도를 기준으로 삼은 게 원인이었지만 이번 건은 원인이 더 앞에 있었다. 정한 걸 아무 데도 안 적어 뒀다.

다음 에이전트도 같은 구조를 따를 수 있도록 결정 내용을 파일로 남기기로 했다.

curvez 의 architecture-setup 은 정한 구조를 .curvez/architecture.md 파일 하나에 적어 둔다.

1. 프롬프트만으로 구조를 전달할 때의 한계

스킬 문서 첫 줄이 이렇게 시작한다.

아키텍처 확정은 설계가 아니라 선택과 기록이다.

아키텍처를 정하려면 고민부터 해야 할 것 같지만, 이 스킬은 그 고민을 줄이려고 만들었다. 프리셋을 골라 레이어 이름 등을 팀 용어에 맞춘 뒤, 검사할 수 있는 금지 import 목록으로 기록한다.

언제 쓰라고 적혀 있는지를 보면 의도가 더 분명하다. 목록의 마지막 줄이 "구현 에이전트가 '이 파일을 어느 레이어에 두는가' 로 매번 다르게 판단할 때" 다. 문서가 없어서 생기는 문제라 문서를 만들어 막겠다는 거다.

2. DDD 프리셋의 지원 범위

curvez 가 가진 아키텍처 프리셋은 ddd 하나다. 스킬 2단계는 "고를 것이 없으므로 이 단계는 확인만 하고 지나간다" 로 되어 있다. 한 줄 더 붙어 있다. 다른 구조를 쓰고 싶다는 요청이 없으면 되묻지 마라.

규모가 작을 때도 프리셋을 바꾸지는 않는다. 화면과 업무 규칙 사이에서 흐름만 조립하는 application 레이어를 빼고 presentation → domain 두 층으로 줄인다. 레이어를 삭제하는 쪽이라 비용도 적다. 판정은 취향이 아니라 수치로 하는데, 라우트 수와 엔티티 수 양쪽이 기준 미만이면 두지 않고 하나라도 넘으면 둔다.

그런데 이 숫자가 두 곳에서 다르다. 프리셋 ddd.md 는 "라우트 6개 이하 그리고 엔티티 4개 이하" 다. 에이전트 정의 curvez-architect.md 는 "라우트 12개 미만이고 엔티티 8개 미만" 이다. 프리셋 쪽은 "정확한 값은 에이전트 정의가 정본" 이라고 넘겨 놨다. 정본을 따르면 12/8 이다. 두 파일의 값이 다르다는 사실은 확인한 그대로 적어 둔다.

DDD 를 안 쓰겠다고 하면 대안 프리셋이 없다. 그 프로젝트의 구조를 인터뷰로 처음부터 만든다. 산출물 형식은 똑같다.

프리셋이 없다는 것은 초안이 없다는 뜻이지 규약이 없다는 뜻이 아니다.

그렇게 만든 구조는 다음 프로젝트로 전파되지 않는다. 검증되지 않은 구조가 프리셋으로 올라가면 기본값으로 굳어서다.

3. 아키텍처 인터뷰 항목

인터뷰는 3문 이상 5문 이하다. 레이어명과 경계 규칙만 조정하고 프리셋 자체는 재설계하지 않는다. 후보로 올라와 있는 문항은 이런 것들이다.

  1. 레이어 이름을 팀 용어로 바꿀 것인가 (application → usecase 같은)
  2. 공유 코드를 어느 레이어에 두고 무엇까지 넣을 것인가
  3. 경계 예외를 허용할 지점이 있는가, 있다면 어디까지인가
  4. (monorepo) 앱 간 공유를 패키지로 자를 것인가 폴더로 자를 것인가

묻지 않는 것도 못박아 뒀다. 어떤 기능을 만드는가는 requirements 가 이미 정했다. 라이브러리는 researcher 담당이다. 폴더 이름의 단복수는 답이 무엇이든 경계가 안 바뀐다. 도메인이 프레임워크를 참조해도 되는가는 아예 협상 대상이 아니라 항상 금지다.

5문까지만 묻는 이유도 적혀 있다. 6문째부터는 프리셋 없이 처음부터 설계하는 것과 같아지고 사용자가 근거 없이 답을 지어내기 시작한다. 그렇게 나온 답이 문서에 확정으로 굳으면 되돌릴 근거조차 안 남는다.

서브에이전트는 사용자에게 직접 못 묻는다. 그래서 경로가 둘이다. 워커를 띄우기 전에 오케스트레이터가 5문을 한 번에 물어 답을 프롬프트에 실어 보낸다. 아니면 이미 도는 워커가 blocked_on 에 질문을 담아 반환하고 오케스트레이터가 대신 묻고 재기동한다. 둘 다 안 되면 기본값으로 확정하고 건너뛴 문항마다 ## 결정 로그 에 되돌릴 위치를 적는다.

4. architecture.md에 기록할 내용

.curvez/architecture.md 는 헤딩 일곱 개를 이 문자열 그대로 담는다.

## 레이어 정의   ## 의존 방향   ## 금지 import   ## 폴더 구조   ## 스택 매핑   ## 예외   ## 결정 로그

결정 로그의 되돌릴 위치 를 .curvez/architecture.md:<헤딩> 형식으로 적기 때문에 문자열을 고정해 뒀다. 헤딩이 곧 앵커이고 자체 검증 grep 도 이 문자열을 찾는다.

일곱 중 절차가 실제로 값을 꺼내 쓰는 건 ## 금지 import 표다. 열 순서가 고정이고 세 번째 열이 grep -E 에 그대로 들어간다.

| 규칙 ID | 검사 경로 | 금지 패턴 (ERE) | 이유 |
| ARCH-001 | src/domain/ | from ['\"](next\|next/.*\|react\|react-dom) | 도메인은 프레임워크 교체에서 분리돼야 한다 |

이 한 줄의 뜻은 이렇다. src/domain/ 아래 파일에 from 'next' 나 from 'react' 같은 줄이 있으면 위반이다.

ARCH-001 은 규칙에 붙인 번호다. 리뷰에서 말로 하던 "이건 domain 에 두면 안 돼요" 를 번호로 부를 수 있게 된다. 번호는 ARCH-001 부터 이어 붙이고 최소 3건이다. 프리셋 기본 규칙은 도메인의 프레임워크 독립을 세 갈래로 나눠 검사한다. 프레임워크(001·002), 레이어 방향(003·004), I/O(005·006·007) 다. 하나만 빠져도 나머지 둘이 통과하면서 도메인은 독립이 아니게 된다.

함정도 하나 적혀 있다. 패턴 안의 | 는 \| 로 이스케이프한다. 안 하면 마크다운 표가 그걸 열 구분자로 읽는다. 게다가 검증 스크립트의 awk 가 필드 구분자를 공백-파이프-공백 으로 잡는다. from ['\"](next | react) 는 from ['\"](next 까지만 읽힌다. 괄호가 안 닫힌 정규식이 되고 만다.

파일은 Write 로 전체를 다시 쓴다. 부분 수정을 허용했더니 레이어 정의만 바꾸고 금지 import 표는 그대로 두는 일이 나왔기 때문이다.

다시 돌릴 때는 규칙이 하나 더 붙는다. 본문은 덮어쓰고 ## 결정 로그 는 한 줄도 안 지운다. 앞선 결정을 뒤집었으면 지우지 않는다. 새 행에 "ARCH-002 를 폐기하고 …로 교체" 처럼 적는다. 결정 로그가 사라지면 다음 재실행이 같은 질문을 다시 묻는다. 사용자는 이전과 다르게 답한다. 문서는 실행할 때마다 흔들려 버린다.

5. 규칙 검증과 완료 기준

문서를 쓴 직후에 세 가지를 실제로 돌리고 출력을 그대로 핸드오프에 옮긴다.

검사통과값
필수 헤딩 7개 grepMISSING 0건
^| ARCH-[0-9]{3} | 개수3 이상
3열 ERE 를 grep -E 에 실제로 먹여 보기REGEX-BAD 0건

표 1. 문서를 쓴 직후에 돌리는 세 가지 검사

세 번째 검사에서는 표의 정규식을 /dev/null 에 대고 한 번씩 실행해 본다. 적어 놓기만 해서는 실제로 도는지 알 수 없으니, 종료 코드로 문법 오류를 걸러낸다. awk 가 오류 없이 돌았는데 출력이 한 줄도 없으면 표 행 형식이 어긋난 것이다. 원본은 "이 함정은 실행 테스트로 두 번 잡혔다" 고 적어 뒀다.

프리셋은 이 표로 규칙을 검사하고 ESLint 로 집행하도록 역할을 나눠 놨다.

ARCH 표 (grep)ESLint
언제감사·리뷰 시점편집 시점, 커밋, CI
정확도문자열 매칭import 구문 파싱
역할규칙이 무엇인지 선언규칙을 집행

표 2. ARCH 표와 ESLint 의 역할 구분

앞 글에서 후순위로 미뤄 뒀던 것을 여기서 해결한다. 확정 직후 eslint.config.mjs 에 계층 규칙을 넣는다. 넣은 다음에 위반을 일부러 만들어 에러가 나는지 확인한다. 설정 오류로 규칙이 로드되지 않아도 lint 는 조용히 통과한다. 원본은 이걸 "검사가 안 돌았는데 통과로 보이는 것" 이라고 부른다. 가장 자주 나온 실패로 꼽았다.

6. 스킬 호출 방법

"아키텍처 잡아줘", "구조 설계해줘", "레이어 나눠줘", "폴더 구조 정해줘", "DDD 로 갈까" 중 아무거나면 뜬다. 구현 시작 전에 .curvez/architecture.md 가 없어도 뜬다.

앞 편에서 만든 .curvez/profile.json 이 있어야 진행할 수 있다. 스택을 추측해 만든 레이어 매핑과 금지 import 는 전부 무효라서 그렇다. 프로파일을 만드는 건 bootstrap 이다. 이 스킬은 그다음이다.

그렇게 해서 저장소에 남는 것이 아래 두 파일이다.

verification 은 요약하지 말라고 못박아 뒀다. 소스 트리가 없어 검사 대상이 0개여도 실패가 아니다. "검사 대상 0개 (소스 미생성). 정규식 문법 검사만 통과" 라고 그대로 적는다.

7. 프리셋 관리에 필요한 작업

문서를 작성한 뒤에도 프리셋과 검사 규칙을 관리해야 한다.

치르는 것내용
고를 게 없다프리셋이 ddd 하나다. 다른 구조는 인터뷰로 처음부터 만들어야 하고 그렇게 만든 구조는 그 프로젝트에만 남는다
정규식으로 못 쓰면 등급이 내려간다표현할 수 없는 규칙은 ## 예외 가 아니라 ## 권고 로 간다. 검사할 수 없는 규칙은 위반해도 아무 일이 없고, 아무 일이 없는 규칙은 3주 뒤에 존재하지 않는다는 게 이유다
grep 은 놓치는 게 있다문자열 매칭이라 주석 안의 import 를 세고, 줄바꿈된 import 를 놓치고, 동적 import() 를 못 본다. 진짜 집행은 ESLint 설정을 따로 해야 붙는다
안 물어본 것은 기본값으로 굳는다5문 상한이라 나머지는 프리셋 값이 그대로 확정된다. 되돌릴 위치가 적혀 있을 뿐, 저절로 되돌아오지는 않는다
위반 0건이 깨끗함은 아니다소스가 아직 없으면 실측이 0건으로 나온다. 실제 위반 검출은 structure-audit 이 코드가 생긴 뒤에 한다

표 3. 이 스킬을 쓸 때 감수하는 것

경계를 지키는 스킬로 보기 쉬운데, 이 스킬은 판정 기준을 만들 뿐이다. 그 기준으로 코드를 판정하는 건 다른 스킬이다. 한 스킬이 둘 다 하면 아직 없는 코드를 감사하거나 이미 확정된 문서를 다시 쓴다.

8. 다음 에이전트에 구조 전달하기

정한 구조를 다음 에이전트에게 넘기려면 헤딩 문자열이 고정된 파일 하나와, 그 안에 grep -E 가 읽을 수 있는 표 세 줄이 필요했다.