C4モデルとは、ソフトウェアの構造を「コンテキスト → コンテナ → コンポーネント → コード」の4段階の拡大率で描き分ける図の考え方です。 地図アプリで国全体から市街地へ拡大していくように、読み手に合わせて1枚ごとの細かさを決めます。記法は自由なので、Mermaid でも PlantUML でも描けます。

この記事では、架空の「備品予約システム」を1つだけ題材にして、コンテキスト図・コンテナ図・コンポーネント図・デプロイ図・動的図の5枚を、Mermaid の C4 記法と C4-PlantUML の両方のコードで並べます。 2つの記法の構文対応表、Mermaid 側の制約と回避の書き方、図ごとに「書くこと・書かないこと」の確認表も付けました。

確認環境:Ubuntu 24.04、Mermaid 12.1.0(@mermaid-js/mermaid-cli 12.0.0 と Chromium 141 で描画)、PlantUML 1.2026.8(OpenJDK 21、同梱の C4 ライブラリ、内蔵レイアウトエンジン Smetana で描画)。掲載したコードは、すべて画像として出力できることを確認しています。

C4モデルの早見表(5つの図と読み手)

図 拡大率 描く範囲 箱になるもの 主な読み手 Mermaid の開始行 C4-PlantUML の読み込み
コンテキスト図 レベル1 システムとその周り 利用者、対象システム、外部システム 関係者全員 C4Context C4_Context
コンテナ図 レベル2 対象システムの内側 画面、API、DB、キューなど動く単位 開発者、運用担当 C4Container C4_Container
コンポーネント図 レベル3 1つのコンテナの内側 コントローラー、サービスなどの部品 そのコンテナの開発者 C4Component C4_Component
コード図 レベル4 1つのコンポーネントの内側 クラス、インターフェース 開発者 クラス図で代用 クラス図で代用
デプロイ図 補助 環境ごとの配置 サーバー、実行環境とその上のコンテナ 運用担当、基盤担当 C4Deployment C4_Deployment
動的図 補助 1つの処理の流れ 既存の箱+番号つきの矢印 開発者、レビュー担当 C4Dynamic C4_Dynamic

c4model.com では、レベル1〜4を中心となる図、システムランドスケープ図・動的図・デプロイ図を補助の図として紹介しています。同サイトには「コンテキスト図とコンテナ図があれば、多くの開発チームには十分」という趣旨の説明があり、まずはこの2枚から始めるのが近道です。 コンポーネント図とコード図は、価値があるときだけ描きます。

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「C4モデルの書き方|コンテキスト図・コンテナ図のMermaid例」

C4モデルの4階層と「コンテナ」の意味

C4 の「コンテナ」は、Docker のコンテナのことではありません。単独で起動して動く単位を指し、Web アプリ、API サーバー、データベース、メッセージキュー、バッチなどが当てはまります。

用語 意味 備品予約システムでの例
人(Person) システムを使う役割 社員、総務担当
ソフトウェアシステム(System) 利用者に価値を届けるまとまり 備品予約システム、社内認証基盤
コンテナ(Container) 単独で起動して動く単位 Web画面、APIサーバー、データベース、通知キュー、通知ワーカー
コンポーネント(Component) コンテナの中の部品。単独では起動しない 予約コントローラー、予約サービス、予約リポジトリ
関係(Relationship) 箱と箱を結ぶ矢印。動詞のラベルを付ける 「APIを呼び出す(HTTPS/JSON)」
境界(Boundary) 箱をまとめる点線の枠 備品予約システムの枠、APIサーバーの枠

階層を下りるときは、上の図の箱を1つ選び、その中身を次の図で開くという関係を守ります。コンテキスト図の「備品予約システム」を開いたものがコンテナ図、コンテナ図の「APIサーバー」を開いたものがコンポーネント図です。箱の名前と説明を上下の図でそろえると、読み手が迷いません。

題材:社内の備品予約システム

以降の図は、次の架空のシステムを描きます。

項目 内容
目的 会議用機材や貸出PCの空きを確認し、予約を受け付ける
利用者 社員(予約する)、総務担当(備品の登録と承認)
外部システム 社内認証基盤(ログイン)、メール配信サービス(通知)
コンテナ Web画面(React)、APIサーバー(Spring Boot)、データベース(PostgreSQL)、通知キュー(RabbitMQ)、通知ワーカー(Python)

レベル1:コンテキスト図の書き方

コンテキスト図は、対象システムを1つの箱として中央に置き、だれが使い、どの外部システムとつながるかだけを描きます。技術の名前は箱に書かず、矢印のラベルに通信方式を添える程度にとどめます。

Mermaid(C4Context)

