Model
Model は、C4 model に従ってソフトウェアアーキテクチャを記述するための Go DSL を提供します。アーキテクチャをコードで定義し、ソフトウェアと一緒にバージョン管理し、図を自動生成できます。
なぜ「コードとしての図」なのか?
従来のアーキテクチャ図は陳腐化しがちです。GUI ツールで作られ、コードベースと切り離され、すぐに実態と乖離してしまいます。Model は、アーキテクチャをリポジトリの第一級の成果物にすることで、これを解決します:
- バージョン管理: アーキテクチャの変更は git コミットとして履歴が残る
- 一貫性: 図全体でスタイルとコンポーネントを共有できる
- 自動化: CI/CD で図を生成でき、手作業のスクリーンショットが不要
- 合成可能: Go パッケージとして部品を共有し、プロジェクト間で再利用できる
- レビュー可能: 他のコードと同様にコードレビューの対象にできる
C4 モデル
C4 は 4 つの抽象度レベルを定義します。Model は最初の 3 つをサポートします:
| レベル | 表すもの | 例 |
|---|---|---|
| コンテキスト | 環境の中のシステム | ソフトウェアシステム、人 |
| コンテナ | システム内のアプリケーション | Web アプリ、API、データベース、メッセージキュー |
| コンポーネント | コンテナ内のモジュール | サービス、コントローラ、リポジトリ |
第 4 レベル(Code)は実際のクラスや関数に対応します。そこは IDE がすでに示してくれます。
インストール
go install goa.design/model/cmd/mdl@latest
go install goa.design/model/cmd/stz@latest
Go 1.23+ が必要です。
クイックスタート
1. Model パッケージを作成する
// model/model.go
package model
import . "goa.design/model/dsl"
var _ = Design("My System", "A description of the overall design", func() {
// Define elements
var System = SoftwareSystem("My System", "Does useful things", func() {
Tag("primary")
})
Person("User", "Someone who uses the system", func() {
Uses(System, "Uses")
Tag("external")
})
// Define views
Views(func() {
SystemContextView(System, "Context", "Shows the system in context", func() {
AddAll()
AutoLayout(RankLeftRight)
})
Styles(func() {
ElementStyle("primary", func() {
Background("#1168bd")
Color("#ffffff")
})
ElementStyle("external", func() {
Shape(ShapePerson)
Background("#08427b")
Color("#ffffff")
})
})
})
})
2. エディタを起動する
mdl serve ./model -dir gen
http://localhost:8080 を開きます。要素をドラッグして位置を調整し、保存します。SVG は gen/ ディレクトリに書き出されます。
3. CI で図を生成する
mdl svg ./model -dir gen --all
ヘッドレスブラウザを起動して自動レイアウトを行い、すべてのビューを SVG ファイルとして保存します。
コアコンセプト
要素の階層
Model は C4 の階層に従います。要素は親の中にネストします:
Design
├── Person (top-level)
├── SoftwareSystem (top-level)
│ └── Container
│ └── Component
└── DeploymentEnvironment
└── DeploymentNode
├── InfrastructureNode
├── ContainerInstance
└── DeploymentNode (nested)
要素の参照
多くの DSL 関数は要素の参照を受け取ります。参照の方法は 2 つあります:
変数で参照(参照を持っている場合):
var API = Container("API", "Backend service", "Go")
var DB = Container("Database", "Stores data", "PostgreSQL")
// Reference by variable
API.Uses(DB, "Reads from", "SQL")
パスで参照(要素が別の場所で定義されている場合):
// Same container, reference sibling by name
Uses("Database", "Reads from", "SQL")
// Different software system, use full path
Uses("Other System/API", "Calls")
// Component in another container
Uses("My System/Other Container/Service", "Invokes")
パスの区切りは / です。相対パスはスコープ内で機能します(同一システム内のコンテナ、同一コンテナ内のコンポーネントなど)。
要素のマージ
同じ要素を 2 回定義すると(同じ階層で同名)、Model はそれらをマージします:
var User = Person("User", "First definition", func() {
Uses("System A", "Uses")
})
var User2 = Person("User", "Updated description", func() {
Uses("System B", "Also uses")
})
// Result: One "User" person with "Updated description" and both relationships
これにより、共有モデルをインポートしつつ、ローカルな関係を追加して拡張できます。
ソフトウェアシステム
デプロイ可能なソフトウェアシステムを表すトップレベル要素:
var ECommerce = SoftwareSystem("E-Commerce", "Online shopping platform", func() {
// Mark as external to the enterprise
External()
// Documentation link
URL("https://docs.example.com/ecommerce")
// Custom metadata
Prop("owner", "Platform Team")
Prop("tier", "1")
// Tags for styling
Tag("critical", "public-facing")
})
コンテナ
ソフトウェアシステム内のアプリケーション、サービス、またはデータストア:
var _ = SoftwareSystem("E-Commerce", "Online store", func() {
var WebApp = Container("Web Application", "Customer UI", "React", func() {
Tag("frontend")
})
var API = Container("API", "Backend REST API", "Go/Goa", func() {
Tag("backend")
URL("https://api.example.com")
})
var Database = Container("Database", "Order and product data", "PostgreSQL", func() {
Tag("database")
})
var Queue = Container("Message Queue", "Async processing", "RabbitMQ", func() {
Tag("infrastructure")
})
// Relationships between containers
WebApp.Uses(API, "Makes requests to", "HTTPS/JSON")
API.Uses(Database, "Reads/writes", "SQL")
API.Uses(Queue, "Publishes events to", "AMQP")
})
Goa では、サービス定義をそのまま使えます:
// Goa service definition
var OrdersService = Service("orders", func() { /* ... */ })
// Use it as a container
var _ = SoftwareSystem("E-Commerce", func() {
Container(OrdersService, func() {
// Name, description, and "Go and Goa v3" technology come from the service
Tag("api")
})
})
コンポーネント
コンテナ内のモジュール。技術的な詳細ドキュメントに使います:
var _ = Container("API", "Backend API", "Go", func() {
var OrderService = Component("Order Service", "Handles order lifecycle", "Go package")
var PaymentService = Component("Payment Service", "Processes payments", "Go package")
var NotificationService = Component("Notification Service", "Sends emails/SMS", "Go package")
OrderService.Uses(PaymentService, "Calls for payment")
OrderService.Uses(NotificationService, "Triggers confirmations")
PaymentService.Uses("Stripe/API", "Processes cards via", "HTTPS")
})
人(Person)
システムとやり取りするユーザー、アクター、ロール:
var Customer = Person("Customer", "A customer placing orders", func() {
External() // Outside the enterprise
Tag("user")
Uses("E-Commerce", "Places orders using", "HTTPS")
})
var Admin = Person("Administrator", "Manages the platform", func() {
// Internal by default
Tag("internal")
Uses("E-Commerce/Admin Portal", "Manages products via")
})
var Support = Person("Support Agent", "Handles customer issues", func() {
InteractsWith(Customer, "Helps") // Person-to-person relationship
Uses("E-Commerce/Support Tools", "Uses")
})
関係(Relationship)
種類
| 関数 | From | To | 用途 |
|---|---|---|---|
Uses | 任意の要素 | 任意の要素 | 一般的な依存関係 |
Delivers | System/Container/Component | Person | ユーザーへの出力 |
InteractsWith | Person | Person | 人同士の相互作用 |
構文
すべての関係関数は、同じオプション引数を受け取ります:
// Minimal
Uses(Target, "description")
// With technology
Uses(Target, "description", "technology")
// With interaction style
Uses(Target, "description", Synchronous) // or Asynchronous
// With technology and style
Uses(Target, "description", "technology", Synchronous)
// With properties
Uses(Target, "description", "technology", func() {
Tag("async")
})
例
// System uses system
PaymentSystem.Uses(BankAPI, "Processes payments via", "REST/JSON", Synchronous)
// Container uses external system
API.Uses("Stripe/API", "Charges cards", "HTTPS", Asynchronous)
// System delivers to person
NotificationSystem.Delivers(Customer, "Sends order updates to", "Email")
ビュー(View)
ビューは、モデルの一部を異なる詳細度でレンダリングします。
System Landscape View
すべてのシステムと人を表示(全体像):
SystemLandscapeView("Landscape", "Enterprise overview", func() {
Title("Company Systems")
AddAll()
AutoLayout(RankTopBottom)
EnterpriseBoundaryVisible() // Shows internal vs external
})
System Context View
特定のシステムと、その直接の依存関係を表示:
SystemContextView(ECommerce, "Context", "E-Commerce in context", func() {
AddAll() // Add system + all related elements
Remove(InternalTooling) // Exclude specific elements
AutoLayout(RankLeftRight)
EnterpriseBoundaryVisible()
})
Container View
システム内のコンテナを表示:
ContainerView(ECommerce, "Containers", "E-Commerce containers", func() {
AddAll()
SystemBoundariesVisible() // Shows external system boundaries
AutoLayout(RankTopBottom)
})
Component View
コンテナ内のコンポーネントを表示:
ComponentView(API, "Components", "API internals", func() {
AddAll()
ContainerBoundariesVisible() // Shows external container boundaries
AutoLayout(RankLeftRight)
})
Dynamic View
特定のフローやシナリオを、順序付きの相互作用として表示:
DynamicView(ECommerce, "OrderFlow", "Order placement flow", func() {
Title("Customer places an order")
Link(Customer, WebApp, func() {
Description("1. Submits order")
Order("1")
})
Link(WebApp, API, func() {
Description("2. Creates order")
Order("2")
})
Link(API, PaymentService, func() {
Description("3. Processes payment")
Order("3")
})
Link(API, Database, func() {
Description("4. Saves order")
Order("4")
})
AutoLayout(RankLeftRight)
})
スコープは Global(任意の要素)、ソフトウェアシステム(そのコンテナ)、またはコンテナ(そのコンポーネント)を指定できます。
Deployment View
コンテナがインフラ上にどのようにデプロイされるかを表示:
DeploymentView(Global, "Production", "ProdDeployment", "Production setup", func() {
AddAll()
AutoLayout(RankLeftRight)
})
Filtered View
タグに基づいて別のビューをフィルタしたバージョンを作成:
// Base view
SystemContextView(System, "AllContext", "Everything", func() {
AddAll()
})
// Filtered to show only external elements
FilteredView("AllContext", func() {
FilterTag("external")
})
// Filtered to exclude infrastructure
FilteredView("AllContext", func() {
FilterTag("infrastructure")
Exclude()
})
ビュー操作
要素を追加する
| 関数 | 挙動 |
|---|---|
AddAll() | スコープ内のすべてを追加する |
AddDefault() | 文脈上、妥当な要素を追加する |
Add(element) | 特定の要素を追加する |
AddNeighbors(element) | 要素とその直接の接続先を追加する |
AddContainers() | すべてのコンテナを追加する(ContainerView/ComponentView) |
AddComponents() | すべてのコンポーネントを追加する(ComponentView) |
AddInfluencers() | コンテナ + 外部依存を追加する(ContainerView) |
AddAll と AddDefault の違い: AddAll はスコープ内で可能な限りすべてを追加します。AddDefault は典型的に関連性のあるものを追加します(SystemContextView なら、システムと直接関係するシステム・人であり、無関係な要素は含めません)。
要素を削除する
SystemContextView(System, "key", "desc", func() {
AddAll()
Remove(InternalTool) // Remove specific element
RemoveTagged("internal") // Remove by tag
RemoveUnrelated() // Remove elements with no relationships
RemoveUnreachable(MainSystem) // Remove elements not reachable from MainSystem
})
関係を管理する
// Explicitly add a relationship
Link(Source, Destination, func() {
Vertices(100, 200, 100, 400) // Waypoints for line routing
Routing(RoutingOrthogonal) // RoutingDirect, RoutingCurved, RoutingOrthogonal
Position(50) // Label position (0-100 along line)
})
// Remove a relationship
Unlink(Source, Destination)
Unlink(Source, Destination, "specific description") // When multiple relationships exist
// Merge multiple relationships into one (Context/Landscape views only)
CoalesceRelationships(Customer, System, "Interacts with") // Explicit merge
CoalesceAllRelationships() // Auto-merge all duplicates
要素の位置指定
要素を追加する際、正確な座標を指定できます:
Add(Customer, func() {
Coord(100, 200) // X, Y position
NoRelationship() // Don't render relationships for this element
})
AutoLayout
AutoLayout はグラフレイアウトアルゴリズムを使って要素を自動配置します:
AutoLayout(RankTopBottom, func() {
Implementation(ImplementationDagre) // or ImplementationGraphviz
RankSeparation(300) // Pixels between ranks (default: 300)
NodeSeparation(600) // Pixels between nodes in same rank (default: 600)
EdgeSeparation(200) // Pixels between edges (default: 200)
RenderVertices() // Create waypoints on edges
})
方向: RankTopBottom, RankBottomTop, RankLeftRight, RankRightLeft
スタイル
スタイルはタグを介して適用されます。要素と関係は複数のタグを持て、スタイルはカスケードします。
要素スタイル
Styles(func() {
ElementStyle("database", func() {
Shape(ShapeCylinder)
Background("#438DD5")
Color("#ffffff")
Stroke("#2E6295")
FontSize(24)
Border(BorderSolid) // BorderSolid, BorderDashed, BorderDotted
Opacity(100) // 0-100
Width(450) // Pixels
Height(300) // Pixels
Icon("https://example.com/db-icon.png")
ShowMetadata() // Show technology
ShowDescription() // Show description text
})
})
利用可能な形状: ShapeBox, ShapeRoundedBox, ShapeCircle, ShapeEllipse, ShapeHexagon, ShapeCylinder, ShapePipe, ShapePerson, ShapeRobot, ShapeFolder, ShapeWebBrowser, ShapeMobileDevicePortrait, ShapeMobileDeviceLandscape, ShapeComponent
関係スタイル
RelationshipStyle("async", func() {
Dashed() // or Solid()
Thickness(2) // Line thickness in pixels
Color("#707070")
FontSize(18)
Width(200) // Label width
Routing(RoutingOrthogonal) // RoutingDirect, RoutingCurved, RoutingOrthogonal
Position(50) // Label position (0-100)
Opacity(100)
})
CSS テーマ対応
出力される SVG には、フォールバック値付きの CSS カスタムプロパティが含まれます。これにより、図を再生成せずにライト/ダークテーマを適用できます。ElementStyle と RelationshipStyle で定義した色は、タグ名に基づく CSS 変数へ変換されます。
変数名の規約:
- 要素背景:
--mdl-<tag>-bg - 要素テキスト色:
--mdl-<tag>-color - 要素ストローク:
--mdl-<tag>-stroke - 関係の色:
--mdl-rel-<tag>-color
たとえば、このスタイル定義:
Styles(func() {
ElementStyle("database", func() {
Background("#438DD5")
Color("#ffffff")
})
RelationshipStyle("async", func() {
Color("#707070")
})
})
は、次のような SVG を生成します:
<rect fill="var(--mdl-database-bg, #438DD5)" ... />
<text fill="var(--mdl-database-color, #ffffff)" ... />
<path stroke="var(--mdl-rel-async-color, #707070)" ... />
サイトにテーマを実装する:
/* Light theme (defaults from SVG) */
:root {
--mdl-database-bg: #438DD5;
--mdl-database-color: #ffffff;
--mdl-rel-async-color: #707070;
}
/* Dark theme overrides */
[data-theme="dark"] {
--mdl-database-bg: #3a7bc8;
--mdl-database-color: #f0f0f0;
--mdl-rel-async-color: #9ca3af;
}
SVG は単体でも動作し(フォールバック値で正しく描画されます)、これらの CSS 変数が定義されたページに埋め込むと自動的に適応します。
重要: CSS カスタムプロパティが効くのは、SVG が HTML にインライン展開される場合のみです。<img src="..."> で読み込んだ SVG は親ページの CSS を継承しません。Hugo サイトでは、diagram ショートコードで SVG をインライン化します:
{{< diagram name="MyDiagram" >}}
{{< diagram name="MyDiagram" caption="System architecture" >}}
デプロイメントのモデリング
インフラと実行環境をモデル化します:
var _ = Design("System", "desc", func() {
var System = SoftwareSystem("My System", func() {
var API = Container("API", "Backend", "Go")
var DB = Container("Database", "Storage", "PostgreSQL")
})
DeploymentEnvironment("Production", func() {
DeploymentNode("AWS", "Amazon Web Services", "Cloud Provider", func() {
DeploymentNode("us-east-1", "US East Region", "AWS Region", func() {
DeploymentNode("ECS Cluster", "Container orchestration", "AWS ECS", func() {
Instances("3") // Can be number or range like "1..N"
ContainerInstance("My System/API", func() {
InstanceID(1)
HealthCheck("API Health", func() {
URL("https://api.example.com/health")
Interval(30) // Seconds
Timeout(5000) // Milliseconds
Header("Authorization", "Bearer token")
})
})
})
DeploymentNode("RDS", "Managed database", "AWS RDS", func() {
ContainerInstance("My System/Database")
})
InfrastructureNode("ALB", "Load balancer", "AWS ALB", func() {
Tag("infrastructure")
URL("https://docs.aws.amazon.com/elasticloadbalancing/")
})
})
})
})
Views(func() {
DeploymentView(Global, "Production", "ProdDeploy", "Production deployment", func() {
AddAll()
AutoLayout(RankLeftRight)
})
})
})
mdl CLI
mdl serve — インタラクティブエディタ
mdl serve ./model -dir gen -port 8080
http://localhost:8080 で Web ベースのエディタを起動します。主な機能:
- 要素をドラッグして配置できる
- ELK.js による複数のレイアウトアルゴリズム(Layered, Force, Tree, Radial)
- 整列と分配ツール
- Undo/Redo(Ctrl+Z / Ctrl+Shift+Z)
- SVG エクスポート
- DSL の変更をホットリロード
主なショートカット(Mac は Cmd を使用):
| 操作 | ショートカット |
|---|---|
| 保存 | Ctrl+S |
| Undo | Ctrl+Z |
| Redo | Ctrl+Shift+Z |
| すべて選択 | Ctrl+A |
| ズーム in/out | Ctrl++/- または Ctrl+scroll |
| 画面にフィット | Ctrl+9 |
| ズーム 100% | Ctrl+0 |
| 自動レイアウト | Ctrl+L |
| グリッド切替 | Ctrl+G |
| グリッドにスナップ | Ctrl+Shift+G |
| 水平整列 | Ctrl+Shift+H |
| 垂直整列 | Ctrl+Shift+A |
| 頂点を追加 | Alt+Click |
mdl gen — JSON エクスポート
mdl gen ./model -out design.json
モデルを JSON として書き出します。他ツールとの連携に便利です。
mdl svg — ヘッドレス SVG 生成
# Generate all views
mdl svg ./model -dir gen --all
# Generate specific views
mdl svg ./model -dir gen --view Context --view Containers
# With layout options
mdl svg ./model -dir gen --all --direction LEFT --compact --timeout 30s
ヘッドレスブラウザで自動レイアウトして保存します。CI/CD に最適です。
stz CLI
Structurizr にモデルをアップロードします:
# Generate Structurizr workspace JSON
stz gen ./model
# Upload to Structurizr
stz put workspace.json -id WORKSPACE_ID -key API_KEY -secret API_SECRET
# Download from Structurizr
stz get -id WORKSPACE_ID -key API_KEY -secret API_SECRET -out workspace.json
Goa プラグイン
Model を Goa と一緒に使う場合、design パッケージで DSL を import します:
package design
import (
. "goa.design/goa/v3/dsl"
. "goa.design/model/dsl"
)
// API definition
var _ = API("orders", func() {
Title("Orders API")
})
var _ = Service("orders", func() {
Method("create", func() { /* ... */ })
})
// Architecture model
var _ = Design("Orders", "Order management system", func() {
var OrdersAPI = SoftwareSystem("Orders API", "Manages orders", func() {
Container(OrdersService) // Uses Goa service
})
Person("Customer", "Places orders", func() {
Uses(OrdersAPI, "Creates orders via", "HTTPS")
})
Views(func() {
SystemContextView(OrdersAPI, "Context", "System context", func() {
AddAll()
AutoLayout(RankTopBottom)
})
})
})
goa gen を実行すると、API コードとアーキテクチャ図(design.json と workspace.json)の両方が生成されます。
CI/CD 連携
GitHub Actions
name: Generate Architecture Diagrams
on: [push]
jobs:
diagrams:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.23'
- name: Install mdl
run: go install goa.design/model/cmd/mdl@latest
- name: Generate SVGs
run: mdl svg ./model -dir docs/architecture --all
- name: Commit diagrams
run: |
git config user.name github-actions
git config user.email [email protected]
git add docs/architecture/
git diff --cached --quiet || git commit -m "Update architecture diagrams"
git push
ベストプラクティス
大規模モデルの整理
複数ファイルへ分割します:
// model/systems.go
package model
import . "goa.design/model/dsl"
var ECommerce = SoftwareSystem("E-Commerce", "Online store", func() {
// containers...
})
var Payments = SoftwareSystem("Payments", "Payment processing", func() {
// containers...
})
// model/views.go
package model
import . "goa.design/model/dsl"
var _ = Design("Platform", func() {
Views(func() {
SystemLandscapeView("Landscape", "All systems", func() {
AddAll()
})
// more views...
})
})
タグ付けの一貫性
タグの規約を決め、統一します:
// Element types
Tag("database")
Tag("api")
Tag("frontend")
Tag("queue")
// Visibility
Tag("external")
Tag("internal")
// Criticality
Tag("critical")
Tag("tier-1")
共有コンポーネントのインポート
// shared/infrastructure.go
package shared
import . "goa.design/model/dsl"
var Stripe = SoftwareSystem("Stripe", "Payment processing", func() {
External()
Tag("external", "payments")
})
// myservice/model.go
package model
import (
. "goa.design/model/dsl"
_ "mycompany/shared" // Import shared elements
)
var _ = Design("My Service", func() {
var Service = SoftwareSystem("My Service", func() {
Uses("Stripe", "Processes payments via") // Reference by name
})
})
リファレンス
- DSL Package Documentation — DSL の全体リファレンス
- Model GitHub Repository — ソースコードと例
- C4 Model — C4 モデルのドキュメント
- Structurizr — 図のホスティングサービス