C4 Dynamic 다이어그램(동적 다이어그램): 예제와 함께 보는 가이드

컨테이너 다이어그램은 API가 주문 서비스와 통신하고, 주문 서비스가 Kafka와 통신하며, 알림 서비스가 Kafka에서 읽는다는 것을 알려 줍니다. 하지만 고객이 Place order를 클릭했을 때 무엇이 어떤 순서로 일어나는지는 알려 주지 않습니다. 결제는 주문 레코드가 기록되기 전에 이루어질까요, 후에 이루어질까요? 확인 이메일은 창고 처리를 기다릴까요? 장애 리뷰에서 사람들이 묻는 질문이 바로 이런 것들인데, 정적인 다이어그램으로는 답할 수 없습니다.

그것이 C4 동적 다이어그램의 역할입니다. 이미 그려 둔 요소들을 가져와, 하나의 구체적인 시나리오에서 그 사이에 일어나는 상호작용에 번호를 매깁니다. 이 가이드는 동적 다이어그램이 무엇인지, UML 시퀀스 다이어그램과 어떻게 다른지, 언제 그릴 가치가 있는지(생각보다 드뭅니다), 완전한 실습 예제, 흔한 실수, 그리고 정적 모델이 바뀔 때 낡지 않게 유지하는 방법을 다룹니다.

C4가 처음이라면 C4 모델이란 무엇인가부터 시작하세요. 아래의 실습 예제는 Container 다이어그램 가이드에서 다룬 종류의 다이어그램을 바탕으로 합니다.

동적 다이어그램이란

동적 다이어그램은 C4 모델의 보조 다이어그램 중 하나로, 시스템 랜드스케이프 다이어그램 및 배포 다이어그램과 나란히 있습니다. 네 가지 핵심 레벨 중 하나가 아닙니다. 그 옆에 자리하며 그 요소들을 빌려 씁니다.

c4model.com의 정의는 짧습니다:

  • 범위: "특정 기능, 스토리, 유스케이스 등"
  • 요소: "자유롭게 선택 — 런타임에 상호작용하는 소프트웨어 시스템, 컨테이너 또는 컴포넌트를 보여 줄 수 있습니다."
  • 대상 독자: "소프트웨어 개발 팀 안팎의 기술직과 비기술직 사람들."
  • 권장 여부: "아니요. 동적 다이어그램은 흥미롭거나 반복되는 패턴, 또는 복잡한 상호작용이 필요한 기능을 보여 주기 위해 아껴서 사용해야 합니다."

이 정의에서 두 가지가 따라 나옵니다.

첫째, 동적 다이어그램은 이미 가지고 있는 관계의 인스턴스를 보여 줍니다. 컨테이너 다이어그램에 주문 서비스에서 Kafka로 가는 화살표가 있다면, 동적 다이어그램은 "그리고 체크아웃의 4단계에서 그 화살표가 OrderPlaced를 발행하는 데 쓰인다"고 말합니다. Structurizr의 DSL은 이를 명시합니다. 문서에 따르면 동적 뷰에서는 "정적 모델에 정의된 관계의 _인스턴스_를 보여 주는 것"이며, 관계가 먼저 정적 모델에 존재해야 합니다(Structurizr DSL 레퍼런스). 이 제약은 유용합니다. 정적 모델이 모르는 호출을 동적 다이어그램이 지어내지 못하게 막아 주기 때문입니다.

둘째, 다이어그램 하나에 시나리오 하나입니다. "주문 서비스가 동작하는 방식"이 아니라 "고객이 주문한다, 카드 결제, 재고 있음"입니다. 실패 경로는 그릴 가치가 있다면 별도의 다이어그램을 갖습니다.

순서는 화살표에 붙인 번호로 나타냅니다. 표기법은 그게 전부입니다. 같은 상자, 같은 화살표에 순서 번호와 그 단계에서 일어나는 일에 대한 설명을 더합니다.

동적 다이어그램과 시퀀스 다이어그램