C4Context
    title 備品予約システムのコンテキスト図

    Person(employee, "社員", "備品を探して予約する")
    Person(admin, "総務担当", "備品の登録と予約の承認を行う")

    System(booking, "備品予約システム", "会議用機材や貸出PCの空きを確認し、予約を受け付ける")

    System_Ext(sso, "社内認証基盤", "社員のログインを一元管理する")
    System_Ext(mail, "メール配信サービス", "通知メールを送信する")

    Rel(employee, booking, "空きを調べて予約する", "HTTPS")
    Rel(admin, booking, "備品を登録し予約を承認する", "HTTPS")
    Rel(booking, sso, "ログインを委ねる", "OpenID Connect")
    Rel(booking, mail, "通知メールの送信を依頼する", "HTTPS/JSON")
    Rel(mail, employee, "予約確定メールを届ける", "SMTP")

    UpdateLayoutConfig($c4ShapeInRow="2", $c4BoundaryInRow="1")

Person(別名, "名前", "説明") のように、1つ目の引数が矢印で参照する別名、2つ目が箱に表示する名前です。_Ext の付いた要素は灰色で描かれ、対象の外にあることが一目でわかります。最後の UpdateLayoutConfig は1行に並べる箱の数で、ここでは2個にしています。

C4-PlantUML(C4_Context)

@startuml
!pragma layout smetana
!include <C4/C4_Context>

title 備品予約システムのコンテキスト図

Person(employee, "社員", "備品を探して予約する")
Person(admin, "総務担当", "備品の登録と予約の承認を行う")

System(booking, "備品予約システム", "会議用機材や貸出PCの空きを確認し、予約を受け付ける")

System_Ext(sso, "社内認証基盤", "社員のログインを一元管理する")
System_Ext(mail, "メール配信サービス", "通知メールを送信する")

Rel(employee, booking, "空きを調べて予約する", "HTTPS")
Rel(admin, booking, "備品を登録し予約を承認する", "HTTPS")
Rel(booking, sso, "ログインを委ねる", "OpenID Connect")
Rel(booking, mail, "通知メールの送信を依頼する", "HTTPS/JSON")
Rel(mail, employee, "予約確定メールを届ける", "SMTP")

SHOW_LEGEND()
@enduml

要素と関係の行は Mermaid と1文字も変わりません。違いは、最初の !include、最後の SHOW_LEGEND()(凡例)、そして title の行だけです。!pragma layout smetana は PlantUML 内蔵のレイアウトエンジンを使う指定で、Graphviz を入れていない環境でも描画できます。Graphviz を使う環境では、この1行を外しても同じコードで描けます。

レベル2:コンテナ図の書き方

コンテナ図は、コンテキスト図の中央の箱を開き、起動する単位(コンテナ)と、それぞれの技術、コンテナ間の通信を描きます。新しく参加した開発者が最初に読む1枚になります。

Mermaid(C4Container)

C4Container
    title 備品予約システムのコンテナ図

    Person(employee, "社員", "備品を探して予約する")
    Person(admin, "総務担当", "備品の登録と予約の承認を行う")

    System_Boundary(booking, "備品予約システム") {
        Container(spa, "Web画面", "TypeScript, React", "予約の検索・登録画面をブラウザで表示する")
        Container(api, "APIサーバー", "Java, Spring Boot", "予約の受付と空き状況の判定を行う")
        ContainerDb(db, "データベース", "PostgreSQL 16", "備品・予約・利用者を保存する")
        ContainerQueue(queue, "通知キュー", "RabbitMQ", "通知の依頼を一時的にためる")
        Container(worker, "通知ワーカー", "Python", "キューから依頼を取り出して通知を送る")
    }

    Boundary(ext, "社外・他部署のシステム", "外部") {
        System_Ext(sso, "社内認証基盤", "社員のログインを一元管理する")
        System_Ext(mail, "メール配信サービス", "通知メールを送信する")
    }

    Rel(employee, spa, "予約を操作する", "HTTPS")
    Rel(admin, spa, "備品を管理する", "HTTPS")
    Rel(spa, api, "APIを呼び出す", "HTTPS/JSON")
    Rel(api, db, "読み書きする", "JDBC")
    Rel(api, queue, "通知の依頼を積む", "AMQP")
    Rel(worker, queue, "依頼を取り出す", "AMQP")
    Rel(api, sso, "トークンを検証する", "OpenID Connect")
    Rel(worker, mail, "送信を依頼する", "HTTPS/JSON")

    UpdateRelStyle(spa, api, $offsetY="-30")
    UpdateRelStyle(api, db, $offsetX="-70", $offsetY="10")
    UpdateRelStyle(api, sso, $textColor="blue", $lineColor="blue", $offsetX="10")
    UpdateRelStyle(worker, mail, $textColor="blue", $lineColor="blue", $offsetX="-110")
    UpdateLayoutConfig($c4ShapeInRow="3", $c4BoundaryInRow="1")

