C4 Code 다이어그램(레벨 4): 언제 필요하고 언제 필요 없는가
코드 다이어그램은 C4 모델에서 가장 오해받는 레벨입니다. 대부분의 팀이 건너뛰는 레벨이고, Simon Brown 본인이 선택 사항이라고 설명하는 레벨이며, 그럼에도 가장 많은 질문을 만들어내는 레벨입니다: "우리가 정말로 클래스를 그려야 하나요?" "이건 그냥 UML 아닌가요?" "코드가 바뀌면 누가 이걸 유지하나요?"
짧은 답: 대부분의 경우, C4 코드 다이어그램은 필요 없습니다. 더 긴 답은 더 흥미롭습니다. 왜냐하면 레벨 4가 실제로 값을 하는 경우는 정확히 팀이 그것 없이는 가장 많은 시간을 잃는 경우 — 밀도 높은 알고리즘, 규제 대상 도메인, 그리고 더 이상 아무도 완전히 이해하지 못하는 코드로의 온보딩 — 이기 때문입니다.
이 가이드는 C4 코드 다이어그램이 실제로 무엇을 보여주는지, 왜 손으로 그리는 것이 거의 항상 실수인지, C4 모델 레벨 4가 가질 가치가 있는 구체적인 상황, 그리고 자동 생성이 비용-편익 계산을 어떻게 완전히 뒤집는지 다룹니다.
C4 모델이 처음이라면, 먼저 C4 모델 완벽 가이드부터 읽으세요 — 이 글은 당신이 네 가지 레벨을 안다고 가정합니다.
C4 Code 다이어그램이란 무엇인가?
코드 다이어그램은 C4 모델의 레벨 4입니다 — 가장 깊은 줌 레벨. 컴포넌트 다이어그램(레벨 3)이 컨테이너 내부의 주요 빌딩 블록을 보여준다면, 코드 다이어그램은 단일 컴포넌트 안으로 확대하여 그 구현 수준의 세부 사항을 보여줍니다:
- 클래스와 그 관계(상속, 합성, 의존)
- 인터페이스와 그것을 구현하는 타입들
- 함수와 메서드, 주요 시그니처 포함
- 엔티티, 값 객체, DTO 같은 데이터 구조
실무에서 C4 레벨 4 다이어그램은 보통 UML 클래스 다이어그램이나 엔티티-관계 다이어그램으로 렌더링됩니다. C4 모델은 이 레벨에 대한 표기법을 규정하지 않습니다 — 클래스를 그리는 문제는 수십 년 전에 해결되었기 때문에 명시적으로 기존 표준에 위임합니다.
코드 레벨을 정의하는 두 가지 속성:
- 범위: 하나의 컴포넌트. 코드 다이어그램은 결코 컨테이너 전체에 걸치지 않으며, 시스템은 더욱 아닙니다. 그것은 정확히 하나의 컴포넌트 —
OrderRepository,PaymentService— 를 열어 그 안에 무엇이 있는지 보여줍니다. - 세분도: 실제 코드 심볼. 코드 다이어그램의 모든 상자는 소스의 실제 클래스, 인터페이스, 또는 함수에 대응합니다. 남은 추상화가 없습니다. 이것은 다이어그램과 코드가 같은 것이어야 하는 레벨입니다.
그 두 번째 속성이 바로 레벨 4가 그토록 자주 건너뛰어지는 이유입니다. 곧 보게 되겠지만요. (빠른 참조는 용어집의 코드 다이어그램 항목을 참고하세요.)
Simon Brown 본인의 권고: 그리지 마라
C4 모델을 처음 접하는 사람들을 놀라게 하는 부분이 여기 있습니다: 그 창시자는 코드 다이어그램을 손으로 만드는 것에 반대하기를 권합니다. Simon Brown의 지침은 레벨 4가 선택 사항이며, 정말 필요할 때는 손으로 그리고 유지하기보다 — IDE, 문서화 도구, 또는 실제 코드를 읽는 다른 무엇에 의해 — 소스 코드로부터 필요에 따라 생성되어야 한다는 것입니다.
그 논리는 반박하기 어렵습니다:
- 코드는 끊임없이 바뀝니다. 손으로 그린 클래스 다이어그램은 대략 누군가 다음 풀 리퀘스트를 병합하는 데 걸리는 시간만큼만 정확합니다. 모든 이름 변경, 모든 추출된 메서드, 모든 새 필드가 그것을 조금씩 더 틀리게 만듭니다.
- 정보는 이미 존재합니다. 아무의 머릿속에도, 어떤 단일 파일에도 살지 않는 결정을 포착하는 컨테이너 다이어그램과 달리, 코드 다이어그램은 소스 코드가 이미 정확하게 진술하는 것을 복제합니다.
- 도구가 더 잘합니다. 현대 IDE(IntelliJ, Visual Studio 등)는 몇 초 만에 코드로부터 클래스 다이어그램을 생성합니다. 그것들은 그려진 것이 아니라 도출된 것이기 때문에 항상 정확합니다.
따라서 C4를 채택하는 모든 팀의 기본 입장은 다음과 같아야 합니다: 레벨 1-3을 모델링하고, 필요할 때 레벨 4를 생성하라. 컨텍스트, 컨테이너, 컴포넌트 다이어그램은 코드에 없는 지식을 포착합니다. 코드 레벨은 바로 코드입니다.
C4 Code 다이어그램이 실제로 값을 하는 때
"보통 건너뛰어라"는 "항상 건너뛰어라"가 아닙니다. 코드 수준 뷰가 문서에서 제자리를 차지하는 실제 상황이 있습니다.
1. 복잡한 알고리즘과 정교한 설계
어떤 컴포넌트는 파일을 위에서 아래로 읽어서는 정말로 따라가기 어려운 로직을 구현합니다: 체인으로 연결된 전략을 가진 가격 책정 엔진, 주문 생명주기를 관장하는 상태 기계, 깊은 비지터 계층을 가진 파서. 구조가 곧 통찰일 때 — 어떤 클래스가 어떤 클래스에 위임하는지 이해하는 것이 싸움의 전부일 때 — 코드 다이어그램은 몇 시간의 코드 읽기를 하나의 그림으로 압축합니다.
판별 기준: 시니어 엔지니어가 다른 시니어 엔지니어에게 컴포넌트를 설명하기 위해 화이트보드가 필요하다면, 그 화이트보드 스케치는 포착되기를 기다리는 코드 다이어그램입니다.
2. 규제 대상 및 감사 대상 도메인
금융, 의료, 항공, 그리고 다른 규제 산업에서, 감사관과 평가자는 종종 구현 수준 문서를 요구합니다: 어떤 클래스가 카드 소지자 데이터를 처리하는지, 암호화가 어디에 적용되는지, 감사 추적이 어떻게 작성되는지. 여기서 레벨 4 다이어그램은 있으면 좋은 것이 아니라 — 증거입니다. 특정 클래스를 통한 데이터 흐름이 중요한 보안 검토와 위협 모델링에도 같은 것이 적용됩니다.
3. 밀도 높고 오래된 코드로의 온보딩
수년간 축적된 결정이 담긴 레거시 컴포넌트는 신규 합류자에게 가혹합니다. 코드베이스에서 가장 무서운 서너 개의 컴포넌트 — 부족 지식이 집중된 것들 — 의 코드 다이어그램은 온보딩 시간을 극적으로 줄일 수 있습니다. 신입 개발자는 8,000줄에 뛰어들기 전에 컴포넌트의 형태를 봅니다.
4. 공개 계약과 디자인 패턴 문서화
컴포넌트가 다른 팀이 빌드하는 대상이 되는 API 표면을 노출하거나, 구조가 곧 문서인 패턴(Strategy, Observer, 헥사고날 포트-앤-어댑터)을 구현한다면, 코드 수준 뷰가 그 계약을 명시적으로 만들어 줍니다.
필요 없는 때(대부분의 경우)
반대 경우에 대해 솔직해지세요. 그것이 어떤 시스템에서든 컴포넌트의 대다수를 차지하기 때문입니다:
- 코드가 읽기 쉬울 때. 명확한 패키지를 가진 잘 명명된 컴포넌트는 다이어그램이 필요 없습니다. 폴더 트리가 곧 다이어그램입니다.
- 레벨 3이 이미 질문에 답할 때. 누군가 "주문 서비스가 결제와 어떻게 통신하나요?"라고 묻는다면, 그것은 컴포넌트 다이어그램 질문입니다. 클래스를 그리는 것은 신호가 아니라 노이즈를 더합니다.
- 컴포넌트가 단순한 CRUD일 때. 핸들러, 서비스, 리포지토리, 모델. 모두가 이 형태를 수천 번 봤습니다. 그것을 클래스 수준에서 문서화하는 것은 아무것도 문서화하지 않습니다.
- 아무도 요청하지 않았을 때. 문서는 사람들이 실제로 가진 질문에 답해야 합니다. 누구도 컴포넌트의 클래스 수준 뷰를 필요로 한 적이 없다면, 하나를 만드는 것은 선반용품에 쓴 노력입니다.
더 넓은 C4 접근법의 지도 원칙이 여기에 온전히 적용됩니다: 당신이 만드는 모든 다이어그램은 당신이 유지해야 하는 다이어그램입니다. 레벨 1-3에서는 유지보수 비용이 다른 어디에도 존재하지 않는 지식을 사줍니다. 레벨 4에서는 보통 약간 더 예쁘고 약간 노후화된 소스 코드의 복사본을 사줍니다.
자동 생성이 방정식을 어떻게 바꾸는가
위의 모든 것은 누군가가 다이어그램을 그리고 유지해야 한다고 가정합니다. 그 가정이 바로 Simon Brown의 "건너뛰거나 생성하라"는 조언을 올바른 판단으로 만든 것입니다 — 수동 레벨 4의 비용은 거의 결코 그 편익을 정당화하지 못했습니다.
자동화는 그 방정식의 비용 측면을 제거합니다. Archyl의 AI 디스커버리는 연결된 리포지토리를 분석하고 코드 수준 요소 — 클래스, 인터페이스, 함수 — 를 소스로부터 직접 추출하여, 그것들을 C4 모델의 올바른 컴포넌트에 붙입니다. 개발자가 다이어그램 도구에서 PaymentService와 그 협력자들을 그리는 대신, 모델은 코드베이스에 실제로 존재하는 것으로부터 채워지고, 그것이 비롯된 리포지토리에 연결된 채로 유지됩니다.
이것은 질문을 "레벨 4가 유지할 가치가 있는가?"에서 "레벨 4가 가질 가치가 있는가?"로 바꿉니다 — 훨씬 넘기 쉬운 기준입니다. 코드 수준 뷰가 손으로 그려지는 대신 생성되고 갱신될 때:
- 그것은 결코 거짓말하지 않습니다. 누군가의 소스에 대한 기억이 아니라 소스로부터 도출되기 때문입니다.
- 그것은 맥락 속에서 탐색 가능합니다: 위키에서 PNG를 찾아 헤매는 대신, 하나의 모델에서 시스템에서 컨테이너로, 컴포넌트로, 코드로 파고듭니다.
- "비용에도 불구하고 가치가 있던" 경우들(감사, 온보딩, 복잡한 컴포넌트)은 명백히 가치 있게 되고, 한계 경우들은 공짜가 됩니다.
여전히 어떤 컴포넌트가 레벨 4에서 주목받을 자격이 있는지에 대해 판단을 적용해야 합니다 — 사소한 CRUD 컴포넌트의 생성된 뷰는 여전히 노이즈입니다. 하지만 실패 모드는 "노후화된 다이어그램이 팀을 오도한다"에서 "비용이 들지 않는 사용되지 않는 뷰"로 바뀝니다.
작성된 예제: 결제 처리 컴포넌트 내부
이것을 구체적으로 만들어 봅시다. 컴포넌트 다이어그램(레벨 3)이 백엔드 API 컨테이너 내부에 PaymentProcessor 컴포넌트를 보여준다고 가정해 봅시다. 레벨 4에서 그것 안으로 확대하면 다음이 드러날 수 있습니다:
[PaymentService (interface)]
├── ProcessPayment(order, method) -> PaymentResult
└── RefundPayment(paymentID, amount) -> RefundResult
[StripePaymentService] ──implements──> [PaymentService]
[StripePaymentService] ──uses──> [StripeAdapter] : wraps the Stripe SDK
[StripePaymentService] ──uses──> [RetryPolicy] : exponential backoff, 3 attempts
[StripePaymentService] ──uses──> [PaymentRepository] : persists payment records
[StripeAdapter] ──maps──> [PaymentResult] : translates Stripe responses to domain types
[RetryPolicy] ──raises──> [PaymentFailedError] : after exhausting retries
[PaymentRepository] ──persists──> [Payment (entity)]
이 뷰가 레벨 3이 할 수 없는 무엇에 답하는지 주목하세요:
- 테스트를 위한 이음새는 어디인가?
PaymentService는 인터페이스입니다. 테스트는 Stripe를 건드리지 않고 가짜를 대체할 수 있습니다. - 두 번째 제공자는 어디에 끼워질까? 동일한 인터페이스를 구현하는
PaypalPaymentService— 구조가 확장 지점을 명백하게 만듭니다. - 실패 시 무슨 일이 일어나는가?
RetryPolicy클래스가 백오프 동작을 소유합니다.PaymentFailedError가 에스컬레이션 경로입니다. "결제가 실패하면 무슨 일이 일어나나요?"라고 묻는 감사관은 다이어그램으로부터 답을 얻습니다. - 무엇이 데이터베이스를 건드리는가? 오직
PaymentRepository. 카드 소지자 데이터 흐름이 추적 가능하며, 이는 PCI 범위 컴포넌트에서 엄청나게 중요합니다.
이것은 레벨 4가 제 값을 하는 컴포넌트입니다: 그것은 금융적으로 민감하고, 실패 처리가 명확하지 않으며, 여러 클래스가 의도적인 패턴으로 협력합니다. 이것을, 말하자면, 프로필 레코드를 읽고 쓰는 UserProfileHandler와 대조해 보세요 — 그것을 클래스 수준에서 그리는 것은 파일 트리가 알려주지 않는 것을 아무것도 알려주지 않을 것입니다.
C4 레벨 4의 흔한 실수
클래스 다이어그램을 손으로 유지하기
전형적인 실패입니다. 의욕적인 엔지니어가 전체 코드베이스에 대해 아름다운 클래스 다이어그램을 그립니다. 6개월 후 그것들은 더 이상 존재하지 않는 코드베이스를 설명하고, 이제 적극적으로 오도합니다. 코드 다이어그램이 소스로부터 생성되지 않는다면 — 또는 최소한 일정에 따라 재생성되지 않는다면 — 그려진 날 만료된 것으로 취급하세요.
게터, 세터, 보일러플레이트 문서화하기
모든 접근자, 생성자 오버로드, 유틸리티 메서드를 나열하는 코드 다이어그램은 1:1 축척의 지도와 같은 문제를 가집니다. 설계 의도를 담은 요소 — 인터페이스, 핵심 협력, 계약을 정의하는 메서드 — 를 포함하고, 의례적인 것은 생략하세요. 생성된 뷰는 덤프되는 것이 아니라 필터링되어야 합니다.
레벨 3으로 충분한 곳에 레벨 4 사용하기
당신의 "코드 다이어그램"이 "컨트롤러가 서비스를 호출하고 서비스가 리포지토리를 호출함" 같은 것을 보여준다면, 클래스 모양의 상자로 컴포넌트 다이어그램을 그린 것입니다. 그것을 레벨 3으로 접으세요. 레벨 4는 내부 구조가 흥미로운 부분인 컴포넌트를 위해 남겨두세요.
전체 컨테이너를 클래스 수준에서 그리기
서비스 전체에 걸친 200개 클래스 다이어그램은 C4 계층 구조를 위반하며 어차피 읽을 수 없습니다. 하나의 코드 다이어그램은 하나의 컴포넌트를 다룹니다. 그렇게 단단히 범위를 정할 수 없다면, 레벨 3 다이어그램의 컴포넌트 경계가 아마 다시 생각해 볼 필요가 있습니다.
"완전한" 문서화를 위해 레벨 4를 필수로 취급하기
어떤 팀은 체크리스트 사고방식으로 C4를 채택합니다: 네 가지 레벨이니까 네 세트의 다이어그램. 그 결과는 낭비된 노력과 그 실천 전체에 대한 반감입니다. C4는 레벨 4가 선택 사항임을 명시합니다. 잘 유지된 세 레벨이 썩어가는 네 레벨을 매번 이깁니다.
FAQ
C4 레벨 4는 그냥 UML 클래스 다이어그램인가요?
대부분 그렇습니다 — 그리고 그것은 설계상 그렇습니다. C4 모델은 코드 레벨에 대한 자체 표기법을 정의하지 않습니다. 기존의 것을 재사용하기를 권하며, UML 클래스 다이어그램이 가장 흔한 선택입니다(ER 다이어그램은 데이터 중심 컴포넌트에 적합합니다). C4가 더하는 것은 범위 지정과 컨텍스트입니다: 클래스 다이어그램은 정확히 하나의 컴포넌트를 설명하고, 그 컴포넌트는 컨테이너와 시스템 다이어그램의 탐색 가능한 계층 구조 안에 자리합니다. 독립형 UML 클래스 다이어그램은 자유롭게 떠다닙니다. C4 코드 다이어그램에는 주소가 있습니다.
모든 컴포넌트에 대해 코드 다이어그램을 만들어야 하나요?
아니요. 대부분의 컴포넌트는 소스 코드가 최선의 문서일 만큼 충분히 단순합니다. 레벨 4는 알고리즘적으로 복잡하거나, 보안 또는 컴플라이언스에 민감하거나, 악명 높은 온보딩 장애물인 컴포넌트를 위해 남겨두세요. 문서가 자동으로 생성된다면, 더 많은 커버리지를 가지는 비용이 떨어집니다 — 하지만 주의력은 여전히 유한하므로, 무엇을 드러낼지 선별하세요.
코드 다이어그램을 어떻게 최신으로 유지하나요?
손으로 유지하지 않음으로써. 그것을 생성하세요: IDE에서 필요에 따라, CI의 문서화 도구로부터, 또는 AI 디스커버리 중에 리포지토리로부터 클래스, 인터페이스, 함수를 추출하고 코드 레벨을 컴포넌트 모델에 붙여두는 Archyl 같은 플랫폼으로부터. 리팩터 후 클래스 다이어그램을 업데이트하기를 사람이 기억하는 것에 의존하는 어떤 프로세스도 실패할 것입니다.
C4에서 컴포넌트와 코드 요소의 차이는 무엇인가요?
컴포넌트(레벨 3)는 단일 책임을 가진 논리적 묶음입니다 — "결제를 처리하는 부분" — 이며 여러 클래스와 파일에 걸칠 수 있습니다. 코드 요소(레벨 4)는 소스의 실제 심볼입니다: 특정 클래스, 인터페이스, 또는 함수. 컴포넌트는 당신이 선택하는 추상화이고, 코드 요소는 컴파일러가 강제하는 사실입니다.
결론
C4 코드 다이어그램은 기본적으로 건너뛰고 의도적으로 배치해야 하는 레벨입니다. 레벨 1-3은 다른 어디에도 존재하지 않는 아키텍처 지식을 포착합니다. 레벨 4는 코드가 이미 말하는 것을 비추므로, 구조 자체가 어려운 부분일 때 — 복잡한 알고리즘, 규제 대상 컴포넌트, 밀도 높은 레거시 코드 — 에만 제자리를 얻으며, 이상적으로는 그려지는 대신 생성될 때입니다.
유지보수 세금 없이 코드 수준 뷰를 원한다면, 리포지토리를 Archyl에 연결하고 AI 디스커버리가 클래스, 인터페이스, 함수를 C4 모델로 자동 추출하게 하세요. 필요할 때 깊이를 얻고, 누구도 다시는 클래스 다이어그램을 다시 그릴 필요가 없습니다.
한 레벨 위로 가고 싶으신가요? C4 컴포넌트 다이어그램 가이드를 읽거나, C4 모델 완벽 가이드로 복습하거나, C4 모델 개요를 탐색하세요. 시도할 준비가 되셨나요? Archyl을 무료로 시작하세요.