"C4 시퀀스 다이어그램"은 흔한 검색어이고, 헷갈리는 것도 당연합니다. 두 다이어그램이 같은 질문에 답하기 때문입니다. C4 사이트에 따르면 동적 다이어그램은 같은 정보를 담은 두 가지 스타일로 그릴 수 있습니다:

  • 협업 스타일. 상자를 자유롭게 배치하고(보통 컨테이너 다이어그램에 있는 위치 그대로) 그 사이에 번호 붙은 화살표를 그립니다. C4는 이 스타일이 UML 커뮤니케이션 다이어그램(예전에는 협업 다이어그램이라고 불렸던 것)에 기반한다고 설명합니다.
  • 시퀀스 스타일. 요소를 상단에 열로 배치하고, 시간은 페이지 아래로 흐르며, 라이프라인 사이에 화살표를 그립니다. UML 시퀀스 다이어그램처럼 보이지만, 참여자는 C4 요소입니다.

즉, 시퀀스 스타일의 동적 다이어그램은 일종의 시퀀스 다이어그램 그 자체입니다. 진짜 차이는 코드에서 그리는 전형적인 UML 시퀀스 다이어그램과의 사이에 있습니다:

C4 동적 다이어그램 UML 시퀀스 다이어그램 (전형적인 사용)
참여자 C4 모델의 시스템, 컨테이너, 컴포넌트 객체, 클래스, 흔히 메서드 수준
화살표의 의미 정적 모델에 있는 관계의 사용, 프로토콜 포함 메시지 또는 메서드 호출
상세 수준 아키텍처 수준: "OrderPlaced 발행 (Kafka)" 흔히 구현 수준: validate(), save(), 반환값
표기법 상자와 번호 붙은 화살표, 특이한 것은 범례로 설명 라이프라인, 활성 막대, 결합 프래그먼트(alt, loop, par)
다른 다이어그램과의 연결 컨테이너 또는 컴포넌트 다이어그램의 요소를 재사용 대개 독립적

협업 스타일을 쓰세요. 공간적 배치가 의미를 가질 때, 예를 들어 독자가 이미 컨테이너 다이어그램을 알고 있어서 그 위에 흐름을 보여 주고 싶을 때입니다. 시퀀스 스타일을 쓰세요. 순서가 핵심일 때, 단계가 대략 여덟 개를 넘을 때, 또는 두 요소 사이에 오가는 것이 많을 때(요청, 응답, 콜백)입니다. 어느 쪽이 더 옳은 것은 아니며, C4는 선택을 여러분에게 맡깁니다.

시나리오를 설명하는 데 alt나 loop 프래그먼트가 필요하다면, 아키텍처가 아니라 알고리즘을 기술하고 있다는 신호인 경우가 많습니다. 아키텍처 버전은 동적 다이어그램으로 그리고, 상세 버전은 필요한 사람이 있다면 코드 옆의 UML 시퀀스 다이어그램으로 남겨 두세요. 각 표기법이 어디에 맞는지는 C4와 UML 비교에서 다룹니다.

그릴 가치가 있을 때 (그리고 없을 때)

"권장 여부"에 대한 C4 자신의 답은 "아니요"이며, 이는 진지하게 받아들일 만합니다. 모든 동적 다이어그램은 아키텍처가 바뀔 때 함께 바뀌어야 하는 산출물이 하나 더 늘어난다는 뜻입니다. 시나리오가 다음 중 적어도 하나에 해당할 때 그리세요:

  • 정적 다이어그램만으로는 순서가 분명하지 않다. 체크아웃, 결제 확정, 실패 시 보상 처리를 하는 사가. 팀의 시니어 엔지니어도 순서를 틀릴 것 같다면 그리세요.
  • 시나리오가 여러 컨테이너나 시스템을 가로지른다. 컨테이너 네 개 이상을 건드리거나, 시스템 밖으로 나갔다가 돌아오는 모든 것(Webhook, 콜백, 3-D Secure 같은 서드파티 리디렉션).
  • 비동기다. 큐가 끼어들면, 정적 다이어그램은 A와 B가 모두 Kafka를 건드린다는 것은 보여 주지만, B가 A 다음에 실행된다는 것이나 A가 B를 기다리지 않는다는 것은 보여 주지 않습니다.
  • 반복된다. 여러 곳에서 쓰이는 패턴(모든 서비스가 요청을 인증하는 방식, 모든 쓰기가 이벤트를 내보내는 방식)은 나머지 문서가 가리킬 수 있는 다이어그램 하나로 만들 가치가 있습니다.
  • 리뷰나 장애 상황에서 누군가 요청한다. 가장 좋은 계기입니다. 장애 리뷰에서 화이트보드에 시퀀스를 재구성하느라 20분을 썼다면, 그 시퀀스는 다이어그램이 될 자격이 있습니다.