このコードには、Mermaid 向けの工夫を3つ入れています。

工夫 書き方 理由
外部システムを下に置く Boundary(ext, …) で囲み、対象システムの枠の後ろに書く 枠に入れない要素は、書いた位置に関係なく枠より上にまとめて描かれるため
ラベルの位置をずらす UpdateRelStyle(spa, api, $offsetY="-30") 矢印のラベルが箱に重なるときに、縦横にずらせる
外部への矢印に色を付ける UpdateRelStyle(api, sso, $textColor="blue", $lineColor="blue") 枠をまたぐ長い矢印を見分けやすくする

C4-PlantUML(C4_Container)

@startuml
!pragma layout smetana
!include <C4/C4_Container>

title 備品予約システムのコンテナ図

Person(employee, "社員", "備品を探して予約する")
Person(admin, "総務担当", "備品の登録と予約の承認を行う")

System_Boundary(booking, "備品予約システム") {
    Container(spa, "Web画面", "TypeScript, React", "予約の検索・登録画面をブラウザで表示する")
    Container(api, "APIサーバー", "Java, Spring Boot", "予約の受付と空き状況の判定を行う")
    ContainerDb(db, "データベース", "PostgreSQL 16", "備品・予約・利用者を保存する")
    ContainerQueue(queue, "通知キュー", "RabbitMQ", "通知の依頼を一時的にためる")
    Container(worker, "通知ワーカー", "Python", "キューから依頼を取り出して通知を送る")
}

System_Ext(sso, "社内認証基盤", "社員のログインを一元管理する")
System_Ext(mail, "メール配信サービス", "通知メールを送信する")

Rel(employee, spa, "予約を操作する", "HTTPS")
Rel(admin, spa, "備品を管理する", "HTTPS")
Rel(spa, api, "APIを呼び出す", "HTTPS/JSON")
Rel(api, db, "読み書きする", "JDBC")
Rel(api, queue, "通知の依頼を積む", "AMQP")
Rel(worker, queue, "依頼を取り出す", "AMQP")
Rel(api, sso, "トークンを検証する", "OpenID Connect")
Rel(worker, mail, "送信を依頼する", "HTTPS/JSON")

SHOW_LEGEND()
@enduml

PlantUML 側は配置を自動で決めるため、外部システムを枠で囲む工夫や、ラベルをずらす指定は不要です。描画すると、外部システムは枠の右側に並び、凡例が右下に付きました。

レベル3:コンポーネント図の書き方

コンポーネント図は、コンテナを1つ選んで中身を開きます。枠の外には、そのコンテナと直接やり取りする相手だけを置きます。ここでは APIサーバーを開きます。

Mermaid(C4Component)

C4Component
    title APIサーバーのコンポーネント図

    Container(spa, "Web画面", "TypeScript, React", "予約の検索・登録画面を表示する")
    ContainerDb(db, "データベース", "PostgreSQL 16", "備品・予約・利用者を保存する")
    ContainerQueue(queue, "通知キュー", "RabbitMQ", "通知の依頼をためる")
    System_Ext(sso, "社内認証基盤", "社員のログインを一元管理する")

    Container_Boundary(api, "APIサーバー") {
        Component(auth, "認証フィルター", "Spring Security", "トークンを検証して利用者を特定する")
        Component(ctrl, "予約コントローラー", "Spring MVC", "予約APIの入口。入力を検査する")
        Component(svc, "予約サービス", "Spring Bean", "予約の登録・変更・取消の手順をまとめる")
        Component(avail, "空き状況チェッカー", "Spring Bean", "時間帯の重なりを判定する")
        Component(repo, "予約リポジトリ", "Spring Data JPA", "予約と備品の読み書きを受け持つ")
        Component(pub, "通知パブリッシャー", "Spring AMQP", "通知の依頼をキューへ送る")
    }

    Rel(spa, auth, "APIを呼び出す", "HTTPS/JSON")
    Rel(auth, sso, "トークンを検証する", "OpenID Connect")
    Rel(auth, ctrl, "認証済みの要求を渡す")
    Rel(ctrl, svc, "予約の処理を依頼する")
    Rel(svc, avail, "空きを問い合わせる")
    Rel(svc, repo, "予約を保存する")
    Rel(avail, repo, "既存の予約を取得する")
    Rel(svc, pub, "通知を依頼する")
    Rel(repo, db, "読み書きする", "JDBC")
    Rel(pub, queue, "依頼を積む", "AMQP")

    UpdateLayoutConfig($c4ShapeInRow="3", $c4BoundaryInRow="1")

C4-PlantUML(C4_Component)

