C4 Dynamic図(動的図):実例で学ぶガイド
コンテナ図は、APIが注文サービスと通信し、注文サービスがKafkaと通信し、通知サービスがKafkaから読み取ることを教えてくれます。けれども、顧客がPlace orderをクリックしたときに何がどの順番で起きるのかは教えてくれません。決済は注文レコードが書き込まれる前でしょうか、後でしょうか。確認メールは倉庫の処理を待つのでしょうか。インシデントレビューで聞かれるのはこうした質問ですが、静的なダイアグラムでは答えられません。
それを担うのがC4のダイナミック図です。すでに描いた要素を使い、1つの具体的なシナリオにおける要素間のやり取りに番号を振ります。このガイドでは、ダイナミック図とは何か、UMLのシーケンス図とどう違うか、描く価値があるのはいつか(思っているより少ないです)、完全な実例、よくある間違い、そして静的モデルが変わったときに古くならないようにする方法を扱います。
C4が初めてなら、まずC4モデルとは何かから始めてください。後半の実例は、Container図ガイドで扱っている種類のダイアグラムを土台にしています。
ダイナミック図とは
ダイナミック図は、C4モデルの補助的なダイアグラムの1つで、システムランドスケープ図やデプロイメント図と並ぶものです。4つのコアレベルの1つではありません。それらの隣に位置し、その要素を借りて使います。
c4model.comの定義は短いものです。
- スコープ:「特定の機能、ストーリー、ユースケースなど」
- 要素:「自由に選べます。ソフトウェアシステム、コンテナ、コンポーネントが実行時にやり取りする様子を示せます」
- 対象読者:「ソフトウェア開発チームの内外にいる、技術者と非技術者」
- 推奨されるか:「いいえ。ダイナミック図は控えめに使い、興味深いパターンや繰り返し現れるパターン、あるいは複雑なやり取りを必要とする機能を示すためにだけ使うべきです」
この定義から2つのことが言えます。
1つ目に、ダイナミック図が示すのはすでに存在する関係のインスタンスです。コンテナ図に注文サービスからKafkaへの矢印があれば、ダイナミック図は「チェックアウトのステップ4で、その矢印がOrderPlacedの発行に使われる」と語ります。StructurizrのDSLはこれを明示しています。ドキュメントによれば、ダイナミックビューでは「静的モデルで定義された関係の_インスタンス_を示している」のであり、関係はまず静的モデルに存在しなければなりません(Structurizr DSLリファレンス)。この制約は役に立ちます。静的モデルが知らない呼び出しを、ダイナミック図がでっち上げるのを防いでくれるからです。
2つ目に、1つのダイアグラムに1つのシナリオです。「注文サービスの仕組み」ではなく、「顧客が注文する、カード決済、在庫あり」です。失敗経路は、そもそも描く価値があるなら、別のダイアグラムにします。
順序は矢印に付けた番号で示します。記法はそれだけです。同じボックス、同じ矢印に、シーケンス番号と、そのステップで何が起きるかの説明を加えます。
ダイナミック図とシーケンス図の違い
「C4 シーケンス図」はよく検索される言葉で、混同するのも無理はありません。どちらのダイアグラムも同じ問いに答えるからです。C4のサイトによれば、ダイナミック図は同じ情報を持つ2つのスタイルで描けます。
- コラボレーションスタイル。 ボックスを自由に配置し(たいていはコンテナ図上の位置と同じ)、そのあいだに番号付きの矢印を引きます。C4は、このスタイルがUMLのコミュニケーション図(以前はコラボレーション図と呼ばれていたもの)に基づいていると述べています。
- シーケンススタイル。 要素を上部に列として並べ、時間はページの下に向かって流れ、ライフライン間に矢印を引きます。見た目はUMLのシーケンス図ですが、参加者はC4の要素です。
つまり、シーケンススタイルのダイナミック図は、ある種のシーケンス図そのものです。本当の違いは、コードから描く典型的なUMLのシーケンス図とのあいだにあります。
| C4ダイナミック図 | UMLシーケンス図(典型的な使い方) | |
|---|---|---|
| 参加者 | C4モデルのシステム、コンテナ、コンポーネント | オブジェクト、クラス。メソッドレベルのことが多い |
| 矢印の意味 | 静的モデルにある関係の使用。プロトコル付き | メッセージまたはメソッド呼び出し |
| 詳細度 | アーキテクチャレベル:「OrderPlacedを発行(Kafka)」 |
実装レベルのことが多い:validate()、save()、戻り値 |
| 記法 | ボックスと番号付き矢印。特殊なものは凡例で説明 | ライフライン、活性区間、複合フラグメント(alt、loop、par) |
| 他のダイアグラムとのつながり | コンテナ図やコンポーネント図の要素を再利用 | たいていは単独 |
コラボレーションスタイルを使うのは、空間的な配置に意味があるときです。たとえば、読み手がすでにコンテナ図を知っていて、その上にフローを重ねて見せたい場合です。シーケンススタイルを使うのは、順序こそが要点のとき、ステップがおよそ8つを超えるとき、あるいは2つの要素のあいだで行き来が多いとき(リクエスト、レスポンス、コールバック)です。どちらがより正しいということはなく、C4は選択をあなたに委ねています。
シナリオを説明するのにaltやloopのフラグメントが必要なら、それはアーキテクチャではなくアルゴリズムを記述しているサインであることが多いです。アーキテクチャ版をダイナミック図として描き、詳細版は、必要とする人がいればコードの隣にUMLのシーケンス図として残しましょう。それぞれの記法がどこに合うかは、C4とUMLの比較で扱っています。
描く価値があるとき(とないとき)
「推奨されるか」に対するC4自身の答えは「いいえ」で、これは真剣に受け止める価値があります。ダイナミック図はどれも、アーキテクチャが変わるたびに更新しなければならない成果物が1つ増えることを意味します。シナリオが次のうち少なくとも1つに当てはまるときに描きましょう。
- 静的なダイアグラムから順序が明らかでない。 チェックアウト、決済の確定、失敗時に補償処理を行うサガ。チームのシニアエンジニアでも順序を間違えそうなら、描きましょう。
- シナリオが複数のコンテナやシステムをまたぐ。 4つ以上のコンテナに触れるもの、あるいはシステムの外に出て戻ってくるもの(Webhook、コールバック、3-Dセキュアのようなサードパーティへのリダイレクト)。
- 非同期である。 キューが絡むと、静的なダイアグラムはAとBの両方がKafkaに触れることは示しても、BがAの後に実行されることや、AがBを待たないことは示しません。
- 繰り返し現れる。 多くの場所で使われるパターン(各サービスがリクエストを認証する方法、各書き込みがイベントを発行する方法)は、ドキュメントの他の部分から参照できる1枚のダイアグラムにする価値があります。
- レビューやインシデントで誰かに求められた。 これが最良のきっかけです。インシデントレビューでホワイトボードにシーケンスを再現するのに20分かかったなら、そのシーケンスはダイアグラムにする価値があります。
次の場合は描かなくて構いません。
- フローが一直線。 ブラウザ、API、データベース、そして戻り。コンテナ図がすでにそれを語っています。
- CRUDである。 作成、読み取り、更新、削除、一覧の5つのダイナミック図は何も付け加えません。
- 誰も読まない。 ユーザーストーリーごとのダイナミック図は、ドキュメントではなくドキュメントのバックログです。
典型的なプロダクトなら、数枚が妥当な目標です。売上を生むか、誰かを夜中に起こす2〜3のジャーニーと、1〜2の繰り返しパターンです。
実例:「顧客が注文する」
完全ガイドのECシステムを例にとります。そのコンテナ図には、ReactのSPA、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を発行 |
いいえ(ファイア・アンド・フォーゲット) |
| 8 | Order Service | Single-Page Application | 注文番号付きで201を返す | はい |
| 9 | Notification Service | Message Queue | OrderPlacedを消費 |
非同期 |
| 10 | Notification Service | Email Service (SendGrid) | 確認メールを送信 | 非同期 |
このような表は、ダイナミック図を書き留める方法としてまったく問題ありません。ライフラインとして描けば、それがシーケンススタイルです。
ダイアグラムからわかること
10のステップを読むと、コンテナ図では答えられなかった質問に答えられます。
- Stripeが落ちていたらどうなるか? ステップ5でオーソリが失敗した時点で、ステップ4ですでに在庫が確保されています。誰かがそれを解放しなければなりません。ダイアグラムを見れば、注文サービスに補償処理の経路が必要なこと、あるいはステップ4と5を入れ替えるべきことが一目瞭然です。
- 存在しない注文の確認メールを顧客が受け取ることはあるか? ありません。イベントはステップ6の書き込みの後、ステップ7で発行されます。この2つが逆だったら、書き込みが失敗してもメールが送られてしまうかもしれません。(書き込みと発行をアトミックにする必要があるなら、そこでアウトボックステーブルの出番です。これは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版からは省いています。レスポンスはたいていリクエストに暗黙的に含まれています。描くのは、レスポンスそのものが重要なときだけにしましょう。
よくある間違い
ステップが多すぎる
30個の番号付き矢印があるダイナミック図は、誰も頭に入れておけないシーケンスです。シナリオがおよそ15ステップを超えるなら、分割しましょう。「チェックアウト、決済まで」と「チェックアウト、決済後」、あるいはフローがまたぐシステムごとに1枚のダイアグラムです。私たちのフローのドキュメントが1フローあたり5〜15ステップを推奨しているのも同じ理由です。
レベルを混在させる
C4ではレベル(システム、コンテナ、コンポーネント)を選べますが、1つのダイアグラムでは1つに絞りましょう。ステップ3では「Order Service」コンテナに向かい、ステップ4ではその内部のPaymentClientコンポーネントに向かうダイアグラムは、読み手にストーリーの途中でズームの切り替えを強います。あるステップにコンポーネントの詳細が必要なら、そのコンテナにスコープを絞った2枚目のダイナミック図を描きましょう。
静的モデルに存在しない矢印
ダイナミック図では通知サービスが注文サービスを直接呼び出しているのに、コンテナ図にその関係がないなら、どちらかが間違っています。たいていは、記憶を頼りに描いたダイナミック図のほうです。静的モデルを信頼できる情報源として扱い、すべてのステップがその関係のどれかを参照するようにしましょう。
すべての呼び出しを描く
ヘルスチェック、トークンのリフレッシュ、ログの転送、メトリクスの収集は実在しますが、シナリオではありません。描くダイナミック図のすべてに現れるようなものは省きましょう。重要なら、繰り返しパターンのダイアグラムとして一度だけ描きます。
同期に見える矢印で非同期を隠す
上のステップ9と10は、顧客がすでにレスポンスを受け取った後に起きます。これをステップ1〜8と同じ矢印で描くと、読み手はページが表示される前にメールが送られると思い込みます。非同期のステップには印を付け(破線、「async」ラベル、9aのような別の番号付け)、その約束事を凡例に書きましょう。
重要な失敗経路を省く
ハッピーパスのダイアグラムが正しいデフォルトです。けれども、フローを描く理由が「決済が失敗したら何が起きるか」なら、ハッピーパスではなくその経路を描きましょう。
静的モデルが変わっても正しく保つ
ダイナミック図は、静的モデルに二重に依存しています。要素と関係の両方にです。そのため、最初に古くなるものの1つです。誰かが注文サービスを「チェックアウトサービス」に改名する、KafkaをSQSに置き換える、在庫確保を新しい在庫サービスに移す。そうすると、そのボックスに触れていたダイナミック図はすべて間違ったものになります。それを知らせてくれるものは何もありません。
役に立つ習慣が3つあります。
- モデルの隣ではなく、モデルから描く。 作図ツールで描いたダイナミック図はコンテナ図のコピーであり、コピーはずれていきます。モデルの要素を識別子で参照するダイナミックビュー(Structurizr DSLはそうしています)なら、少なくとも改名には追従し、関係が消えたときにははっきりと失敗します。
- リストを短く保つ。 四半期ごとに確認する5枚のダイナミック図は、一度も開かない30枚に勝ります。
- 触れているコンテナが変わったらレビューする。 プルリクエストがコンテナや関係を変更するとき、それを使うダイナミック図もレビューの対象です。
archylでのフローの仕組み
archylでは、ダイナミック図は**フロー(Flow)**です。順序付きのステップのリストで、各ステップにはソース要素、ターゲット要素、関係、説明があり、ダイアグラムの上でステップごとに再生されます(フローのドキュメント)。モデルから関係を選んで手作業で作ることも、シナリオを説明してAI Flow GeneratorにC4モデルからステップの下書きを作らせることもできます。ジェネレーターは保存する前に、すべてのステップをモデルに照らして検証します。各ステップのソースとターゲットが存在し、参照する関係がその2つの要素を結んでいなければなりません。一致しないステップは描かれるのではなく、破棄されます。
制約が2つあります。まさにこのセクションで扱っている問題なので、はっきり書いておきます。
- フローは、ステップが追加された時点で、使用する要素と関係のスナップショットを保持します。 そのため、要素が後で削除されてもフローは読める状態を保ちますが、モデルでコンテナを改名しても既存のフロー内の名前は変わりません。モデルが変わったら、それに触れているフローを開いて確認してください。
- ドリフトスコアは振る舞いをチェックしません。 archylのドリフトスコアは、文書化された要素がまだコードに存在するかどうかを教えてくれます。2つのサービス間の同期呼び出しがキューのメッセージに変わっても、何も改名・移動されていなければ、スコアは変わらず、フローも変わりません。
前提条件やエラー処理を含め、フローをドキュメントとしてどう書くかといった実践面については、ユーザーフローの文書化を参照してください。
よくある質問
ダイナミック図はC4モデルの一部ですか?
はい、補助的なダイアグラムとしてです。4つのコアレベルはSystem Context、Container、Component、Codeです。C4モデルはこれに3つの補助的なダイアグラムを加えています。システムランドスケープ図、ダイナミック図、デプロイメント図です。ダイナミック図はコアレベルの要素を再利用し、1つのシナリオでそれらがどうやり取りするかを示します。
C4のダイナミック図とシーケンス図の違いは何ですか?
C4のダイナミック図は、コラボレーションスタイル(自由な配置、番号付きの矢印)でもシーケンススタイル(ライフライン、時間は下に向かって流れる)でも描けます。シーケンススタイルはUMLのシーケンス図に似ていますが、参加者はC4のシステム、コンテナ、コンポーネントであり、各矢印はメソッド呼び出しではなく、静的モデルにある関係の使用です。
ダイナミック図はどのレベルを使うべきですか?
問いに答えられるレベルを、1つのダイアグラムにつき1つだけ使います。最もよく使われるのはコンテナレベルです。描く価値のあるシナリオの多くは、複数のデプロイ単位をまたぐからです。システム間のフローにはシステムレベルを、1つのコンテナの内部を説明するにはコンポーネントレベルを使いましょう。
ダイナミック図にはいくつのステップを含めるべきですか?
公式な上限はありません。およそ15ステップを超えると、ほとんどの読み手はついていけなくなります。シナリオをいくつかの部分に分けるか、またぐシステムごとに1枚のダイアグラムを描きましょう。
C4のダイナミック図で非同期メッセージングを表現できますか?
はい。発行と消費を別々の番号付きステップとして示し、呼び出し側が待つステップと待たないステップがわかるようにします。破線、「async」ラベル、あるいは別の番号体系を使い、ダイアグラムの凡例で説明しましょう。
archylはC4のダイナミック図に対応していますか?
はい、フローとして対応しています。各ステップはモデル内のソース要素、ターゲット要素、関係を参照し、フローはダイアグラムの上でステップごとに再生されます。フローは手作業で作成することも、テキストの説明から下書きを生成することもできます。フローは使用する要素のスナップショットを保持するので、触れているコンテナが変わったらレビューしてください。
すでに存在するモデルの上に、最初のフローを描いてみませんか? archylを無料で試し、まずはコードからC4モデルを生成しましょう。続けて読む:C4モデルとは?完全ガイド | C4 Container図ガイド | ユーザーフローの文書化 | フローのドキュメント