다음과 같다면 건너뛰세요:

  • 흐름이 일직선이다. 브라우저, API, 데이터베이스, 그리고 돌아오기. 컨테이너 다이어그램이 이미 그렇게 말하고 있습니다.
  • CRUD다. 생성, 조회, 수정, 삭제, 목록에 대한 동적 다이어그램 다섯 개는 아무것도 더해 주지 않습니다.
  • 아무도 읽지 않는다. 모든 사용자 스토리마다 동적 다이어그램을 두는 것은 문서가 아니라 문서 백로그입니다.

전형적인 제품이라면 몇 개가 적당한 목표입니다. 돈을 벌어 주거나 사람을 한밤중에 깨우는 두세 개의 여정, 그리고 한두 개의 반복 패턴입니다.

실습 예제: "고객이 주문한다"

저희 완벽 가이드의 이커머스 시스템을 예로 들겠습니다. 이 시스템의 컨테이너 다이어그램에는 React 싱글 페이지 앱, Kong API 게이트웨이, 주문·상품·사용자를 위한 Go 서비스(각각 자체 PostgreSQL 데이터베이스 보유), Kafka, 알림 서비스가 있습니다. 레벨 1에서 시스템은 결제 게이트웨이인 Stripe, 그리고 이메일을 위한 SendGrid와도 통신합니다.

다음은 이 시나리오가 사용하는 정적 모델의 관계입니다. 아래의 모든 단계는 이 중 하나에 대응해야 합니다.

[Customer] --> [Single-Page Application (React)] : Uses (HTTPS)
[Single-Page Application] --> [API Gateway (Kong)] : Makes API calls (HTTPS/JSON)
[API Gateway] --> [Order Service (Go)] : Routes requests
[Order Service] --> [Product Service (Go)] : Checks stock (gRPC)
[Order Service] --> [Payment Gateway (Stripe)] : Authorizes payments (HTTPS/REST)
[Order Service] --> [Order Database (PostgreSQL)] : Reads/writes orders (SQL)
[Order Service] --> [Message Queue (Kafka)] : Publishes order events
[Notification Service (Go)] --> [Message Queue] : Consumes order events
[Notification Service] --> [Email Service (SendGrid)] : Sends email (HTTPS)

동적 다이어그램, 협업 스타일

같은 상자들 위에 그린 번호 붙은 상호작용입니다:

1.  [Customer] -> [Single-Page Application] : Clicks "Place order"
2.  [Single-Page Application] -> [API Gateway] : POST /orders (HTTPS/JSON)
3.  [API Gateway] -> [Order Service] : Routes the authenticated request
4.  [Order Service] -> [Product Service] : Reserves stock for each line item (gRPC)
5.  [Order Service] -> [Payment Gateway (Stripe)] : Authorizes the card for the order total (HTTPS)
6.  [Order Service] -> [Order Database] : Writes the order with status "placed" (SQL)
7.  [Order Service] -> [Message Queue] : Publishes OrderPlaced (Kafka)
8.  [Order Service] -> [Single-Page Application] : Returns 201 with the order number (via the gateway)
9.  [Notification Service] -> [Message Queue] : Consumes OrderPlaced (Kafka)
10. [Notification Service] -> [Email Service (SendGrid)] : Sends the confirmation email (HTTPS)

컨테이너 다이어그램 위에 배치하면 번호가 이야기를 들려줍니다. 1~8단계는 동기식이며 고객이 기다리는 동안 일어납니다. 9단계와 10단계는 그 후에 일어나며, 고객은 이를 기다리지 않습니다.