@startuml
!pragma layout smetana
!include <C4/C4_Component>

title APIサーバーのコンポーネント図

Container(spa, "Web画面", "TypeScript, React", "予約の検索・登録画面を表示する")
ContainerDb(db, "データベース", "PostgreSQL 16", "備品・予約・利用者を保存する")
ContainerQueue(queue, "通知キュー", "RabbitMQ", "通知の依頼をためる")
System_Ext(sso, "社内認証基盤", "社員のログインを一元管理する")

Container_Boundary(api, "APIサーバー") {
    Component(auth, "認証フィルター", "Spring Security", "トークンを検証して利用者を特定する")
    Component(ctrl, "予約コントローラー", "Spring MVC", "予約APIの入口。入力を検査する")
    Component(svc, "予約サービス", "Spring Bean", "予約の登録・変更・取消の手順をまとめる")
    Component(avail, "空き状況チェッカー", "Spring Bean", "時間帯の重なりを判定する")
    Component(repo, "予約リポジトリ", "Spring Data JPA", "予約と備品の読み書きを受け持つ")
    Component(pub, "通知パブリッシャー", "Spring AMQP", "通知の依頼をキューへ送る")
}

Rel(spa, auth, "APIを呼び出す", "HTTPS/JSON")
Rel(auth, sso, "トークンを検証する", "OpenID Connect")
Rel(auth, ctrl, "認証済みの要求を渡す")
Rel(ctrl, svc, "予約の処理を依頼する")
Rel(svc, avail, "空きを問い合わせる")
Rel(svc, repo, "予約を保存する")
Rel(avail, repo, "既存の予約を取得する")
Rel(svc, pub, "通知を依頼する")
Rel(repo, db, "読み書きする", "JDBC")
Rel(pub, queue, "依頼を積む", "AMQP")

SHOW_LEGEND()
@enduml

コンポーネント図はコードの変更に合わせて直す回数が多くなります。c4model.com でも任意の図とされているので、処理の入口が多いコンテナや、複数人で分担するコンテナに絞って描くと、手入れの負担と効果のつり合いが取れます。レベル4(コード図)は、Mermaid なら classDiagram、PlantUML ならクラス図でそのまま描けるため、C4 専用の構文はありません。

デプロイ図の書き方

デプロイ図は、コンテナ図の箱が、どの環境のどのサーバーで動くかを描きます。Deployment_Node を入れ子にして、機器 → 実行環境 → コンテナの順に包みます。環境(本番、検証など)ごとに1枚ずつ作ります。

Mermaid(C4Deployment)

C4Deployment
    title 備品予約システムのデプロイ図(本番環境)

    Deployment_Node(pc, "社員のPC", "Windows 11") {
        Deployment_Node(browser, "Webブラウザ", "Chrome / Edge") {
            Container(spa, "Web画面", "TypeScript, React", "予約の検索・登録画面を表示する")
        }
    }

    Deployment_Node(dc, "社内データセンター", "仮想化基盤") {
        Deployment_Node(web, "Webサーバー x2", "Ubuntu 24.04, nginx") {
            Container(static, "静的ファイル配信", "nginx", "Web画面のファイルを配信しAPIへ中継する")
        }
        Deployment_Node(app, "アプリサーバー x2", "Ubuntu 24.04, Java 21") {
            Container(api, "APIサーバー", "Java, Spring Boot", "予約の受付と空き状況の判定を行う")
            Container(worker, "通知ワーカー", "Python", "通知を送る")
        }
        Deployment_Node(mq, "キューサーバー", "Ubuntu 24.04") {
            ContainerQueue(queue, "通知キュー", "RabbitMQ", "通知の依頼をためる")
        }
        Deployment_Node(dbnode, "DBサーバー(主・待機)", "Ubuntu 24.04") {
            ContainerDb(db, "データベース", "PostgreSQL 16", "備品・予約・利用者を保存する")
        }
    }

    Rel(spa, static, "画面とAPIを要求する", "HTTPS")
    Rel(static, api, "APIを中継する", "HTTP")
    Rel(api, db, "読み書きする", "JDBC")
    Rel(api, queue, "依頼を積む", "AMQP")
    Rel(worker, queue, "依頼を取り出す", "AMQP")

C4-PlantUML(C4_Deployment)

@startuml
!pragma layout smetana
!include <C4/C4_Deployment>

title 備品予約システムのデプロイ図(本番環境)

Deployment_Node(pc, "社員のPC", "Windows 11") {
    Deployment_Node(browser, "Webブラウザ", "Chrome / Edge") {
        Container(spa, "Web画面", "TypeScript, React", "予約の検索・登録画面を表示する")
    }
}

