- 設計
ドメインモデル図の書き方|クラス図との違いと例
ドメインモデル図は、業務に出てくる「もの・こと」(会員・注文・商品など)と、そのつながり(関連と多重度)を、クラス図の記号で描いた図です。 画面・テーブル・プログラムの都合を入れず、業務の言葉だけで描きます。書く順番は、①名詞を拾う → ②関連を引く → ③多重度を書く → ④属性を書く、の4つです。
| 手順 | やること | 受注・会員の例 |
|---|---|---|
| ① 名詞を拾う | 業務の説明から名詞を抜き出し、概念(クラス)の候補にする | 会員、注文、注文明細、商品、配送先、出荷 |
| ② 関連を引く | 概念どうしのつながりを線で結び、動詞の名前を付ける | 会員が注文を「行う」 |
| ③ 多重度を書く | 線の両端に「いくつ対いくつか」を書く | 会員 1 ― 0..* 注文 |
| ④ 属性を書く | 概念が持つ値を書く(この段階では型を書かない) | 注文:注文番号、注文日時、状態 |
確認環境:定義は、Martin Fowler の『Patterns of Enterprise Application Architecture』のカタログページ「Domain Model」と、Eric Evans の「Domain-Driven Design Reference」(2015年3月版)の原文で確かめました。図は Mermaid 12.1.0(@mermaid-js/mermaid-cli 12.0.0、Linux)で描画できることを、テーブル設計の DDL は SQLite 3.45.1 で実行して確かめています(確認日 2026年10月13日)。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「ドメインモデル図の書き方|クラス図との違いと例」
ドメインモデルとは(原典の定義)
ドメインモデルの定義は、資料によって少しずつ違います。 よく引かれる2つを原文で示します。
| 出典 | 原文 | 意味 |
|---|---|---|
| Martin Fowler『Patterns of Enterprise Application Architecture』の「Domain Model」 | "An object model of the domain that incorporates both behavior and data." | 振る舞いとデータの両方を持つ、業務領域のオブジェクトモデル |
| Eric Evans「Domain-Driven Design Reference」の用語集「model」 | "A system of abstractions that describes selected aspects of a domain and can be used to solve problems related to that domain." | 業務領域のうち選んだ面を表し、その領域の問題を解くのに使える、抽象のまとまり |
| 同・用語集「domain」 | "A sphere of knowledge, influence, or activity." | 知識・影響・活動の範囲 |
- Fowler の説明では、ドメインモデルは「つながり合ったオブジェクトの網」で、それぞれのオブジェクトは「会社のような大きなものから、注文書の1行のような小さなものまで」、意味のある1つの対象を表します。
- Evans の用語集では、モデルは「業務領域の、選んだ面」を表すものです。全部を描くのではなく、解きたい問題に関係する面だけを描く、という点が大事です。
- Fowler は別の記事「AnemicDomainModel」で、getter と setter だけの入れ物になったオブジェクトを「ドメインモデル貧血症」と呼び、検証・計算・業務ルールはドメインのオブジェクトに置くべきだとしています。
この記事では、「業務の概念とそのつながりを、クラス図の記号で描いたもの」をドメインモデル図と呼びます。 要件定義の段階では属性と関連だけを描き、設計の段階で振る舞い(操作)を足す、という進め方を取ります。DDD の全体像は、関連記事「ドメイン駆動設計入門|DDDの基本と小さく始める手順をJavaで解説」で扱っています。
クラス図・ER図との違い
3つの図は記号が似ていますが、描く目的と描くものが違います。
| 比べる点 | ドメインモデル図 | クラス図(設計) | ER図 |
|---|---|---|---|
| 目的 | 業務の概念と言葉をそろえる | プログラムの構造を決める | テーブルの構造を決める |
| 描くもの | 業務の概念(会員、注文) | クラス・インタフェース(OrderService) |
テーブル・列 |
| 属性の型 | 書かないことが多い | 書く(String、int) |
書く(VARCHAR、INTEGER) |
| 操作(メソッド) | 書かないか、業務の振る舞いだけ | 書く。可視性(+ −)も | 書かない |
| ID・外部キー | 業務で使う番号だけ(会員番号) | 実装に合わせて書く | 主キー・外部キーを書く |
| 多対多 | そのまま描く | そのまま、または中間クラス | 中間テーブルに分ける |
| 主な読み手 | 業務の担当者・開発者 | 開発者 | 開発者・DB設計者 |
| 書く時期 | 要件定義 | 基本設計〜詳細設計 | 基本設計〜詳細設計 |
ドメインモデル図は、クラス図の記号(クラスの箱・関連・多重度・コンポジション)を使います。 記号の意味は、関連記事「UML クラス図の書き方|記号・関係線6種の一覧とMermaid/PlantUML」で扱っています。ER図の記号は、関連記事「ER図の書き方|記号の読み方からCREATE TABLEまで実行して確認」で扱っています。
書く手順(受注・会員の例)
例として、次の業務の説明からドメインモデル図を描きます。内容は説明のための仮のものです。
会員は、会員番号・氏名・メールアドレスで登録する。会員にはランク(一般・シルバー・ゴールド)がある。
会員は配送先を1件以上登録できる。
会員は商品を選んで注文する。1回の注文には、1つ以上の商品を、それぞれ数量を指定して入れる。
注文には、注文した時点の単価を記録する。注文ごとに、登録した配送先から届け先を1つ選ぶ。
注文は、在庫がそろった分から出荷する。1つの注文が複数回に分けて出荷されることがある。
出荷前の注文は取り消せる。
① 名詞を拾う
説明の中の名詞を全部抜き出し、「概念(クラス)」「属性(値)」「捨てるもの」に分けます。
| 拾った名詞 | 分け方 | 理由 |
|---|---|---|
| 会員 | 概念 | 番号で区別し、注文と結び付く |
| 会員番号・氏名・メールアドレス・ランク | 会員の属性 | 会員を説明する値 |
| 配送先 | 概念 | 1人の会員が複数持つ |
| 商品 | 概念 | 注文の明細から指される |
| 注文 | 概念 | 業務の中心になる「こと」 |
| 数量・単価 | 注文明細の属性 | 注文の「1行」ごとに違う値 |
| 注文明細 | 概念(説明に出ていないが足す) | 「1つ以上の商品を、それぞれ数量を指定して」から |
| 出荷 | 概念 | 1つの注文に複数回ある |
| 在庫 | 今回は捨てる | この図で解く問題(受注)の外。在庫の図で扱う |
| 時点 | 捨てる | 注文日時の属性で表せる |
「注文明細」のように、説明に名前が出てこない概念を足すのがコツです。 「1回の注文に、商品ごとの数量がある」という文から、「注文の1行」という概念が必要だと分かります。Fowler が例に挙げている「注文書の1行」と同じものです。
② 関連を引く
概念どうしを線で結び、線に動詞の名前を付けます。 この段階では多重度はまだ書きません。
classDiagram
direction LR
class Member["会員"]
class Address["配送先"]
class Order["注文"]
class OrderLine["注文明細"]
class Product["商品"]
class Shipment["出荷"]
Member -- Address : 登録する
Member -- Order : 行う
Order -- OrderLine : 含む
OrderLine -- Product : 指す
Order -- Address : 届け先にする
Order -- Shipment : 出荷される
矢印の向き(どちらからたどれるか)は、ドメインモデル図では決めないことが多いです。 たどる向きはプログラムの設計で決めることだからです。この記事の図でも、関連は向きのない線で描いています。
③ 多重度を書く
線の両端に「相手1つに対して、こちらがいくつあるか」を書きます。 業務の担当者に「注文が1件もない会員はいますか」「明細のない注文はありますか」と聞くと決まります。
| 関連 | 多重度 | 業務の確認 |
|---|---|---|
| 会員 ― 配送先 | 1 ― 1..* | 配送先は1件以上(登録時に必ず1件) |
| 会員 ― 注文 | 1 ― 0..* | 注文が1件もない会員もいる |
| 注文 ― 注文明細 | 1 ― 1..* | 明細のない注文はない |
| 注文明細 ― 商品 | 0..* ― 1 | 1行は1つの商品を指す。売れていない商品もある |
| 注文 ― 配送先 | 0..* ― 1 | 注文ごとに届け先は1つ |
| 注文 ― 出荷 | 1 ― 0..* | 出荷前は0件。分けて出荷すると複数 |
| 多重度の書き方 | 意味 |
|---|---|
1 |
ちょうど1つ |
0..1 |
0か1つ |
0..*(*) |
0以上 |
1..* |
1以上 |
④ 属性を書く(完成図)
最後に、それぞれの概念が持つ値を書きます。 型は書かず、業務の言葉で書きます。注文と注文明細は、明細が注文なしでは存在しないため、コンポジション(黒いひし形)でつないでいます。
classDiagram
direction LR
class Member["会員"] {
会員番号
氏名
メールアドレス
会員ランク
}
class Address["配送先"] {
郵便番号
住所
宛名
}
class Order["注文"] {
注文番号
注文日時
状態
合計金額()
取り消す()
}
class OrderLine["注文明細"] {
数量
単価
小計()
}
class Product["商品"] {
商品コード
商品名
販売価格
}
class Shipment["出荷"] {
出荷日
送り状番号
}
Member "1" -- "1..*" Address : 登録する
Member "1" -- "0..*" Order : 行う
Order "1" *-- "1..*" OrderLine : 含む
OrderLine "0..*" -- "1" Product : 指す
Order "0..*" -- "1" Address : 届け先にする
Order "1" -- "0..*" Shipment : 出荷される
- 注文明細の「単価」と商品の「販売価格」は別の属性です。 業務の説明に「注文した時点の単価を記録する」とあるからです。販売価格が変わっても、過去の注文の金額は変わりません。
- 注文の「合計金額()」「取り消す()」は、業務の振る舞いです。Fowler の定義のとおり、振る舞いもモデルに含めると、「出荷前なら取り消せる」というルールを置く場所が決まります。要件定義の段階で振る舞いを書かない場合は、ルールを図の横に注記で書きます。
- 状態(受付済み・出荷済み・取消)の移り変わりは、状態遷移図で別に描くと整理できます。関連記事「状態遷移図と状態遷移表の書き方|テスト設計とMermaid・PlantUML」で扱っています。
Mermaid のテンプレート
上の完成図のコードは、そのまま書き換えて使えます。Mermaid の classDiagram では、class Order["注文"] のように 英字の ID と表示名を分けると、日本語の名前を表示できます。関連の書き方は A "多重度" -- "多重度" B : 関連名 です。
よくある誤り
ドメインモデル図に、画面やテーブル、プログラムの都合が入ると、業務の担当者が読めなくなり、業務の言葉もそろわなくなります。 次の図は、誤りをわざと入れた例です。
classDiagram
direction LR
class OrderTable["T_ORDER"] {
ORDER_ID : BIGINT
MEMBER_ID : BIGINT
MEMBER_NAME : VARCHAR
DEL_FLG : CHAR
}
class OrderSearchForm["注文検索画面"] {
検索条件
ページ番号
}
class OrderService["注文サービス"] {
+register()
+findAll()
}
OrderSearchForm --> OrderService
OrderService --> OrderTable
| 誤り | 図の中の例 | 直し方 |
|---|---|---|
| テーブル名・列名で書く | T_ORDER、ORDER_ID |
業務の言葉(注文、注文番号)で書く |
| 外部キーを属性に書く | MEMBER_ID |
会員との関連の線で表す |
| ほかの概念の属性を写す | 注文に MEMBER_NAME |
会員の属性に置き、関連でたどる |
| 削除フラグなど管理用の列を書く | DEL_FLG |
業務の状態(取消)で表す |
| 画面を概念にする | 注文検索画面、ページ番号 | 画面は画面設計で扱う |
| プログラムの部品を書く | 注文サービス、register() |
設計のクラス図で扱う |
| 動詞を概念にする | 「注文する」という箱 | 「注文」という名詞にする。動きは関連名か操作に |
| 多重度を書かない | 線だけ | 両端に 1、0..* などを書く |
「T_ORDER」と「注文」は、似ていても役割が違います。 ドメインモデル図で業務の言葉をそろえてから、テーブルに落とし込みます。業務の言葉をチームでそろえる考え方は、DDD では「ユビキタス言語」と呼ばれます。Evans の用語集では「境界づけられたコンテキストの中で、チーム全員が使う、ドメインモデルを軸にした言葉」とされています。
業務の出来事から概念を見つける方法もあります。 「注文を受け付けた」「商品が出荷された」のような出来事を先に並べ、その出来事にかかわる「もの」を拾う方法です。関連記事「イベントストーミングのやり方|付箋の色・進め方・システム開発の例」で扱っています。
テーブル設計への落とし込み
ドメインモデル図の概念・属性・関連は、次の対応でテーブルに落とし込めます。 そのまま1対1で写せないところ(値の写し、多対多、状態)を決めるのが、この段階の仕事です。
| ドメインモデル図 | テーブル設計 | 受注・会員の例 |
|---|---|---|
| 概念 | テーブル | 注文 → orders |
| 属性 | 列(型・NOT NULL・CHECK を決める) | 数量 → quantity INTEGER NOT NULL CHECK (quantity >= 1) |
| 1 ― 0..* の関連 | 多い側に外部キー | 注文 → member_id |
| コンポジション | 子の主キーに親のキーを含める | 注文明細の主キー (order_id, line_no) |
| 多対多の関連 | 中間テーブル | (この例にはない) |
| 業務の番号 | 一意制約(主キーは別に持つことが多い) | 会員番号 → member_no UNIQUE |
| 状態 | 値を限った列 | status CHECK (status IN (...)) |
| 「その時点の値」を持つ関連 | 値を写して持つ | 注文の届け先を orders に写す |
注文の届け先は、配送先テーブルへの外部キーではなく、注文の時点の住所を写して持つ設計にしています。 会員があとで配送先を変えても、過去の注文の届け先が変わらないようにするためです。ドメインモデル図の「注文 ― 配送先」の関連を、テーブルでは「写し」で表した例です。
PRAGMA foreign_keys = ON;
CREATE TABLE member (
member_id INTEGER PRIMARY KEY,
member_no TEXT NOT NULL UNIQUE,
name TEXT NOT NULL,
email TEXT NOT NULL UNIQUE,
member_rank TEXT NOT NULL DEFAULT 'REGULAR'
CHECK (member_rank IN ('REGULAR', 'SILVER', 'GOLD'))
);
CREATE TABLE address (
address_id INTEGER PRIMARY KEY,
member_id INTEGER NOT NULL REFERENCES member(member_id),
postal_code TEXT NOT NULL,
address_line TEXT NOT NULL,
recipient TEXT NOT NULL
);
CREATE TABLE product (
product_id INTEGER PRIMARY KEY,
product_code TEXT NOT NULL UNIQUE,
product_name TEXT NOT NULL,
sales_price INTEGER NOT NULL CHECK (sales_price >= 0)
);
CREATE TABLE orders (
order_id INTEGER PRIMARY KEY,
order_no TEXT NOT NULL UNIQUE,
member_id INTEGER NOT NULL REFERENCES member(member_id),
ordered_at TEXT NOT NULL,
status TEXT NOT NULL
CHECK (status IN ('ACCEPTED', 'SHIPPED', 'CANCELLED')),
-- 届け先は注文した時点の内容を写して持つ
ship_postal_code TEXT NOT NULL,
ship_address_line TEXT NOT NULL,
ship_recipient TEXT NOT NULL
);
CREATE TABLE order_line (
order_id INTEGER NOT NULL REFERENCES orders(order_id),
line_no INTEGER NOT NULL,
product_id INTEGER NOT NULL REFERENCES product(product_id),
quantity INTEGER NOT NULL CHECK (quantity >= 1),
unit_price INTEGER NOT NULL CHECK (unit_price >= 0),
PRIMARY KEY (order_id, line_no)
);
CREATE TABLE shipment (
shipment_id INTEGER PRIMARY KEY,
order_id INTEGER NOT NULL REFERENCES orders(order_id),
shipped_on TEXT NOT NULL,
tracking_no TEXT
);
同じ構造を Mermaid の ER 図で描くと、次のようになります(列は主なものだけ載せています)。
erDiagram
member ||--|{ address : "登録する"
member ||--o{ orders : "行う"
orders ||--|{ order_line : "含む"
product ||--o{ order_line : "指される"
orders ||--o{ shipment : "出荷される"
member {
INTEGER member_id PK
TEXT member_no UK
TEXT name
TEXT email UK
TEXT member_rank
}
orders {
INTEGER order_id PK
TEXT order_no UK
INTEGER member_id FK
TEXT ordered_at
TEXT status
TEXT ship_address_line
}
order_line {
INTEGER order_id PK, FK
INTEGER line_no PK
INTEGER product_id FK
INTEGER quantity
INTEGER unit_price
}
product {
INTEGER product_id PK
TEXT product_code UK
TEXT product_name
INTEGER sales_price
}
address {
INTEGER address_id PK
INTEGER member_id FK
TEXT address_line
}
shipment {
INTEGER shipment_id PK
INTEGER order_id FK
TEXT shipped_on
}
動かして確かめる
上の DDL を SQLite 3.45.1 の sqlite3 コマンドで流し、会員1人・商品2つ・注文1件(明細2行)を入れたあと、商品の販売価格を 1200 から 1500 に上げて、注文の合計と明細を出しました。
INSERT INTO member VALUES (1, 'M0001', '山田 花子', 'hanako@example.com', 'GOLD');
INSERT INTO address VALUES (1, 1, '100-0001', '東京都千代田区千代田1-1', '山田 花子');
INSERT INTO product VALUES (1, 'P-100', 'ボールペン 10本入り', 1200);
INSERT INTO product VALUES (2, 'P-200', 'A4 コピー用紙 500枚', 800);
INSERT INTO orders VALUES (1, 'O-2026-0001', 1, '2026-10-13T10:00:00', 'ACCEPTED',
'100-0001', '東京都千代田区千代田1-1', '山田 花子');
INSERT INTO order_line VALUES (1, 1, 1, 2, 1200);
INSERT INTO order_line VALUES (1, 2, 2, 5, 800);
-- 注文のあとで商品の販売価格を上げる
UPDATE product SET sales_price = 1500 WHERE product_id = 1;
SELECT o.order_no, SUM(l.quantity * l.unit_price) AS total
FROM orders o JOIN order_line l ON l.order_id = o.order_id
GROUP BY o.order_no;
SELECT l.line_no, p.product_name, l.unit_price, p.sales_price AS current_price
FROM order_line l JOIN product p ON p.product_id = l.product_id;
order_no total
----------- -----
O-2026-0001 6400
line_no product_name unit_price current_price
------- ------------- ---------- -------------
1 ボールペン 10本入り 1200 1500
2 A4 コピー用紙 500枚 800 800
合計は 1200×2+800×5=6400 のままで、販売価格の変更(1500)の影響を受けていません。 ドメインモデル図で「単価」と「販売価格」を分けたことが、テーブルでもそのまま効いています。
多重度の「1以上」「ちょうど1つ」を、制約で守れるかも試しました。
-- 数量 0 の明細を入れる(CHECK で止まる)
Runtime error near line 2: CHECK constraint failed: quantity >= 1 (19)
-- 存在しない商品 99 を指す明細を入れる(外部キーで止まる)
Runtime error near line 2: FOREIGN KEY constraint failed (19)
一方、「注文には明細が1つ以上(1..*)」は、この DDL の制約だけでは守れません。 明細のない注文の行も入れられるからです。こうした「集まり全体のルール」は、アプリケーションの側(注文を作る処理)で守ります。DDD では、注文と注文明細をひとまとまり(集約)として扱い、注文を通してしか明細を足せないようにします。
SQLite は、外部キーの検査を接続ごとに PRAGMA foreign_keys = ON; で有効にします。 上の確認でも、各接続の最初にこの文を流しています。テーブルと列の名前の付け方は関連記事「DB命名規則一覧|テーブル名・カラム名・インデックス名・制約名」で、正規化は「データベースの正規化をわかりやすく|第1〜第3正規形の例」で、多対多の中間テーブルは「多対多のテーブル設計|中間テーブルの主キー・命名・属性の決め方」で扱っています。
設計のクラス図へ進むとき
ドメインモデル図を、設計のクラス図に描き直すときは、型・可視性・たどる向きを足します。 注文の部分だけを描いた例です。
classDiagram
direction LR
class Order {
-OrderNo orderNo
-LocalDateTime orderedAt
-OrderStatus status
-List~OrderLine~ lines
+total() int
+cancel() void
}
class OrderLine {
-int lineNo
-ProductId productId
-int quantity
-int unitPrice
+subtotal() int
}
class OrderStatus {
<<enumeration>>
ACCEPTED
SHIPPED
CANCELLED
}
Order "1" *-- "1..*" OrderLine
Order --> OrderStatus
- 商品は
Productへの参照ではなく、ProductId(商品のID)で持っています。注文の集約の外にあるものは、ID で指す設計の例です。 cancel()の中で「出荷済み(SHIPPED)なら取り消せない」を判定すると、ルールが注文のクラスに集まります。Fowler の「ドメインモデル貧血症」を避ける書き方です。
オブジェクト指向の基本は、関連記事「オブジェクト指向とは?コードでわかる基本と使いどころ」で扱っています。
よくある質問
Q. ドメインモデル図は、誰が、いつ書きますか。 A. 要件定義の段階で、業務の担当者と開発者が一緒に書くと、言葉のずれをその場で直せます。業務の流れ(業務フロー図)と並べて描くと、流れの中に出てくる「もの」の抜けに気づきやすくなります。関連記事「業務フロー図の書き方|記号一覧・スイムレーン例とMermaidテンプレート」で扱っています。
Q. 属性に型は書きますか。 A. 「文字列か数値か」を決めるのは設計の仕事です。業務の担当者と話す段階では型を省き、「金額」「日付」のように業務の言葉で書くと、話がテーブルの都合に引っぱられにくくなります。
Q. どこまで細かく描きますか。 A. Evans の定義のとおり、モデルは「選んだ面」を表すものです。解きたい問題(この例では受注)に関係する概念だけを描き、在庫や会計は別の図に分けます。1枚に収まらなくなったら、業務のまとまりごとに図を分ける合図です。
Q. ER 図から始めるのは、どんな場合ですか。 A. データの登録と照会が中心で、業務のルールが少ないシステムでは、ER 図から始めることもあります。業務のルールが多く、言葉が部署ごとに違う場合は、ドメインモデル図で言葉をそろえてから ER 図に進むと、テーブルの意味の取り違えが減ります。
まとめ
- ドメインモデル図は、業務の概念とそのつながりを、クラス図の記号で、業務の言葉だけで描いた図
- 定義は Fowler が「振る舞いとデータの両方を持つ、業務領域のオブジェクトモデル」、Evans がモデルを「業務領域の選んだ面を表す抽象のまとまり」としている
- 書く手順は 名詞を拾う → 関連を引く → 多重度を書く → 属性を書く
- テーブル名・外部キー・画面・プログラムの部品は描かない
- テーブルへは、概念→テーブル、関連→外部キー、コンポジション→複合主キー、「その時点の値」→写し、で落とし込む。「1..*」のような集まりのルールは、制約だけでは守れないことを SQLite で確かめた
参考・出典
確認日はいずれも 2026年10月13日です。
- Martin Fowler「Domain Model」(Catalog of Patterns of Enterprise Application Architecture):https://martinfowler.com/eaaCatalog/domainModel.html
- Martin Fowler「AnemicDomainModel」:https://martinfowler.com/bliki/AnemicDomainModel.html
- Eric Evans「Domain-Driven Design Reference」(2015年3月版、CC BY 4.0):https://www.domainlanguage.com/ddd/reference/
- SQLite「SQLite Foreign Key Support」:https://www.sqlite.org/foreignkeys.html
- Mermaid「Class diagrams」:https://mermaid.js.org/syntax/classDiagram.html
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「ドメインモデル図の書き方|クラス図との違いと例」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。