같은 시나리오, 시퀀스 스타일

# From To 일어나는 일 동기?
1 Customer Single-Page Application "Place order" 클릭 예
2 Single-Page Application API Gateway POST /orders 예
3 API Gateway Order Service 요청 라우팅 예
4 Order Service Product Service 재고 예약 예
5 Order Service Payment Gateway (Stripe) 카드 승인 예
6 Order Service Order Database 주문 기록 예
7 Order Service Message Queue OrderPlaced 발행 아니요 (fire and forget)
8 Order Service Single-Page Application 주문 번호와 함께 201 반환 예
9 Notification Service Message Queue OrderPlaced 소비 비동기
10 Notification Service Email Service (SendGrid) 확인 이메일 발송 비동기

이런 표는 동적 다이어그램을 적어 두는 방법으로 전혀 손색이 없습니다. 라이프라인으로 그리면 그것이 시퀀스 스타일입니다.

다이어그램이 알려 주는 것

열 개의 단계를 읽으면, 컨테이너 다이어그램으로는 답할 수 없었던 질문에 답할 수 있습니다:

  • Stripe가 다운되면 어떻게 되는가? 5단계에서 승인이 실패할 때 재고는 이미 4단계에서 예약되어 있습니다. 누군가는 그것을 해제해야 합니다. 다이어그램을 보면 주문 서비스에 보상 경로가 필요하다는 것, 또는 4단계와 5단계의 순서를 바꿔야 한다는 것이 분명해집니다.
  • 존재하지 않는 주문에 대한 확인 메일을 고객이 받을 수 있는가? 아니요. 이벤트는 6단계의 기록 이후인 7단계에서 발행됩니다. 이 둘의 순서가 반대라면, 기록이 실패해도 이메일이 발송될 수 있습니다. (기록과 발행이 원자적이어야 한다면 아웃박스 테이블이 필요한 지점이며, ADR로 남길 가치가 있습니다.)
  • 고객의 크리티컬 패스에는 무엇이 있는가? 2~8단계입니다. 이메일은 포함되지 않으며, 그래서 이메일은 Kafka를 거칩니다.

다음은 모델을 코드로 관리하는 팀을 위해 같은 시나리오를 Structurizr DSL로 작성한 것입니다. 각 관계가 정적 모델에 존재해야만 컴파일되며, 이것이 앞에서 설명한 제약입니다:

dynamic webshop "PlaceOrder" "Customer places an order" {
    customer -> spa "Clicks Place order"
    spa -> gateway "POST /orders"
    gateway -> orderService "Routes the request"
    orderService -> productService "Reserves stock"
    orderService -> stripe "Authorizes the card"
    orderService -> orderDb "Writes the order"
    orderService -> kafka "Publishes OrderPlaced"
    notificationService -> kafka "Consumes OrderPlaced"
    notificationService -> sendgrid "Sends confirmation"
    autoLayout lr
}

8단계인 응답은 정적 모델에서 별도의 관계가 아니므로 DSL 버전에서는 뺐습니다. 응답은 보통 요청에 암묵적으로 포함됩니다. 응답 자체가 중요할 때만 그리세요.

흔한 실수

단계가 너무 많다

번호 붙은 화살표가 서른 개인 동적 다이어그램은 아무도 머릿속에 담아 둘 수 없는 시퀀스입니다. 시나리오가 대략 열다섯 단계를 넘으면 나누세요. "체크아웃, 결제까지"와 "체크아웃, 결제 이후"로, 또는 흐름이 가로지르는 시스템마다 다이어그램 하나로. 저희 플로우 문서가 플로우당 5~15단계를 권장하는 것도 같은 이유입니다.

레벨을 섞는다

C4에서는 레벨(시스템, 컨테이너, 컴포넌트)을 고를 수 있지만, 다이어그램 하나에는 하나만 고르세요. 3단계는 "Order Service" 컨테이너로 가고 4단계는 그 안의 PaymentClient 컴포넌트로 가는 다이어그램은, 독자에게 이야기 도중에 확대 수준을 바꾸도록 강요합니다. 어떤 단계에 컴포넌트 수준의 상세가 필요하다면, 그 컨테이너로 범위를 좁힌 두 번째 동적 다이어그램을 그리세요.

