📘

「ドメイン駆動設計入門」を読んだ後

2021/12/03に公開

はじめに

ドメイン駆動設計の知識を持ち合わせていることが常識になりつつある昨今、しかし表層の部分を聞いて完全に理解した気持ちになっているエンジニアが大半だと思われます。私自身そのうちの一人だったので「そろそろドメイン駆動設計の本を読まないといけないな」と思い始め、(いきなりエヴァンス本に手を出すと恐らく挫折するので)成瀬さんの「ドメイン駆動設計入門」を購入して読むことにしたのですが、これがなんと非常に綺麗にまとまっており、成瀬さん自身の知見も豊富に含まれていたのでとても良い買い物だったなと思いました。

ただ読んだだけでは知識の定着率は低いまま「読んで満足」で終わってしまうため、浅い知識ではありますが、簡単に感想をまとめることに致しました(一通りドメイン駆動設計というものを理解したい場合は、ここにとても分かりやすい成瀬さんのドメイン駆動設計に関する記事「ボトムアップドメイン駆動設計」があるので是非こちらを参考にしてください)

書籍

成瀬 允宣. ドメイン駆動設計入門 ボトムアップでわかる!ドメイン駆動設計の基本

成瀬 允宣. ドメイン駆動設計入門 ボトムアップでわかる!ドメイン駆動設計の基本

ドメイン駆動設計はなぜ失敗することが多いのか

ドメイン駆動設計は、業務領域に焦点を当てたソフトウェアの設計手法なのでドメインモデリングをせずにDDDで出てくるパターンだけを取り入れシステムに適用させることは軽量DDDというアンチパターンになります。しかし、実際にDDDを学んだ方がこのアンチパターンをよく踏んでいる印象が強いです。何故そのアンチパターンを踏むのかを私なりに考えてみたのですが、

  • ドメインモデリングはしたいが、そもそも近くにドメインエキスパートがいない
  • 他部署にドメインエキスパートがいるらしいが、その人を特定するのと呼ぶのはとても面倒かつ時間がかかる
  • イケてるパターンだけでも今のシステムに適用させたい
  • DDD的に構築して、チームに普及しようとするがそもそも難易度が高いため浸透せず
  • DDDを導入した者がプロジェクトから抜け、誰も正解がわからないためメンテナンスされなくなる
  • DDDでの実装に疲れ、コピーアンドペーストが至る所で横行し、半端な形でDDDが守られるようになる

このような流れを辿っているのではないかと思います(実際に知り合いでこの末路を辿った方がいました)。そもそもDDDはメンバー全体の技術力が高いか、会社の構造や文化がそもそも目的に合った形でない限り短期間で導入するのは失敗に繋がりやすいのではないかと考えています。なぜなら、システムは長期的に運用される特性上一人の開発者が改善し運用し続けるには限界があり、チームや開発部以外の他部署とも連携しながら改善活動をしていかなければビジネスとしてスケールアウトしていかなくなるにも関わらず、周りと連携を取らず自分一人の限られた知識や力だけでいきなり導入を試みようとすれば破綻に繋がることは自明です。少なくとも、チームと合意形成をしておき、定期的に見直し議論する機会を設け、少しずつ改善活動を繰り返していき、それと同時にDDDに関する知識や重要性を全体に浸透させていく必要があります(他部署に関しては、そもそもまず開発者と定期的に会話をする機会を設けて「壁を無くす」ところからのスタートになるとは思いますが)。全体的に浸透するということは、ドメインエキスパートの重要性などを他部署も理解し始めているということでもあるため、この段階でドメインエキスパートと定期的にやりとり出来る機会を設けやすい状態になっているものと思われます。そこでやっとドメインモデリングできる環境が整い、本格的にDDDをスタートさせることができるのではないか、と考えております。しかし、これを一開発者が行うには、ある種「開発から長期的に離れる覚悟」が必要になってくると考えています。それほどにDDDの導入は難易度の高いことであり、痛みを伴うことだと私は思いますので、DDDを進めようとしている方がもしいるのであれば文化形成から慎重に進めていくことをお勧めします。

実装してみました

GitHub | RyoAtsuta / DDDPractice

成瀬さんのコードを参考にしながら少し実装してみましたが、書くと細かい部分で気になる部分が出てきたり色々と知見に繋がるのでやはり実際に手を動かすというのは非常に学習効率が良いので皆さんも是非読了後に実装してみてください。

「属性」で分類するのではなく「意味のまとまり」で

個人的にとても参考になったことは、パッケージ構成を「属性」で分類するのではなく、「意味のまとまり」で区切って管理していくことで、そのディレクトリ内部にあるファイルの一覧を見ればそれに関係しているクラスの存在を認識できるようになることです。例えば、属性で分類するというのはChangeUserDetailCommandクラスであればcommandディレクトリに置いておき, InMemoryUserRepositoryクラスであればrepositoryディレクトリに分類してそれぞれのディレクトリへ格納していくことです。これではuserという領域にはどんなクラスがあるのかをcommand, factory, modelなど全体的に確認しにいかなければならなくなり、規模が大きくなるにつれて依存関係が把握できなくなります。

