パターンからわかりやすく入門するドメイン駆動設計(DDD)|研修コースに参加してみた

今回参加したコースは パターンからわかりやすく入門するドメイン駆動設計( DDD ) です。
ドメイン駆動設計( DDD Domain-Driven Design )は「難解」と言われます。 ただ今の時代、インフラの Kubernetes 、フロントエンドの React いずれもイミュータブルに遷移させるアーキテクチャが主流であり、バックエンドでも同様のムーブが起こっています。 DDD と並び関数型言語に注目が集まっているのも、そういった背景があるからですね。
フロントを React Hooks で、 urql や Recoil を使って状態管理。一方、ヘビーな業務を処理するバックエンドの はオニオンアーキテクチャよろしく戦術的DDD。クラス指向なOOで UseCase層あり、集約をきちんとつかったドメインモデルと、それに応じた Repository パターンみたいに実装する (続く)
— Naoya Ito (@naoya_ito) May 13, 2022
そう、「 DDD って難しいんでしょ」と思って記事や本をそっ閉じしていた私も、重すぎる腰を上げる時代になったのです(言い方)。
そんな “難解” なものでも、「 DDD は理解するコツがある」とおっしゃる講師の成瀬さん。 そのコツに従って学んでみると、ナント、私でも DDD の特徴が見えてきました! 先に挙げたツイートもわかるようになったのです !!
では、どのような内容だったのか、レポートします!
コース情報
想定している受講者 |
|
---|---|
受講目標 |
|
講師紹介
この “参加してみた” レポートでは初めての登場となる 成瀬 允宣さんが登壇されました。

GMO インターネットグループ テックリード
テックリードとしてソフトウェア開発の第一線で活躍するプログラマ。 開発業務の傍ら、所属会社 GMO インターネットグループにて研修講師を務めるほか、大規模カンファレンス登壇や個人セミナー講師として活躍。 得意領域はソフトウェア開発・設計全般。 難解な知識を分かりやすく噛み砕いて解説することを得意とする。
成瀬さんは日本最大の Java のカンファレンスでの登壇に加え、 YouTube でも「なるせみ」という IT 技術解説で人気のチャンネルを持ってらっしゃいます。
ドメイン駆動設計とは
まずはドメイン駆動設計とは何か紹介いただきました。
- ソフトウェア開発は難しい
- 理由: たくさんの技術 + 対象のドメイン知識 (物流、など)
- ドメインとはソフトウェア対象領域
- ドメインのソフトウェアを作りたいなら、ドメインを主軸とした設計 = ドメイン駆動設計が必要
- 「エリック・エヴァンスのドメイン駆動設計」(翔泳社刊) という本が原典(翻訳版は 2013 年刊行。 原著は 2003 年出版)
- ただし、とっっっっっっても難解
- 「エリック・エヴァンスのドメイン駆動設計」(翔泳社刊) という本が原典(翻訳版は 2013 年刊行。 原著は 2003 年出版)
ドメイン駆動設計の進め方

- “モデリング” と “パターン” というパートに分けて進める
- 関係者と開発者が集まって、モデリングで設計して、設計したものをパターンで実装する
- それぞれに専門用語がある
- モデリングだと “ユビキタス言語” とかがある(これも難解)
- パターンだと “コード” でわかる
- “パターン” をおさえるとドメイン駆動設計の 4 割はわかる
なるほど! コードであればわかりやすくなるということですね。 そこで、今日はこの “パターン” を中心に解説いただきます。
ドメイン駆動設計におけるパターン
今日ご紹介いただくパターンとテクニックは以下の 10 のものです。

