위키에서 본 모든 아키텍처 다이어그램은 틀렸다. 극적으로 틀린 건 아니다. 조용히, 점진적으로 틀린 것이다. “Auth”라고 적힌 서비스는 6개월 전에 세 개의 microservice로 쪼개졌다. “sync call”이라고 표시된 화살표는 이제 queue를 통해 async로 동작한다. “PostgreSQL”이라고 적힌 데이터베이스는 장애 대응 중에 다른 것으로 마이그레이션되었는데, 아무도 그 상자를 업데이트하지 않았다.
이건 부주의가 아니다. 물리학이다. 코드는 일주일에 수백 번 바뀐다. 문서는 누군가 기억할 때 바뀐다. 아키텍처 다이어그램의 반감기는 대략 한 스프린트쯤이다. 그 이후로는 소설이 된다.
목표는 절대 틀어지지 않는 문서를 만드는 게 아니다. 다음 엔지니어를 현혹시키기 전에 그 착오를 감지하는 것이 목표다.
왜 문서는 코드보다 빨리 썩는가
코드에는 내장된 교정 메커니즘이 있다: 컴파일되고 테스트를 통과하거나, 아니면 통과하지 못한다. 문서에는 compiler도, test suite도, CI gate도 없다. 틀린 다이어그램도 완벽하게 렌더링된다. 낡은 ADR은 신선한 ADR만큼이나 설득력 있게 읽힌다.
이 불일치는 사회적인 측면도 있다. 엔지니어는 build가 깨지기 때문에 코드를 업데이트한다. 문서를 업데이트하는 건 죄책감 때문이다. 죄책감은 믿을 수 없는 스케줄러다.
새로운 엔지니어가 입사해서 wiki를 읽으면, 시스템에 대한 mental model을 구축한다. 그 모델이 6개월 전의 것이라면, 기존의 엉망을 가중시키는 결정을 내리게 된다. 온볼딩 마찰을 줄여야 할 문서가 미묘하고 비싼 오판의 원천이 되는 것이다.
세 가지 소설의 함정
아키텍처 문서가 가장 빨리 소설이 되는 곳이 세 곳 있다.
위키 묘지. Confluence, Notion, SharePoint에 있는 아키텍처 문서는 저장소 밖에 산다. 코드 변경과 연결된 commit history가 없다. 리팩토링으로 서비스가 삭제되어도, 위키 페이지는 디지털 유령 도시처럼 살아남는다. build가 깨지지 않는다. alert도 울리지 않는다.
다이어그램의 허구. Lucidchart나 draw.io에서 내보낸 아름다운 다이어그램을 README에 붙여넣으면 권위적으로 보인다. 하지만 그건 static image일 뿐이다. 아키텍처를 변경하는 다음 개발자는 디자인 도구를 열고, 소스 파일을 찾고, 업데이트하고, 다시 내보내고, 붙여넣어야 한다. 그 워크플로우의 생존율은 5%다.
전지전능한 README. 모든 서비스, 모든 의존성, 모든 데이터 흐름을 하나의 파일에 담으려는 최상위 README. 그건 모두가 건드리기 두려워하는 mega-document가 된다. 결국 용감한 이가 “이 섹션은 outdated일 수 있습니다”라는 메모를 단다. 그리고 또 하나. 그러다 전체 문서가 scare quotes로 감싸져 버리고 버려진다.
정직함을 유지하는 살아있는 문서
해결책은 더 많은 문서를 쓰는 게 아니다. 문서를 피할 수 없게 만드는 것이다.
문서를 저장소로 옮겨라. 문서가 코드 변경과 같은 pull request에 있지 않다면, 업데이트되지 않는다. ADR, runbook, 결정 기록은 기술하는 코드 옆의 docs/architecture/나 docs/adr/에 있어야 한다. 리뷰어가 문서 업데이트 없는 리팩토링을 본다면, merge를 막을 수 있다.
코드에서 다이어그램을 생성하라. Mermaid, PlantUML, Structurizr은 저장소에 있는 텍스트 파일로 다이어그램을 정의할 수 있게 한다. CI job이 매 commit마다 렌더링한다. 아키텍처가 바뀌면 다이어그램 소스도 함께 바뀐다. 디자인 도구는 필요 없다.
<!-- docs/architecture/data-flow.md -->
## Ingestion Pipeline
```mermaid
graph LR
A[Client SDK] -->|HTTP POST| B[Ingest API]
B -->|Pub/Sub| C[Event Consumer]
C -->|Write| D[(ClickHouse)]
이 다이어그램은 diff에서 리뷰할 수 있다. 파이프라인에 새로운 Kafka topic이 추가되면, 그 변경사항은 consumer를 추가하는 같은 PR에 나타난다.
**"무엇(what)"과 "왜(why)"를 분리하라.** 코드베이스는 이미 시스템이 무엇을 하는지를 설명한다. comment, function name, type이 현재 구조를 인코딩한다. 문서는 시스템이 왜 이런 모습인지에 집중해야 한다. 조용히 틀어지는 건 결정 자체가 아니라, 결정 뒤의 reasoning이다.
Architecture Decision Record가 이걸 포착한다. ADR은 spec이 아니다. "이 날짜에, 이런 이유로, 우리는 이걸 선택했다. 우리가 받아들인 제약은 이러하다"라고 적힌 timestamped note다. 제약이 바뀌면, ADR은 새로운 것으로 supersede된다. 오래된 기록은 역사로 남는다.
## "무엇"은 자동화하고, "왜"는 직접 써라
가장 정직한 아키텍처 문서는 손으로 쓰지 않는 것들이다.
controller code에서 생성된 OpenAPI spec은 API surface를 정확히 기술한다. TypeDoc, RustDoc, Javadoc은 내부 type을 기술한다. `cargo tree`, `go mod graph`, `webpack-bundle-analyzer`가 생성한 dependency graph는 실제 module structure를 보여준다.
더 높은 수준의 아키텍처 제약을 위해, automated architecture test가 safety net 역할을 한다. Java에서는 ArchUnit이 "`domain` 패키지가 `infrastructure`에 의존하면 안 된다" 같은 규칙을 강제할 수 있다. Python에서는 `import-linter`가 같은 일을 한다. Go에서는 AST를 순회하는 작은 test를 작성해서, forbidden import가 나타나면 build를 실패시킬 수 있다.
```java
// This test fails the build if architecture rules break.
@ArchTest
static final ArchRule domain_independence =
noClasses()
.that().resideInAPackage("..domain..")
.should().dependOnClassesThat()
.resideInAPackage("..infrastructure..");
개발자가 실수로 잘못된 패키지를 import하면, build는 즉시 깨진다. 아키텍처 문서가 경고할 필요가 없다. compiler가 그 일을 한다.
아무도 인정하지 않는 트레이드오프
살아있는 문서는 공짜가 아니다. 리뷰 시간, CI 분, 그리고 변경사항이 새 ADR을 warrant하는지에 대한 가끔의 논쟁이 든다.
모든 시스템에 다이어그램이 필요한 건 아니다. 세 개의 레이어를 가진 단일 monolith는 C4 model이 필요 없다. 생성된 문서를 유지하는 노력은 시스템을 오해하는 고통에 비례해야 한다. 틀린 mental model이 하루의 디버깅을 비용으로 산다면, 한 시간을 다이어그램에 투자하라. 5분이라면, comment를 달아라.
생성된 문서도 거짓말할 수 있다. 코드에서 생성된 OpenAPI spec은 문서화하는 걸 잊어버린 internal endpoint까지 모두 기술한다. dependency graph는 아키텍처를 위반하는 import까지 모두 보여준다. 생성된 진실은 정확하지만 항상 유용한 건 아니다. 무엇이 중요한지 강조하기 위해 여전히 human curation이 필요하다.
ADR은 쌓인다. 2년이 지나면 docs/adr/에 서른 개의 ADR이 있을 수 있다. 대부분은 irrelevant할 것이다. 해결책은 삭제하는 게 아니다. 명확하게 표시하는 것이다: replacement로의 링크와 함께 status: superseded. 역사에는 가치가 있다. 혼란에는 가치가 없다.
동작하는 최소한의 설정
문서 플랫폼은 필요 없다. 저장소에 세 가지만 있으면 된다.
- 결정을 위한
docs/adr/YYYY-MM-DD-title.md. 가벼운 템플릿을 사용하라:
# ADR 012: Event Storage Backend
- Status: accepted
- Date: 2026-03-15
## Context
We need durable storage for ingestion events with sub-second query latency.
## Decision
Use ClickHouse for hot storage, S3 for cold archival.
## Consequences
- Fast analytical queries on recent data.
- Complex operational footprint compared to PostgreSQL.
- Migration path defined in ADR 013.
-
시스템 경계와 데이터 흐름을 위한 Mermaid 다이어그램이 들어간
docs/architecture/*.md. 구조적 코드 변경과 같은 PR에서 문서 업데이트를 요구하라. -
가장 비싼 invariant를 강제하는 하나의 architecture test. 규칙이 하나뿐이라면, 마지막으로 고통스러웠던 리팩토링을 막는 규칙으로 하라. 시스템이 성장하면서 더 추가하라.
FAQ
모든 microservice를 다이어그램으로 그려야 하나요?
아니다. 팀이 인수인계하는 경계, 데이터가 trust zone을 넘나드는 곳, 장애가 cascade되는 곳을 다이어그램으로 그려라. 내부 CRUD service의 다이어그램은 보통 낭비다. 어떤 서비스가 payment gateway를 호출하는지의 다이어그램은 보통 가치 있다.
팀이 이미 Confluence를 사용하고 있다면?
중요한 문서를 저장소에 미러링하고 링크를 걸어라. source of truth는 git에 남는다. 위키는 convenience layer가 된다. 위키가 drift되어도, 엔지니어는 실제 버전을 어디서 찾을지 안다.
ADR이 너무 많으면 언제쯤인가?
상한선은 없지만, readability limit은 있다. 새로운 엔지니어가 docs/adr/를 훑어보고 10분 안에 시스템의 evolution을 이해하지 못한다면, index를 추가하라. subsystem별로 그룹화하라. superseded된 기록을 명확히 표시하라.
비기술적 이해관계자를 위한 위키는?
제품 관리자와 임원들은 high-level summary가 필요하다. ADR index에서 그것들을 생성하라. 별도의 narrative를 유지하지 마라. 같은 사실이 source code에서 ADR을 거쳐 인간이 읽는 summary로 흘러가야 한다. 하나의 source of truth, 여러 개의 view.
하나의 ADR과 하나의 테스트로 시작하라
이번 주에 문서화 문화를 전면 개편할 필요는 없다. 혼란을 야기한 마지막 아키텍처 결정을 고르라. 왜 그 선택을 했는지 설명하는 하나의 ADR을 작성하라. 자동으로 강제되길 바랐던 invariant 하나를 고르라. 누군가 위반하면 build를 실패시키는 하나의 architecture test를 작성하라.
이 두 artifact는 저장소에 살고, PR에서 리뷰되며, CI를 통과하거나 배포되지 않기 때문에 정직함을 유지할 것이다. 나머지 모든 것 — 위키 페이지, slide deck, 컨퍼런스 포스터 — 는 부차적이다. 있으면 좋다. 유용할 수도 있다. 아마 틀렸을 것이다.