정적 모델에 없는 화살표

동적 다이어그램에서는 알림 서비스가 주문 서비스를 직접 호출하는데 컨테이너 다이어그램에 그 관계가 없다면, 둘 중 하나는 틀린 것입니다. 보통은 기억에 의존해 그린 동적 다이어그램 쪽입니다. 정적 모델을 기준 정보로 삼고, 모든 단계가 그 관계 중 하나를 참조하게 하세요.

모든 호출을 그린다

헬스 체크, 토큰 갱신, 로그 전송, 메트릭 수집은 실제로 일어나지만 시나리오는 아닙니다. 여러분이 그리는 모든 동적 다이어그램에 나타날 만한 것은 빼세요. 중요하다면 반복 패턴 다이어그램으로 한 번만 그리면 됩니다.

동기처럼 보이는 화살표 뒤에 비동기를 숨긴다

위의 9단계와 10단계는 고객이 이미 응답을 받은 뒤에 일어납니다. 이를 1~8단계와 같은 화살표로 그리면, 독자는 페이지가 로드되기 전에 이메일이 발송된다고 생각합니다. 비동기 단계는 표시하고(점선, "async" 레이블, 또는 9a 같은 별도 번호), 그 규칙을 범례에 적으세요.

중요한 실패 경로를 빠뜨린다

해피 패스 다이어그램이 올바른 기본값입니다. 하지만 흐름을 그리는 이유가 "결제가 실패하면 무슨 일이 일어나는가"라면, 해피 패스가 아니라 그 경로를 그리세요.

정적 모델이 바뀌어도 정확하게 유지하기

동적 다이어그램은 정적 모델에 두 번 의존합니다. 요소에, 그리고 관계에. 그래서 가장 먼저 낡는 것 중 하나입니다. 누군가 주문 서비스의 이름을 "체크아웃 서비스"로 바꾸거나, Kafka를 SQS로 교체하거나, 재고 예약을 새 재고 서비스로 옮기면, 그 상자들을 건드렸던 모든 동적 다이어그램이 이제 틀린 것이 됩니다. 그리고 아무것도 그 사실을 알려 주지 않습니다.

도움이 되는 습관 세 가지:

  1. 모델 옆이 아니라 모델에서 그리세요. 드로잉 도구로 그린 동적 다이어그램은 컨테이너 다이어그램의 복사본이고, 복사본은 어긋납니다. 모델 요소를 식별자로 참조하는 동적 뷰(Structurizr DSL이 그렇게 합니다)라면 적어도 이름 변경은 반영되고, 관계가 사라지면 요란하게 실패합니다.
  2. 목록을 짧게 유지하세요. 분기마다 확인하는 동적 다이어그램 다섯 개가 한 번도 열지 않는 서른 개보다 낫습니다.
  3. 건드리는 컨테이너가 바뀌면 검토하세요. 풀 리퀘스트가 컨테이너나 관계를 바꾸면, 그것을 사용하는 동적 다이어그램도 리뷰 대상입니다.

archyl에서 플로우가 동작하는 방식

archyl에서 동적 다이어그램은 **플로우(Flow)**입니다. 순서가 있는 단계 목록으로, 각 단계에는 소스 요소, 대상 요소, 관계, 설명이 있으며, 다이어그램 위에서 단계별로 재생됩니다(플로우 문서). 모델에서 관계를 골라 직접 만들 수도 있고, 시나리오를 설명해 AI Flow Generator가 C4 모델에서 단계 초안을 작성하게 할 수도 있습니다. 생성기는 저장하기 전에 모든 단계를 모델과 대조해 검증합니다. 각 단계의 소스와 대상이 존재해야 하고, 인용한 관계가 그 두 요소를 연결해야 합니다. 일치하지 않는 단계는 그려지지 않고 버려집니다.