.
├── Main.java
├── app
│   ├── application
│   │   └── user
│   │       └── UserApplicationService.java
│   ├── command
│   │   └── user
│   │       ├── ChangeUserDetailCommand.java
│   │       ├── CreateUserCommand.java
│   │       ├── DeleteUserCommand.java
│   │       ├── GetAllUsersCommand.java
│   │       └── GetUserCommand.java
│   ├── factory
│   │   └── user
│   │       ├── IUserFactory.java
│   │       ├── InMemoryUserFactory.java
│   │       └── MySQLUserFactory.java
│   ├── model
│   │   └── user
│   │       └── User.java
│   ├── repository
│   │   └── user
│   │       ├── IUserRepository.java
│   │       ├── InMemoryUserRepository.java
│   │       └── MySQLUserRepository.java
│   ├── service
│   │   └── user
│   │       └── UserService.java
│   └── specification
│       └── user
│           └── UserFullSpecification.java
└── sources.txt

一方、「意味のまとまり」で分類していくというのは以下のような形です。このようにしておけばuserをみて一目で何があって何に依存しているのかを把握することができます。

.
├── Main.java
├── app
│   ├── application
│   │   └── user
│   │       ├── ChangeUserDetailCommand.java
│   │       ├── CreateUserCommand.java
│   │       ├── DeleteUserCommand.java
│   │       ├── GetAllUsersCommand.java
│   │       ├── GetUserCommand.java
│   │       ├── UserApplicationService.java
│   │       ├── UserCollection.java
│   │       └── UserData.java
│   ├── domain
│   │   └── model
│   │       └── user
│   │           ├── IUserFactory.java
│   │           ├── IUserRepository.java
│   │           ├── User.java
│   │           ├── UserFullSpecification.java
│   │           └── UserService.java
│   ├── in_memory  # 技術基盤毎に分けてます
│   │   └── user
│   │       ├── InMemoryUserFactory.java
│   │       └── InMemoryUserRepository.java
│   └── mysql
│       └── user
│           ├── MySQLUserFactory.java
│           └── MySQLUserRepository.java
└── sources.txt

もしも「app.application.userパッケージの中が多くなって見づらくなってきたな」と感じたら、「意味のまとまりの中」で属性で分類していけばパッケージの見通しが良くなります。

.
├── app
│   ├── application
│   │   └── user
│   │          ├── command
│   │          │   ├── ChangeUserDetailCommand.java
│   │          │   ├── CreateUserCommand.java
│   │          │   ├── DeleteUserCommand.java
│   │          │   ├── GetAllUsersCommand.java
│   │          │   └── GetUserCommand.java
│   │          ├── UserApplicationService.java
│   │          ├── UserCollection.java
│   │          └── UserData.java

パッケージ構成を考えることはやはり大切

読みながら感じたのは「やはりパッケージ構成をはじめにチームでしっかり議論し、ルール、方針などベースを構築してしまうのが効率良さそうだ」ということでした(読みながら自身の中でアイデアが広がっていった形なので、本の中でこのことに関して深く言及されているというわけではありません)。パッケージ構成の議論なしに行き当たりばったりで進めようとすると、新しいクラスを実装するたびに置き場所に迷い、ある者はそもそもクラスを実装することを諦めてメソッドのみで済まそうとし、またある者は適当な場所に「将来的に使うと思われる」新しいディレクトリを作成するなどして結果的にプロジェクト全体が散らかり、割れ窓理論のようにその中のソースコードの治安も悪くなり、目も当てられない状態になります。パッケージ構成をはじめに深く議論し、ベースを構築しておくことで、チームのメンバーはクラスの置き場所を迷わなくなり、気軽にクラスを実装しやすくなるのでプロジェクト全体としての開発スピードと可読性保守性が上がります。そして、大切なのは「定期的にパッケージ構成をチームで見直す会」のようなものを設定しておくことです。そうすることで、負担をかけずに少しずつ、長く継続的に改善していくことができます。

最後に

成瀬さんの「ドメイン駆動設計入門」は想像以上に学びが深く、有意義なものでした。この方のコードや考え方はとても私好みですので積極的に参考にしていきたいなと思っています。エンジニア歴2年目くらいの方が丁度可読性やクラス設計などを意識し始める頃合かなと思うので、この本をお勧めしておきます。特に「何がドメインのルールで、どこにおくべきか」を意識して実装できるようになればそれだけである程度ビジネス側の変更に強いシステムに作ることができるようになるので、是非ご一読いただければと思います(しかし、無理に完璧なものを求め過ぎないように)。

内容は少し薄いですが、今日はこれで終了とさせていただきます。

Discussion