Deployment_Node(dc, "社内データセンター", "仮想化基盤") {
    Deployment_Node(web, "Webサーバー x2", "Ubuntu 24.04, nginx") {
        Container(static, "静的ファイル配信", "nginx", "Web画面のファイルを配信しAPIへ中継する")
    }
    Deployment_Node(app, "アプリサーバー x2", "Ubuntu 24.04, Java 21") {
        Container(api, "APIサーバー", "Java, Spring Boot", "予約の受付と空き状況の判定を行う")
        Container(worker, "通知ワーカー", "Python", "通知を送る")
    }
    Deployment_Node(mq, "キューサーバー", "Ubuntu 24.04") {
        ContainerQueue(queue, "通知キュー", "RabbitMQ", "通知の依頼をためる")
    }
    Deployment_Node(dbnode, "DBサーバー(主・待機)", "Ubuntu 24.04") {
        ContainerDb(db, "データベース", "PostgreSQL 16", "備品・予約・利用者を保存する")
    }
}

Rel(spa, static, "画面とAPIを要求する", "HTTPS")
Rel(static, api, "APIを中継する", "HTTP")
Rel(api, db, "読み書きする", "JDBC")
Rel(api, queue, "依頼を積む", "AMQP")
Rel(worker, queue, "依頼を取り出す", "AMQP")

SHOW_LEGEND()
@enduml

台数は「Webサーバー x2」のようにノードの名前に書いています。Deployment_Node の3つ目の引数は種類(OS や製品名)を書く場所です。

動的図の書き方

動的図は、1つの処理について、箱の間をどの順番でやり取りが流れるかを番号つきの矢印で描きます。箱はコンテナ図(またはコンポーネント図)のものをそのまま使います。題材は「予約を登録する」処理です。

Mermaid(C4Dynamic)

C4Dynamic
    title 予約を登録するときの流れ(動的図)

    Container(spa, "Web画面", "TypeScript, React", "予約の検索・登録画面を表示する")
    Container(api, "APIサーバー", "Java, Spring Boot", "予約の受付と空き状況の判定を行う")
    ContainerDb(db, "データベース", "PostgreSQL 16", "備品・予約・利用者を保存する")
    ContainerQueue(queue, "通知キュー", "RabbitMQ", "通知の依頼をためる")
    Container(worker, "通知ワーカー", "Python", "通知を送る")
    System_Ext(mail, "メール配信サービス", "通知メールを送信する")

    RelIndex(1, spa, api, "予約登録を要求する", "POST /reservations")
    RelIndex(2, api, db, "空きを確かめて予約を保存する", "SELECT, INSERT")
    RelIndex(3, api, queue, "通知の依頼を積む", "AMQP")
    RelIndex(4, worker, queue, "依頼を取り出す", "AMQP")
    RelIndex(5, worker, mail, "確定メールの送信を依頼する", "HTTPS/JSON")

    UpdateRelStyle(spa, api, $offsetX="-50", $offsetY="-40")
    UpdateRelStyle(api, db, $offsetX="-90")
    UpdateRelStyle(worker, mail, $offsetX="-50", $offsetY="-40")
    UpdateLayoutConfig($c4ShapeInRow="3", $c4BoundaryInRow="1")

Mermaid 12.1.0 で描画すると、番号は RelIndex を書いた順に 1 から振られます。1つ目の引数に 10 や 20 を書いても、表示は 1、2 となりました。番号を入れ替えたいときは、行の順番を入れ替えます。

また、同じ2つの箱の間に同じ向きの矢印を2本書くと、後に書いた1本だけが描かれます。APIサーバーからデータベースへの「検索」と「保存」を別々の行にすると「検索」が図から消えるため、上のコードでは「空きを確かめて予約を保存する」と1本にまとめています。

C4-PlantUML(C4_Dynamic)

@startuml
!pragma layout smetana
!include <C4/C4_Dynamic>

title 予約を登録するときの流れ(動的図)

Container(spa, "Web画面", "TypeScript, React", "予約の検索・登録画面を表示する")
Container(api, "APIサーバー", "Java, Spring Boot", "予約の受付と空き状況の判定を行う")
ContainerDb(db, "データベース", "PostgreSQL 16", "備品・予約・利用者を保存する")
ContainerQueue(queue, "通知キュー", "RabbitMQ", "通知の依頼をためる")
Container(worker, "通知ワーカー", "Python", "通知を送る")
System_Ext(mail, "メール配信サービス", "通知メールを送信する")

Rel(spa, api, "予約登録を要求する", "POST /reservations")
Rel(api, db, "同じ時間帯の予約を検索する", "SELECT")
Rel(api, db, "予約を保存する", "INSERT")
Rel(api, queue, "通知の依頼を積む", "AMQP")
Rel(worker, queue, "依頼を取り出す", "AMQP")
Rel(worker, mail, "確定メールの送信を依頼する", "HTTPS/JSON")