한계가 두 가지 있습니다. 이 섹션이 다루는 바로 그 문제이기 때문에 분명히 밝힙니다:

  • 플로우는 단계가 추가될 때 사용하는 요소와 관계의 스냅샷을 보관합니다. 그래서 나중에 요소가 삭제되어도 플로우는 읽을 수 있는 상태로 남지만, 모델에서 컨테이너 이름을 바꿔도 기존 플로우에서는 바뀌지 않는다는 뜻이기도 합니다. 모델이 바뀌면 그것을 건드리는 플로우를 열어 확인하세요.
  • 드리프트 점수는 동작을 검사하지 않습니다. archyl의 드리프트 점수는 문서화된 요소가 여전히 코드에 존재하는지 알려 줍니다. 두 서비스 사이의 동기 호출이 큐 메시지로 바뀌었는데 이름이 바뀌거나 옮겨진 것이 없다면, 점수도 바뀌지 않고 플로우도 바뀌지 않습니다.

사전 조건과 오류 처리를 포함해 플로우를 문서로 작성하는 방법 등 실천적인 측면은 사용자 플로우 문서화하기를 참고하세요.

자주 묻는 질문

동적 다이어그램은 C4 모델의 일부인가요?

네, 보조 다이어그램으로서 그렇습니다. 네 가지 핵심 레벨은 System Context, Container, Component, Code입니다. C4 모델은 여기에 보조 다이어그램 세 가지를 더합니다: 시스템 랜드스케이프, 동적, 배포 다이어그램입니다. 동적 다이어그램은 핵심 레벨의 요소를 재사용해 하나의 시나리오에서 그것들이 어떻게 상호작용하는지 보여 줍니다.

C4 동적 다이어그램과 시퀀스 다이어그램의 차이는 무엇인가요?

C4 동적 다이어그램은 협업 스타일(자유로운 배치, 번호 붙은 화살표)이나 시퀀스 스타일(라이프라인, 아래로 흐르는 시간)로 그릴 수 있습니다. 시퀀스 스타일은 UML 시퀀스 다이어그램처럼 보이지만, 참여자는 C4의 시스템, 컨테이너, 컴포넌트이고, 각 화살표는 메서드 호출이 아니라 정적 모델에 있는 관계의 사용입니다.

동적 다이어그램은 어느 레벨을 사용해야 하나요?

질문에 답해 주는 레벨을, 다이어그램 하나에 하나만 사용합니다. 가장 흔한 것은 컨테이너 레벨인데, 그릴 가치가 있는 시나리오 대부분이 여러 배포 단위를 가로지르기 때문입니다. 시스템 간 흐름에는 시스템 레벨을, 하나의 컨테이너 내부를 설명할 때는 컴포넌트 레벨을 사용하세요.

동적 다이어그램에는 몇 단계가 있어야 하나요?

공식적인 제한은 없습니다. 대략 열다섯 단계를 넘으면 대부분의 독자가 흐름을 놓치므로, 시나리오를 여러 부분으로 나누거나 가로지르는 시스템마다 다이어그램 하나를 그리세요.

C4 동적 다이어그램으로 비동기 메시징을 표현할 수 있나요?

네. 발행과 소비를 별도의 번호 붙은 단계로 보여 주고, 호출자가 기다리는 단계와 기다리지 않는 단계가 드러나도록 하세요. 점선, "async" 레이블, 또는 별도의 번호 체계를 쓰고 다이어그램 범례에서 설명합니다.

archyl은 C4 동적 다이어그램을 지원하나요?

네, 플로우로 지원합니다. 각 단계는 모델의 소스 요소, 대상 요소, 관계를 참조하며, 플로우는 다이어그램 위에서 단계별로 재생됩니다. 플로우는 직접 작성할 수도 있고 텍스트 설명에서 초안을 생성할 수도 있습니다. 플로우는 사용하는 요소의 스냅샷을 보관하므로, 건드리는 컨테이너가 바뀌면 검토하세요.


이미 있는 모델 위에 첫 플로우를 그려 보고 싶으신가요? archyl을 무료로 사용해 보고 먼저 코드에서 C4 모델을 생성하세요. 더 읽어 보기: C4 모델이란? 완벽 가이드 | C4 Container 다이어그램 가이드 | 사용자 플로우 문서화하기 | 플로우 문서.