要約 / inb4 tl;dr
- 成瀬允宣さん(Twitter , nrslib )の 『ドメイン駆動設計入門 ボトムアップでわかる!ドメイン駆動設計の基本』 を読んでサンプルコードを Laravel で実装してみた。
- DDD 入門として、とても良い書籍だったのでオススメ。
- 実装の前提条件やポイントをまとめた。
- ※注:この記事は、あくまでサンプルコードの書き起こしと整理のみです。DDD 本来の設計作業についての記載はほとんどありません。
- コードはこちら
はじめに
2020年2月9-11日に開催された PHPerKaigi2020 に参加してきました。
こういうイベントごとには滅多に参加してこなかったのですが、今回は少し足を伸ばして練馬まで行ってきました。
沢山学びがあったのですが、その中でも以下のセッションがキッカケとなって DDD 入門するに至りました。
セッションでお話しされてた成瀬さんの書籍に興味を持って購入してみたのですが、これがまた本当に良い本でした。
ドメイン駆動設計入門 ボトムアップでわかる!ドメイン駆動設計の基本 - Amazon
一通り通読したあと、 Laravel / PHP で実装してみたので、忘れないうちに構築のステップなどをメモしておこうというのがこの記事の内容です。
概要
ローカル開発環境
実装時に使用したローカル開発環境のソフトウェア・スタックはコチラ
- docker - ローカル開発環境の構築 / Laravel on [ [Vagrant + VirtualBox] with Docker ] - Qiita
- Visual Studio Code - PHPの開発環境を VSCode と Vagrant + Docker で構築する時のツール類 - Qiita
- Visual Studio Code - "Remote Development" を使って Docker Container on "Vagrant + VirtualBox" のファイルを編集する - Qiita
- Visual Studio Code - Remote Development を使用して PHP Xdebug を使ったデバッグを行う - Qiita
- 開発環境 - Docker を使用した 可搬性 と 可変性 のある Web Application の開発環境を考える - Qiita
Software Version
実装時に使用したローカル開発環境のソフトウェア・バージョン情報はコチラ
項目 | 内容 | 備考 |
---|---|---|
HostOS | Windows 10 | |
仮想化 | VirtualBox Vagrant 2.1.2 |
|
Editor | Visual Studio Code 1.26.1 | |
WebBrowser | Chrome 80.0.3987.122 |
GuestOS のソフトウェア・バージョンは下表の通りです。
項目 | 内容 | 備考 |
---|---|---|
GuestOS | CentOS Linux release 7.6.1810 (Core) | |
git | git version 2.16.5 | |
heroku | heroku/7.35.1 linux-x64 node-v12.13.0 | |
OpenSSH | OpenSSH_7.4p1, OpenSSL 1.0.2k-fips 26 Jan 2017 | |
docker | Docker version 19.03.5, build 633a0ea | |
docker-compose | docker-compose version 1.17.1, build 6d101fb |
Application用の Docker Container のソフトウェア・バージョンは下表の通りです。
項目 | 内容 | 備考 |
---|---|---|
PHP | PHP 7.2.26 (cli) (built: Dec 28 2019 22:17:07) ( NTS ) | |
Xdebug | Xdebug 2.9.2 | |
composer | Composer version 1.9.3 2020-02-04 12:58:49 | |
Laravel | Laravel Framework 6.18.0 | |
PHPUnit | PHPUnit 8.5.2 |
一般的なWebシステムの構成
『将来的には、こういうシステムで稼働するはず』の図
環境定義
Application Architecture
最終的に出来上がったアプリケーション内の Package ( Module ) / Class の構成は下図のようになりました。
Set up
【dokcer環境 on VirtualBox by Vagrant】 を構築して Laravel と VSCode の初期設定まで。
1. CentOS7 の起動
ホストOS 上で以下のコマンドを実行することで、 Docker と docker-compose が入った CentOS 7 を構築します。
// GitHub から Vagrantfile と provision.sh を取得する
$ git clone https://github.com/anfangd/vagrantfile-centos7-and-docker centos-7-docker && cd $(basename $_ .git)
// HostOS に Vagrant のプラグインをインストールする
$ vagrant plugin install vagrant-vbguest
$ vagrant plugin install vagrant-proxyconf
// Vagrant の起動する
$ vagrant up
// 仮想環境へ ssh でアクセスする
$ vagrant ssh
起動した仮想環境内で、 Docker と docker-compose をコマンド操作できるか確認します。
// docker の動作確認をする
$ docker run hello-world
// docker のバージョン確認をする
$ docker --version
Docker version 18.06.1-ce, build e68fc7a
// docker-compose のバージョン確認をする
$ docker-compose --version
docker-compose version 1.17.1, build 6d101fb
HostOS の VSCode で Remote Development を使って、 GuestOS 上の Docker Container にアクセスするための手順は下記を参照ください。
2. Laravel の導入
仮想環境内で、 Laravel + MySQL + nginx を dockerコンテナ にインストールする為のスクリプトを取得し、ビルド、実行することで、 Laravel を導入します。
// GitHub から docker-compose 用の スクリプトを取得する
git clone https://github.com/anfangd/docker-compose-php-laravel.git dc-laravel && cd $(basename $_ .git)
// 取得したスクリプトをビルドして、docker-compose で コンテナを起動する
$ docker-compose build
$ docker-compose up -d
// laravel でプロジェクトを作成する
// nginx で `laravel-src` のフォルダ指定があるため、ディレクトリ名はこれで固定
$ docker-compose exec php composer create-project laravel/laravel laravel-src --prefer-dist "6.*"
// 作成したプロジェクトの storage に書き込み権限をつける
$ docker-compose exec php chmod -R 777 ./laravel-src/storage
// MySQL への接続情報を変更する
$ vim src/laravel-src/.env
Laravel から MySQL へ接続する為に、 .env を更新します。
DB_CONNECTION=mysql
DB_HOST=mysql
DB_PORT=3306
DB_DATABASE=root
DB_USERNAME=root
DB_PASSWORD=root
マイグレーションを行います。
// マイグレーションを行う
$ docker-compose exec php php ./laravel-src/artisan migrate
Migration table created successfully.
Migrating: 2014_10_12_000000_create_users_table
Migrated: 2014_10_12_000000_create_users_table
Migrating: 2014_10_12_100000_create_password_resets_table
Migrated: 2014_10_12_100000_create_password_resets_table
3. 接続確認
以下の URL から接続を確認します。
この画面を確認することが出来たら、初期構築は完了です。
4. composer で関連ツールの導入
composer
で導入するツールは次の通りです。
// IDの採番に使用する
$ composer require robinvdvleuten/ulid
// AOPの実現に使用する
$ composer require ytake/laravel-aspect:4.0
// Debug Tools
$ composer require --dev "squizlabs/php_codesniffer=*"
$ composer require --dev friendsofphp/php-cs-fixer
$ composer require --dev phpmetrics/phpmetrics
$ composer require --dev fzaninotto/faker
$ composer require --dev mockery/mockery
$ composer require --dev php-coveralls/php-coveralls
5. VSCode に 拡張機能を導入
VSCode に導入した拡張機能は次の通りです。
# PHP 関連
bmewburn.vscode-intelephense-client
felixfbecker.php-intellisense
ecodes.vscode-phpmd
ikappas.phpcs
junstyle.php-cs-fixer
MehediDracula.php-namespace-resolver
mikestead.dotenv
neilbrayfield.php-docblocker
# js 関連
dbaeumer.vscode-eslint
eg2.tslint
octref.vetur
# 便利ツール
eamodio.gitlens
donjayamanne.githistory
znck.grammarly
Gruntfuggly.todo-tree
wayou.vscode-todo-highlight
HookyQR.beautify
emilast.LogFileHighlighter
eriklynd.json-tools
redhat.vscode-yaml
IBM.output-colorizer
wraith13.background-phi-colors
# VSCode 関連
johnpapa.vscode-peacock
MS-CEINTL.vscode-language-pack-ja
ms-vscode-remote.remote-containers
ms-vscode-remote.remote-ssh
ms-vscode-remote.remote-ssh-edit
ms-vscode-remote.remote-wsl
ms-vscode-remote.vscode-remote-extensionpack
# その他
yzhang.markdown-all-in-one
shd101wyy.markdown-preview-enhanced
jebbs.plantuml
zhouronghui.propertylist
vscode-icons-team.vscode-icons
設計
理想としては以下の記事のように丁寧に設計を行ってみたいのですが、今回は書籍で紹介されていたコードの実装に注目しているので、簡単な情報の整理のみを行います。
ユビキタス言語
ユビキタス言語 | 英名 | 説明 |
---|---|---|
ユーザ | User | # 意味 サービスの利用者のこと。ユースケース図のアクターとなる。 # 用例 ユーザを作成する。 |
ユーザID | User ID | # 意味 ユーザを一意に識別するコードのこと。 # 用例 山田さんのユーザIDは XXXXX です。 |
ユーザ名 | User Name | # 意味 ユーザの名前のこと。姓名の分割は行わない。 |
プレミアム会員 | Premium Member | # 意味 ユーザの属性のうち、特殊な属性を持つユーザであることを示す。 # 用例 山田さんはプレミアム会員です。 |
サークル | Circle | # 意味 ユーザが所属する任意の組織のこと。 # 用例 佐藤さんは「野球」サークルのメンバーです。 |
サークルID | Circle ID | # 意味 サークルを一意に識別するコードのこと。 |
オーナ | Owner | # 意味 サービスの所有者のこと。 # 用例 「野球」サークルのオーナは山田さんです。 |
メンバー | Member | # 意味 サークルに所属するユーザのこと。 # 用例 岡村さんは「野球」サークルのメンバーです。 |
ユーザ登録 | Register User | # 意味 ユーザがサービスに利用登録する操作のこと。 # 用例 サービスの利用にあたってはユーザ登録をしてください。 |
ユーザ情報の更新 | Update User | # 意味 ユーザ名の変更操作を行うこと。 # 用例 本当にユーザ情報を更新しますか? |
退会 | Delete | # 意味 ユーザがサービスを利用停止すること。 # 用例 山田さんが退会しました。 |
サークル作成 | Create Circle | # 意味 サークルを新しく作成すること。 # 用例 山田さんがサークルを作成します。 |
参加 | Join | # 意味 ユーザがサービスのメンバーとして登録すること。 # 用例 林さんが「野球」サークルに参加しました。 |
勧誘 | Invite | # 意味 オーナがサークルにユーザを招待すること。 # 用例 山田さんが「野球」サークルに佐藤さんを勧誘する。 |
コンテキストモデル(?)
実装したシステムの全体像はこんな感じ。
ドメインモデル(?)
ドメインはシンプルに下図の通り。
ユースケース図
ユースケースは下図の通り。
ロバストネス分析
- ロバストネス図の概要
- ロバストネス分析の目的 - Qiita
- 実践ロバストネス分析 第1回 ロバストネス分析の基礎 | オブジェクトの広場
- ロバストネス図を活用したシステム設計 | Think IT(シンクイット)
UseCase: ユーザを登録する
UseCase: ユーザ情報を取得する
UseCase: ユーザ情報を更新する
UseCase: 退会する
UseCase: サークルを作成する
UseCase: サークルに参加する
UseCase: サークルに勧誘する
クラス図
省略。
ER図
ER図は下図の通り。
ディレクトリ構成
作成したアプリケーションのディレクトリ構成は以下の通り。
├── app
│ ├── Aspect # AOP Modules
│ │ ├── Annotation
│ │ ├── Interceptor
│ │ ├── Modules
│ │ │ ├── LogExceptionsModule.php
│ │ │ ├── LoggableModule.php
│ │ │ ├── QueryLogModule.php
│ │ │ └── TransactionalModule.php
│ │ └── PointCut
│ ├── Console
│ │ └── Kernel.php
│ ├── Database # Eloquent Models
│ │ └── Eloquent
│ │ ├── CircleDataModel.php
│ │ └── UserDataModel.php
│ ├── Exceptions
│ │ └── Handler.php
│ ├── Http # Http Controllers
│ │ ├── Controllers
│ │ │ ├── Controller.php
│ │ │ └── UserController.php # Controller
│ │ ├── Kernel.php
│ │ ├── Middleware
│ │ └── Models
│ │ └── User
│ ├── Providers
│ │ └── AppServiceProvider.php
│ └── User.php
│
├── bootstrap
│ └── app.php
├── composer.json
├── config
│ ├── app.php
│ └── ytake-laravel-aop.php # conf about Laravel-Aspect
├── database
│ └── migrations
│
├── packages # 作成したコードの大部分はこの下に配置
│ └── Techno # Domain Name
│ └── Sns # Sub Domain Name
│ ├── Application # Application Layer
│ │ ├── Circle
│ │ └── User
│ ├── Domain # Domain Layer
│ │ ├── Circle
│ │ ├── Exceptions # Exceptions
│ │ ├── Service
│ │ └── User
│ ├── Infrastructure # Infrastructure
│ │ ├── Eloquent
│ │ ├── InMemory
│ │ └── Notification
│ └── UseCase # UseCase
│ ├── Circle
│ └── User
│
├── routes
│ ├── api.php
│ └── web.php
└── tests
├── Feature # Feature Test Code
│ └── Http
│ └── Controllers
└── Unit # Unit Test Code
└── packages
└── Techno
└── Sns
実装
やっとこさ、この記事の本題です。
実装した順に、コードと対象スコープを記載します。
コードは、機能単位で GitHub の Pull Request にまとめているので、詳細はそちらを参照ください。
0. 関連ソフトウェア
基本的には 生のPHP + Laravel の一部機能( Collection ) で実現をしますが、 AOP の実現 や ユーザIDの発行 のために以下のソフトウェアを使用しました。
- AOP の実現
- ID の発行
1. Value Object の作成
2. Entity の作成
Service の作成
Repository の作成
Application Service の作成
Application Service の分割
Controller の作成 と IoC の実装
ファクトリ( Factory )の実装
AOP の実装
ここを始めた段階で、 Laravel の 7.x 系を使っていたことに気づいて 6.x 系にダウングレードした。。
サークル 関連機能の作成
集約( Aggregation )の実装
仕様( Specification )の実装
Notification の実装
実装したコードの残課題
- 最後の方は疲れてしまって、サークル機能で一部実装漏れがあります。
-
packages
ディレクトリ配下の構成がもっと良くなる気がしています。- ex.) Domain に関連するテストコードはコチラにまとめた方が良いかも。
- => Laravel の
tests/
は Laravel のスコープだけテストする。 - Laravel 側にまとめると、せっかく切り離したドメイン領域と繋がってしまう為。
- => Laravel の
- ex.)
tests/
配下のテストコードの見直し。-
tests/Unit
配下で DB接続までしてしまっているので、モックで対応する。 -
tests/Feature
でクラス間の結合をテストする。
-
- ex.) Domain Model の配置構成を改善する。
-
Model/
ディレクトリがあった方がService
と並列に捉えやすいかもしれない。
-
- ex.)
Infrastructure/
の位置付けに悩み。- いまの構成だとサブドメインごとに別々のものが出来上がりそう。
- ★ 以下の "Implementing Domain-Driven Design" のサンプルが参考になりそう。
- ex.) Domain に関連するテストコードはコチラにまとめた方が良いかも。
- Domain Object は 抽象クラス を継承するとより分かりやすくなりそう。
- ex.)
Value Object
の抽象クラスを作り継承するなど。
- ex.)
- 拡張Exception の配置場所に悩んでいる。
- =>
Domain/
と並列よりは、直下の方がスッキリした。
- =>
- DB 接続以外の Infrastructure も実装してみたい。
- ex.) SaaS 接続、メール送信、外部サービス認証、etc.
- CI との接続
- ex.) CircleCI との連動など。
- 共通クラスをどこに配置するか悩み。
感想
- 機能が全然ない割にコード量が膨大で驚き。
- DDD 以前に習熟しておくべき技術要素が盛りだくさん。
- TDD
- AOP
- Repository Pattern
- 設計手法
- git / ブランチ戦略(GitFlow) / GitHub
- ...etc.
- 成瀬さんがお話しされてた通り、チームに適用するときは何かしらのコード生成ツールを自作して導入しないと、メンバーはとっかかりづらい。
- UnitTest 作りながら実装しないと盛大にデグレが発生する。
- 事前の設計が非常に大事。
今後
繰り返しになりますが、『ドメイン駆動設計入門 ボトムアップでわかる!ドメイン駆動設計の基本』は非常に面白かったですし、入門書としてとても読みやすかったです。
本格的に DDD の道に入るべく、積読中の以下の本もちゃんと読もうと思います。