SHOW_LEGEND()
@enduml

C4-PlantUML の動的図では、Rel を書いた順に番号が自動で付きます。同じ箱の間の矢印も別々に描かれるので、「2: 検索」「3: 保存」と分けたまま 6 本の矢印になります。やり取りの本数が多い処理は、C4-PlantUML のほうが細かく描けます。

MermaidとC4-PlantUMLの構文対応表

要素(箱)

役割 Mermaid C4-PlantUML 引数の順
人 Person / Person_Ext Person / Person_Ext 別名, 名前, 説明
システム System / System_Ext System / System_Ext 別名, 名前, 説明
システム(DB・キューの形) SystemDb / SystemQueue(_Ext あり) 同じ 別名, 名前, 説明
コンテナ Container / Container_Ext 同じ 別名, 名前, 技術, 説明
コンテナ(DB・キューの形) ContainerDb / ContainerQueue(_Ext あり) 同じ 別名, 名前, 技術, 説明
コンポーネント Component / ComponentDb / ComponentQueue(_Ext あり) 同じ 別名, 名前, 技術, 説明
配置先のノード Deployment_Node / Node / Node_L / Node_R 同じ 別名, 名前, 種類

境界(枠)

役割 Mermaid C4-PlantUML 補足
汎用の枠 Boundary(別名, "名前", "種類") { … } 同じ 種類は枠の見出しに表示される
組織の枠 Enterprise_Boundary(別名, "名前") { … } 同じ システムランドスケープ図で使う
システムの枠 System_Boundary(別名, "名前") { … } 同じ コンテナ図で使う
コンテナの枠 Container_Boundary(別名, "名前") { … } 同じ コンポーネント図で使う

関係(矢印)と配置

役割 Mermaid 12.1.0 C4-PlantUML
矢印 Rel(元, 先, "ラベル", "技術") 同じ
双方向 BiRel(元, 先, "ラベル") 同じ
逆向きに描く Rel_Back(元, 先, "ラベル") 同じ
向きを指定する矢印 Rel_U Rel_D Rel_L Rel_R は書けるが、配置は Rel と同じ Rel_U Rel_D Rel_L Rel_R が配置のヒントになる(結果はレイアウトエンジンによって変わる)
番号つきの矢印 RelIndex(番号, 元, 先, "ラベル")。表示は書いた順 Rel に自動で番号。$index で指定も可能
矢印なしの配置指定 Lay_D などは書けない(構文エラー) Lay_U Lay_D Lay_L Lay_R
全体の向き 指定なし(上から下へ行ごとに並ぶ) LAYOUT_TOP_DOWN() LAYOUT_LEFT_RIGHT() LAYOUT_LANDSCAPE()
1行に並べる数 UpdateLayoutConfig($c4ShapeInRow="3", $c4BoundaryInRow="1") なし(自動配置)
凡例 なし SHOW_LEGEND() LAYOUT_WITH_LEGEND() SHOW_FLOATING_LEGEND()
色の変更 UpdateElementStyle(別名, $bgColor="…", $fontColor="…", $borderColor="…") AddElementTag でタグを定義し $tags で付ける
矢印の色とラベル位置 UpdateRelStyle(元, 先, $textColor="…", $lineColor="…", $offsetX="…", $offsetY="…") AddRelTag でタグを定義し $tags で付ける
タイトル title 文字列 title 文字列

要素・境界・基本の矢印は共通で、違いが出るのは配置・凡例・見た目の指定です。 Mermaid の公式ドキュメントも、C4 図の構文は C4-PlantUML と互換になるように作られていると説明しています。

Mermaid側の制約と回避の書き方

Mermaid の C4 図は、公式ドキュメントで試験的な図とされており、構文や属性が今後のリリースで変わる可能性があると書かれています。Mermaid 12.1.0 で確かめた挙動と、回避の書き方をまとめます。

確かめたこと Mermaid 12.1.0 での結果 回避の書き方
Rel_U Rel_D Rel_L Rel_R 描画結果は Rel と同じ。配置は変わらない 要素を書く順番と UpdateLayoutConfig の $c4ShapeInRow で並びを決める
Lay_D(a, b) 構文エラー(Lexical error) 行を削る。並びは要素の順番で調整
LAYOUT_LEFT_RIGHT() SHOW_LEGEND() 構文エラー(Lexical error) 行を削る。凡例は図の下に表で書く
AddElementTag(…) 構文エラー(Lexical error) UpdateElementStyle で要素ごとに色を指定する
要素の $tags $link $sprite エラーにならず描画されるが、見た目は変わらない そのままでよい(PlantUML と同じコードを共有できる)
同じ2要素間に同じ向きの Rel を2本 後に書いた1本だけが描かれる ラベルを1本にまとめる。細かい順序は sequenceDiagram に分ける
RelIndex の番号 指定した値ではなく、書いた順に 1 から表示 表示したい順に行を並べる
枠に入れない要素の位置 枠より上にまとめて描かれる 下に置きたい要素は Boundary で囲み、後ろに書く
ラベルと箱の重なり 矢印が増えると起きる UpdateRelStyle の $offsetX $offsetY でずらす
定義していない別名への Rel 描画時にエラー 別名のつづりをそろえる