コースでは解説とコードを交えて詳細に解説いただきました。 ただ、それをまとめても大変なボリュームになるため、このレポートでは一部割愛しながら紹介します。
値オブジェクト value of object
まずはドメインを表現するオブジェクト(ドメインオブジェクト)の 1 つ、 “値オブジェクト” です。
最初に伺っていると、構造体のようなものかなぁ、と感じていたところ、オブジェクトらしくコードを含めたものでした。
- ドメインの値を表したオブジェクト
- 値は変更するものではなく、値を新しくて交換する
- 要は値はイミュータブル
- が、メンドイ!
- メリット
- 不変なので、扱いやすい = 並行処理がしやすい、キャッシュしやすい
- コードを通すので表現がわかりやすくなる
- それによってエラーがわかりやすい
- 誤りの値が代入されるとコンパイルエラーで検知できる
- コード量を節約できる (コンストラクタで定義する)
- 値オブジェクトの例
- 通貨単位が合わないと加算できないというドメインのルールを書く
public class Money { private final BigDecimal value; private final String currency; // 中略: コンストラクタが入る public Money add(Money arg) { objects.requireNonNull(arg); if (!currency.equals(arg.currency)) { throw new IllegalArgumentException("通貨単位が異なります。"); } return new Money(value.add(arg.value), arg.currency); } }
- 値オブジェクトの使用例
var jpy1 = new Money (100, "JPY"); var jpy2 = new Money (100, "JPY"); var sum = jpy1.add(jpy2); var usd1 = new Money(1, "USD"); var invalid = jpy1.add(usd1); // Error
コードと使用例を見ると、具体的にイメージできますね。 また、コンパイルエラーを吐いてくれるのは予想以上に安心安全ですが、定義を書くのが、確かに面倒です。
- 値オブジェクトのルール
- クラス名やメソッドはドメインの言葉を使う
- 値オブジェクトのポイント
- 値をロジックで記述する
というわけで、ルールを書ける構造体、というのが私の印象です。
エンティティ
今度はドメインオブジェクトのもう一つ、 “エンティティ” です。
- DB の用語ではない
- 値オブジェクトの逆
- 値に代入できる = 可変
- id で区別する
- 他のメソッドやロジックなどの値オブジェクトと同じルール
- エンティティの例
public class User { private final UserId id; private String Name; // 中略: コンストラクタが入る @Override public boolean equals(Obejct o) { if (this == o) return true; if (this == null || getClass() != o.getClass()) return false; User user = (User) o; return Objects.equals(id, user.id); } }
基本はドメインオブジェクト、値オブジェクトとエンティティの 2 つでモデリングした対象を実装するとのことです。 個人的には「名詞に閉じて処理を完結させるのか!?」とちょっと驚きました。
ドメインサービス
さて次なるものは “ドメインサービス” です。 うーむ、とにかく名前から何をするのか連想しにくくて困ります。
- 値オブジェクトやエンティティに書くと不自然な処理になるものをドメインサービスに渡す
- ex. 重複チェック
- 複数のインスタンスを使うときにドメインサービスに渡す
- 例えば物流の例
- 荷物と輸送拠点のクラスがあって、配送記録を残すときにどうする?
- 新しいドメインサービスを作って、そこに残す
- 例えば物流の例
- ドメインサービスを使いすぎて、ドメインオブジェクトに getter setter しかなくなることがある = ドメインモデル貧血症
- ドメインサービスを濫用しないようにする
- 対象になるのは、モノやコト(荷物や輸送拠点)ではなく “動き” (輸送) が多い
なるほど! 最後の “動き” でピンときましたね!
ここまでのところでドメインオブジェクトで完結できるようになったので、ユーザ登録処理をコードで表してみます。
public class Program {
public void createUser(String username) throws SQLException {
var user = new User(new UserName(username));
var userService = new UserService();
if (userService.exists(user)) {
throw new RuntimeException("ユーザが存在しています");
}
try (var connection = DriverManager
.getConnection("jdbc:oracle:thin:@localhost:1521:XE", "user", "pass")) {
try (var statement = connection
.prepareStatement("INSERT INTO users (id, name) VALUES(?, ?)")) {
// 登録処理
connection.commit();
}
}
}
}
id を持っているので、これはエンティティです。
また重複チェックをドメインサービスにあたる UserService に投げています。
public class UserService {
public boolean exists(User user) throws SQLException {
try (var connection = DriverManager
.getConnection("jdbc:oracle:thin:@localhost:1521:XE", "user", "pass")) {
try (var statement = connection
.prepareStatement("SELECT id FROM users WHERE name = ?")) {
statement.setString(1, user.getName().getValue());
var resultSet = statement.executeQuery();
return resultSet.next();
}
}
}
}
一方で、ユーザ登録処理なのに、データベースへの登録処理が大半を占めてしまっています。
リポジトリ
ドメイン駆動設計では、先程の登録処理のようなデータベースアクセスを “リポジトリ” というパターンを使って、ドメインオブジェクトから切り離します。

- データベースアクセスのためのオブジェクト (インターフェイス)
- ドメイン駆動設計はドメインを主軸にするので永続化の処理を後回しにしがち
- リポジトリを使うことでデータベースを切り替えられるようになる
- 本番と開発用を分けられて開発時は InMemoryRepository を使う
サンプルとして、リポジトリを使うと、どんなコードになるのか確認します。
先程のユーザ登録処理の create メソッドは以下のようになります。
public void createUser(String username) {
var user = new User(new UserName(username));
var userService = new UserService(repository);
if (userService.exists(user)) {
throw new RuntimeException("ユーザが存在しています");
}
repository.save(user);
}
データベースの処理は repository の save メソッドに任せていますね。
もう一つ、 UserService はどうなるのでしょうか。
public boolean exists(User user) {
var optUser = repository.find(user.getName());
return optUser.isPresent();
}
ここもデータベースの処理が無くなり、 repository の find メソッドに替わりました。
では、 repository はどのようなコードになるのでしょうか。
public abstract class InMemoryCrudRepository<K, V> {
public HashMap<K, V> keyToValue = new HashMap<>();
public void save(V value) {
// 処理
}
public Optional<v> find(K key) {
// 処理
}
public void delete(V value) {
// 処理
}
protected abstract K getkey(V value);
protected abstract V deepClone(V value);
}
この InMemoryCrudRepository がインターフェイスとなり、さらにデータベースの処理を実装したクラス InMemoryUserRepository が別にできます。
このパターンはフロントエンドの MVVM にも出てくるものですね。 それにしてもドメインオブジェクトに DAO (Data Access Obejct) すら入れないという徹底ぶりに驚きます。
アプリケーションサービス
ドメインを表現するドメインオブジェクト、ドメインオブジェクトをサポートするリポジトリを組み合わせて実現するのが “アプリケーションサービス” です。 名前付け、何とか変えてもらえないでしょうか … 。

- 具体的にはユースケースを達成するためのサービス
- DI (Dependency Injection) を使って成立させる
- ドメインサービスやリポジトリを入れる
- DI (Dependency Injection) を使って成立させる
先程のユーザ登録処理の Program というクラスがまさしくアプリケーションサービスです。 その中で userService や repository を使っていたので、それを DI で入れるということですね。
Public class UserApplicationService {
private final UserRepository repository;
private final UserService userService;
// DI
public UserApplicationService(UserRepository repository, UserService userService) {
this.repository = repository;
this.userService = userService;
}
public void createUser(String username) {
// 省略
}
}
ただし、上記のコードではトランザクションができないため、 Sprint Boot なら @Transactional アノテーションで、もし違うなら Transaction というインターフェイスを入れて、実装を別にする方法を紹介いただきました。
ファクトリ
先程リポジトリでデータベースアクセスを切り出しましたが、例えば、採番処理のようなものが必要だった場合、また再びデータベースアクセスが発生します。
それを解決するのが “ファクトリ” です。
- デザインパターンのファクトリではない
- ドメインオブジェクトを生成する
- 用途 looks_one ドメインオブジェクトからデータベースなどインフラに関するコードを出す
- id の採番処理のような、リポジトリで処理するもの以外のデータベースの処理を行う
public class InMemoryUserFactory implements UserFactory { private int number; @Override public User create(UserName name) { this.number++; var idValue = Integer.toString(number); return new User( new UserId(idValue), name ); } }
- id の採番処理のような、リポジトリで処理するもの以外のデータベースの処理を行う
- 用途 looks_two ドメイン同士が関連するときに使う
- 例えば、ユーザとサークルというドメインオブジェクトがあったとして、サークル作成にサークルメンバーとなるユーザの情報を入れなくてはならない
- そこでサークル内でファクトリを使って、ユーザ情報を生成する
public interface UserFactory { User create(UserName name); }
- getter を使わず、エンティティなどドメインオブジェクトを生成できる
- メソッドのようにファクトリを使うこともある(ファクトリメソッド)
// User クラス内でサークルを作る public Circle createCircle() { return new Circle( new CircleId(UUID.randomUUID().toString()), this.id ) }
集約
今日の一番のメインのようなトピック、 “集約” です。 成瀬さん曰く、最も難しいテーマとのことです。
- ドメインオブジェクトとの境界につくる
- 外部からデータを操作させないようにする
- 集約ルート ( Aggregate Root ) を必ず通して操作させる
- ドメインオブジェクトを不変にする
- 集約の単位はリポジトリで考える
先程のサークルとユーザを例に考えてみます。 以下のルールがあったとします。
- サークルには 29 名までユーザは登録できる
- サークルには別にサークルオーナーとなるユーザがいる

これをもとにサークルにユーザを登録することを考えてみましょう。
- サークルの所属するユーザ数が 29 以下ならユーザを登録する処理
public join(CircleId, circleId, UserId memberId) { // 中略 if (circle.getMembers().size() >= 29) { throw new CircleFullException(circleId); } circle.getMembers().add(memberId); }
- この処理の中に 29 名以下までと書いてしまうと、他の処理でも書いてしまう可能性がある
- 集約ルート外から User オブジェクトを操作してしまう
そこでサークルの集約ルートにこのルールを書きます。
public class Circle {
// 中略
public boolean isFull() {
return memberSize() >= 30;
}
public boolean memberSize() {
return members.size() + 1;
}
public void join(User member) {
Objects.requireNonNull(member);
if (isFull()) {
throw new CircleFullException(id);
}
members.add(member.getId());
}
}
こうすることでオブジェクトを不変にできます。
不変にすると「安心安全」だというのは理解できるのですが、集約のパターンを見ていると、なぜそこまでドメインオブジェクトにこだわるのか、素朴な疑問にかられました。
アーキテクチャ
最後に今までのものをアーキテクチャにして整理します。

- ヘキサゴナルアーキテクチャと言う
- ポートアンドアダプターとも呼ばれる
- UI やインフラ ( DB ) が差し替えやすくなる
- UserApplicationService から UserRepository までは変わらない
これによってドメインを UI や インフラ(ここでは DB )と分離できるようになりました。
ここまでがパターンのお話でした。 とはいえ、まだドメイン駆動設計の全体の 4 割だけなので、この先、モデリングもマスターしましょう、とのことでした。
DDD コードリーディング
ここからは、パターンを使ってどのように実装しているのか、成瀬さんの GitHub 上にあるリポジトリ( DDD の文脈ではない … ややこしい)を使って、動かしながらコードリーディングしました。
コードリーディング準備
利用したリポジトリ nrslib/itddd-java
これはもともと成瀬さんの著書「ドメイン駆動設計入門 ボトムアップでわかる!ドメイン駆動設計の基本」で書いたコードを Java に書き換えたコードで、レイヤードアーキテクチャが採用されています。
- インフラ ( JPA [ OR マッパー] を使用)
- アプリケーションサービス
- ドメインオブジェクト
- Web アプリケーションフレームワーク Spring Boot
もし IntelliJ IDEA をお使いの方がいらっしゃれば、以下のファイルの IntroductionToDddWebApplication クラスを動かすと動きます。
また API のドキュメントは以下の README にまとまっていますので、ぜひ操作してお試しください。
コードリーディング開始
コースでは実際にアプリケーションを動かして CRUD の操作を行い、ブレークポイントで止めて処理から処理へと、流れるようなコードリーディングでした。 圧巻でした!
その中でパターンの実装方法を詳しく解説されたのですが、動画で無い限り再現できないため(ぜひコースにご参加ください!)、ここでは解説中に出てきた Tips を紹介します。
パッケージ構造を整理すると見通しがよくなる
- ドメインオブジェクトを User 集約 として以下のようにまとめている
- User
- UserFactory
- UserId
- UserName
- UserRepository
- UserType
- アプリケーションサービスのメソッドごとに必要なオブジェクトを作ったりするので、以下のようにメソッドごとにディレクトリを用意する (ex. Circle のアプリケーションサービス)
/circle ├ common/ │ └ CircleData ├ create/ │ └ CircleCreateInputData ├ get/ │ ├ CircleGetInputData │ └ CircleGetOutputData ├ getcandidates/ │ ├ CircleGetCandidatesInputData │ └ CircleGetCandidatesOutputData :
- 上記と同じようにディレクトリを分ける必要はないが、 valueobject のようなディレクトリに値オブジェクトを全部突っ込むことは避ける
record 型を使うとクラス定義をシンプルに書ける
- class の代わりに Java 16 で追加された record 型で書くと、シンプルな記述をするだけで getter などが自動的に定義される ex. UserName.java
- Record クラスは必ず値が不変になるので、値オブジェクトに最適
レイヤーをまたがるときにオブジェクトの積み替えをする
- レイヤーをまたがってデータを渡すときに、そのままの形で渡す方針もあれば、違うオブジェクトにデータを積み替えるパターンもある
- サンプルでは、レイヤードアーキテクチャのユーザインターフェース層、ドメイン層、アプリケーション層、インフラストラクチャ層、それぞれをまたぐときに必ず DTO(Data Transfer Object )に格納してから渡している
- ex. ドメインからユーザの登録情報 UserPostResponseModel を Web に渡す UserController.java
@PostMapping public UserPostResponseModel post(@RequestBody UserPostRequestModel body) { var inputData = new UserRegisterInputData(body.name()); var user = userApplicationService.register(inputData); return new UserPostResponseModel( new UserResponseModel( user.id(), user.name(), user.userType() ) ); }
- ex. ドメインからユーザの登録情報 UserPostResponseModel を Web に渡す UserController.java
OR マッパー専用のクラスを作る
- サンプルでは、 OR マッパー専用のクラスを作っている ex. CircleDataModel.java
- もしもドメインオブジェクトに OR マッパーのためのコードを書いてしまうと、 OR マッパーを変更したくなったときに、ドメインオブジェクトの修正が必要になる
- OR マッパー専用のクラスを作れば(もしくは DAO を使えば)、 OR マッパーを変更するときはそのクラスを捨てて作り直せばうまく動く
フロントの要求のために QueryService を使う
- ページングなど、フロントのためのデータ要求のために、ドメインとは切り離して QueryService というものを作ることがある ex. JpaCircleQueryService.java
- アプリケーションサービスにインターフェイスを置いている CircleQueryService.java
- これはコマンドとクエリを分けようという CQS (コマンド・クエリ分離の原則)の考えに基づく
- コマンド系は、アプリケーションサービスやドメインオブジェクトを使って組み立てる
- 逆にクエリ(読み取り)は、それらを使わずに DB 直で見に行ってもよい(パフォーマンスを求められることが多いため)
レイヤードアーキテクチャへの不安
- レイヤを重ねても実行時間に変わりない
- アプリケーションサービスなどはユースケースごとにファイルを分けるので、メンバが同じファイルを触らずに開発できる = 開発効率が良い
- パターンを入れるとフレームワークの良さがトレードオフされるのでは?
- Spring Boot はそもそも自由な構成できるので、あまり影響はない
- PHP の Laravel を使った場合のパターンを生かした構成もあるので、時間があればご覧ください nrslib/LaraClean
- ただし Ruby on Rails は影響を受けるかも知れない
このあと、今後、学習をする上で、オススメの書籍を紹介いただきました。 前半にも紹介した成瀬さんの著書や、原典となる「エリック・エヴァンスのドメイン駆動設計」、加えて 「実践ドメイン駆動設計」(Vaughn Varnon 著、高木正弘 訳、翔泳社) を挙げていただきました。 最後の本はコード( Java )を中心に解説するので読みやすい、とのことでした。
モデリングの話
このコースは “パターン” にフォーカスした内容でしたが、最後におまけとして、 “モデリング” の用語として「ドメインエキスパート」と「ユビキタス言語」についても説明いただけました。 成瀬さん、ありがとうございます!
- ドメインを知る人たち
- プロダクトオーナや経営層ではない
- 並大抵の努力では巻き込めない
- 属人化を逆に歓迎しているケースが多い
- 開発メンバーがドメインエキスパートになることもある
- ドメインエキスパートの役割
- 開発者と一緒に知識を噛み砕く
ドメインエキスパート
- ドメインエキスパートと開発者の共通言語
- 技術語 で話さない / ドメイン固有語 で話さない
- ドメインエキスパートの知識が曖昧なことがある
- ドメインエキスパートの言葉がすべてではない
- なるべくこの言語に沿ってコードを書く
- 稀だが、ドメインエキスパートがコードを見てわかることがある
ユビキタス言語
この 2 つのモデリングの用語を説明したところで、このコースは修了しました。
まとめ
ドメイン駆動設計( DDD )のパターンを、コードを見ながら学びました。
とにかく「ドメインオブジェクトの安心安全を保つ」ためのテクニックが満載でした。 集約はその代表的なものでしたね。 成瀬さんに丁寧にコードで解説いただいたので、 DDD が何を重要としているのか、特徴がとてもわかるものでした。
一方で、素朴な Web アプリケーション(ほぼ CRUD 処理だけ)しか知らない経験不足な私は、コードリーディングで次から次へと interface を渡り、オブジェクトの移し替えを行う、その層の厚さに、見通しの悪さを感じてしまいました。 修行が足りませぬ(個人的に Laravel で「なぜこのコードが動くのか」を辿って内部のコードリーディングを進めていると、膨大な時間がかかって挫折した経験が思い出されました)。
なぜ、 DDD ではそこまでドメインオブジェクトにこだわるのか、ドメインが複雑だから、という理由では片付かない何かがありそうで、後半になるとパターンの前にある “モデリング” で何をしているのか、とても気になりました!
「 DDD って難しいんでしょ」と思うプログラマには、その特徴がわかりやすく、 DDD への興味が湧く内容でした!
label SEカレッジを詳しく知りたいという方はこちらから !!

IT専門の定額制研修 月額 28,000 円 ~/ 1社 で IT研修 制度を導入できます。
年間 670 講座をほぼ毎日開催中!!

SEプラスにしかないコンテンツや、研修サービスの運営情報を発信しています。