PlantUML 用の行が残っていると、次のようなエラーになります(Lay_D を足した例)。

Error: Lexical error on line 5. Unrecognized text.
... Rel(a, b, "uses") Lay_D(a, b)
----------------------^

別名のつづりが違うときのエラーは次のとおりです。

Error: C4 rel "a" -> "zzz" references an unknown shape or boundary

色を変える:UpdateElementStyle と AddElementTag

「今回追加する部分」に色を付ける例です。Mermaid では要素ごとに指定します。

C4Container
    title 通知まわりの追加(色分け)

    Container(api, "APIサーバー", "Java, Spring Boot", "予約を受け付ける")
    ContainerQueue(queue, "通知キュー", "RabbitMQ", "通知の依頼をためる")
    Container(worker, "通知ワーカー", "Python", "通知を送る")

    Rel(api, queue, "依頼を積む", "AMQP")
    Rel(worker, queue, "依頼を取り出す", "AMQP")

    UpdateElementStyle(queue, $bgColor="#2e7d32", $fontColor="white", $borderColor="#1b5e20")
    UpdateElementStyle(worker, $bgColor="#2e7d32", $fontColor="white", $borderColor="#1b5e20")

C4-PlantUML では、タグを1回定義して要素と矢印に付けます。凡例にもタグの説明が自動で載ります。

@startuml
!pragma layout smetana
!include <C4/C4_Container>
LAYOUT_LEFT_RIGHT()
AddElementTag("new", $bgColor="#2e7d32", $fontColor="white", $legendText="今回追加する部分")
AddRelTag("async", $lineStyle=DashedLine(), $legendText="非同期")

Container(api, "APIサーバー", "Java, Spring Boot", "予約を受け付ける")
ContainerQueue(queue, "通知キュー", "RabbitMQ", "通知の依頼をためる", $tags="new")
Container(worker, "通知ワーカー", "Python", "通知を送る", $tags="new", $link="https://example.com/worker")

Rel(api, queue, "依頼を積む", "AMQP", $tags="async")
Rel(worker, queue, "依頼を取り出す", "AMQP", $tags="async")
Lay_D(api, worker)
SHOW_LEGEND()
@enduml

どちらの記法を選ぶかの判断表

重視すること 向いている記法 理由
Markdown の文書にそのまま埋め込みたい Mermaid コードブロックに書くだけで図になる環境が多い
凡例を自動で付けたい C4-PlantUML SHOW_LEGEND() の1行で付く
全体の向きや箱の並びを指定したい C4-PlantUML LAYOUT_LEFT_RIGHT() や Lay_D などの指定がある
動的図で同じ箱の間の往復を描きたい C4-PlantUML 同じ2要素間の矢印を何本でも描ける
タグで色分けし、凡例にも出したい C4-PlantUML AddElementTag と $tags
追加の道具を入れずに始めたい Mermaid Java の実行環境が不要
両方で同じ図を保ちたい 共通部分だけで書く 要素・境界・Rel は同じ構文。配置と凡例の行だけ分ける

要素と Rel の行は共通なので、まず Mermaid で描き始め、配置や凡例が必要になった図だけ C4-PlantUML に移す進め方もできます。移すときは、開始行を !include に、UpdateLayoutConfig などの Update… の行を PlantUML 用の指定に置き換えます。

図ごとの「書くこと・書かないこと」確認表

図 書くこと 書かないこと 箱の数の目安(例)
コンテキスト図 利用者の役割、対象システム(1箱)、直接つながる外部システム、やり取りの目的 対象システムの内部、技術の名前、サーバーの台数 5〜10
コンテナ図 起動する単位、技術、コンテナ間の通信方式、データの保存先 クラスや関数、サーバーの台数、冗長構成 5〜12
コンポーネント図 1つのコンテナの部品、部品の責務、枠の外の直接の相手 他のコンテナの内部、メソッド単位の細かさ 5〜12
デプロイ図 環境名、機器や実行環境の入れ子、その上で動くコンテナ、台数 コンポーネント、処理の順番 ノード 5〜10
動的図 1つの処理に関わる箱、番号つきのやり取り 関係しない箱、分岐をすべて網羅すること 矢印 5〜10

すべての図に共通する確認項目は次のとおりです。

確認項目 見るところ
タイトルに図の種類と対象が入っているか 「備品予約システムのコンテナ図」のように書く
箱に名前・種類・説明の3点があるか 説明は「何をするか」を1文で
矢印が一方向で、動詞のラベルがあるか 「使う」より「予約を保存する」のように具体的に
コンテナ間の矢印に通信方式があるか HTTPS/JSON、JDBC、AMQP など
上の階層の図と名前がそろっているか 「APIサーバー」と「API」が混ざっていないか
略語に説明があるか 初出の略語は説明欄か凡例に書く
凡例があるか 色と形の意味。Mermaid では図の下に表で補う

表計算ソフトに貼って使えるタブ区切り版です。

図   確認項目    結果  メモ
共通  タイトルに図の種類と対象が入っている      
共通  箱に名前・種類・説明がある       
共通  矢印が一方向で動詞のラベルがある        
共通  上の階層の図と名前がそろっている        
コンテキスト図 対象システムが1箱で内部を描いていない     
コンテキスト図 利用者の役割と外部システムがそろっている        
コンテナ図   各コンテナに技術が書いてある      
コンテナ図   コンテナ間の通信方式が書いてある        
コンポーネント図    開いたコンテナは1つだけ        
デプロイ図   環境名と台数が書いてある        
動的図 対象の処理が1つに絞られている     

C4モデルの図を描く手順

  1. 対象システムを1文で説明する(だれの、何のためのシステムか)。これがコンテキスト図の中央の箱の説明になります。
  2. 利用者の役割と外部システムを洗い出し、コンテキスト図を描く。
  3. 起動する単位を数えて、コンテナ図を描く。迷ったら「別々に起動・停止できるか」で分けます。
  4. 説明が必要なコンテナだけ、コンポーネント図を描く。
  5. 環境ごとの配置をデプロイ図にする。
  6. レビューで質問が出やすい処理を1つ選び、動的図にする。
  7. 図のコードを設計書と同じリポジトリに置き、構成が変わる変更と同じタイミングで直す。

よくある質問

Q. C4モデルは4枚すべて描く必要がありますか? A. 必要な図だけで構いません。c4model.com では、コンテキスト図とコンテナ図があれば多くのチームには十分とされ、コンポーネント図とコード図は任意の扱いです。

Q. C4モデルとUMLはどう使い分けますか? A. C4 は拡大率ごとの描き分けの考え方で、記法は決めていません。レベル4はクラス図で描けますし、処理の順番を細かく追うときはシーケンス図を併用できます。C4 の図で全体をつかみ、細部は UML で補う組み合わせが使いやすいです。

Q. Mermaid で矢印のラベルが重なるときはどうしますか? A. UpdateRelStyle の $offsetX と $offsetY でラベルをずらします。あわせて UpdateLayoutConfig の $c4ShapeInRow を小さくすると箱の間隔に余裕ができます。矢印が10本を超えるときは、図を分けるほうが読みやすくなります。

Q. コンテナ図とデプロイ図は何が違いますか? A. コンテナ図は「何が動くか」と「どう話すか」、デプロイ図は「どこで動くか」を描きます。台数や冗長構成、サーバーの種類はデプロイ図に書き、コンテナ図には書きません。

まとめ

  • C4モデルは、コンテキスト・コンテナ・コンポーネント・コードの4段階で拡大率を変えて描く考え方。まずコンテキスト図とコンテナ図の2枚から始める。
  • コンテナは「単独で起動して動く単位」。画面、API、DB、キュー、バッチが当てはまる。
  • 要素・境界・Rel の構文は Mermaid と C4-PlantUML で共通。違いは配置・凡例・色の指定に集まっている。
  • Mermaid 12.1.0 では Rel_U などで配置は変わらず、Lay_D や SHOW_LEGEND() は構文エラー。並びは要素の順番と UpdateLayoutConfig、ラベルは UpdateRelStyle で整える。
  • Mermaid の動的図は、同じ2要素間の矢印が1本になり、番号は書いた順。往復を細かく描くなら C4-PlantUML が向いている。
  • 図ごとに「書かないこと」を決めると、1枚の役割がはっきりする。台数はデプロイ図、処理の順番は動的図へ。

参考・出典

  • C4 model「Diagrams」 https://c4model.com/diagrams
  • Mermaid 公式ドキュメント「C4 Diagrams」 https://mermaid.js.org/syntax/c4.html
  • C4-PlantUML(plantuml-stdlib) https://github.com/plantuml-stdlib/C4-PlantUML

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「C4モデルの書き方|コンテキスト図・コンテナ図のMermaid例」

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?