Help us understand the problem. What is going on with this article?

Laravel - 『ドメイン駆動設計入門』を読んで Laravel を使って実装してみた

要約 / inb4 tl;dr

はじめに

2020年2月9-11日に開催された PHPerKaigi2020 に参加してきました。

image.png
PHPerKaigi 2020

こういうイベントごとには滅多に参加してこなかったのですが、今回は少し足を伸ばして練馬まで行ってきました。

沢山学びがあったのですが、その中でも以下のセッションがキッカケとなって DDD 入門するに至りました。

セッションでお話しされてた成瀬さんの書籍に興味を持って購入してみたのですが、これがまた本当に良い本でした。

image.png
ドメイン駆動設計入門 ボトムアップでわかる!ドメイン駆動設計の基本 - Amazon

一通り通読したあと、 Laravel / PHP で実装してみたので、忘れないうちに構築のステップなどをメモしておこうというのがこの記事の内容です。

概要

ローカル開発環境

実装時に使用したローカル開発環境のソフトウェア・スタックはコチラ

Software Version

実装時に使用したローカル開発環境のソフトウェア・バージョン情報はコチラ

HostOS のソフトウェア・バージョンは下表の通りです。
項目 内容 備考
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システムの構成

『将来的には、こういうシステムで稼働するはず』の図

記事の本筋とはズレますが、思考の整理ということで、今回、DDDで構築した Webアプリケーションの稼働する場所を整理します。
近年の Webアプリケーション のザックリとしたシステム構成は下図の感じで、赤い「Application Server」上で、Webアプリケーションは稼働します。

system-component-diagram.jpg

環境定義

『今回の構成をベースに、まずは heroku で動かしてみたい』の図

今回はローカルPC上で動けば満足なのですが、簡単にリモートでも動かすイメージとして Heroku での動作をイメージしています。

environment-scope.jpg

Application Architecture

最終的に出来上がったアプリケーション内の Package ( Module ) / Class の構成は下図のようになりました。

img_laravel_ddd_architectual_diagram.jpg

Set up

【dokcer環境 on VirtualBox by Vagrant】 を構築して Laravel と VSCode の初期設定まで。

1. CentOS7 の起動

ホストOS 上で以下のコマンドを実行することで、 Docker と docker-compose が入った CentOS 7 を構築します。

@HostOS
// 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 をコマンド操作できるか確認します。

@VirtualOS
// 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 を導入します。

@GuestOS
// 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 を更新します。

src/laravel-src/.env
DB_CONNECTION=mysql
DB_HOST=mysql
DB_PORT=3306
DB_DATABASE=root
DB_USERNAME=root
DB_PASSWORD=root

マイグレーションを行います。

@GuestOS
// マイグレーションを行う
$ 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 から接続を確認します。

image.png

この画面を確認することが出来たら、初期構築は完了です。

4. composer で関連ツールの導入

composer で導入するツールは次の通りです。

@DockerOS
// 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 に導入した拡張機能は次の通りです。

VSCode_extension
# 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


実装してみると、機能がほとんど無い割にコード量が多く、クラスもどんどん増殖してゆくので、VSCode の拡張機能や PHPUnit、 Xdebug を使い倒さないと実装の抜け漏れに気づくのが大変だということを痛感しました。

設計

理想としては以下の記事のように丁寧に設計を行ってみたいのですが、今回は書籍で紹介されていたコードの実装に注目しているので、簡単な情報の整理のみを行います。

ユビキタス言語

ユビキタス言語 英名 説明
ユーザ 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 # 意味
オーナがサークルにユーザを招待すること。
# 用例
山田さんが「野球」サークルに佐藤さんを勧誘する。

コンテキストモデル(?)

実装したシステムの全体像はこんな感じ。

ContextModel-Diagram.jpg

ドメインモデル(?)

ドメインはシンプルに下図の通り。

DomainModel-Diagram.jpg

ユースケース図

ユースケースは下図の通り。

UseCase-Diagram.jpg

ロバストネス分析

UseCase: ユーザを登録する

UseCase-001.jpg

UseCase: ユーザ情報を取得する

UseCase-002.jpg

UseCase: ユーザ情報を更新する

UseCase-003.jpg

UseCase: 退会する

UseCase-004.jpg

UseCase: サークルを作成する

UseCase-005.jpg

UseCase: サークルに参加する

UseCase-006.jpg

UseCase: サークルに勧誘する

UseCase-007.jpg

クラス図

省略。

ER図

ER図は下図の通り。

ER-Diagram.jpg

ディレクトリ構成

作成したアプリケーションのディレクトリ構成は以下の通り。

DirectoryTree
├── 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の発行 のために以下のソフトウェアを使用しました。

1. Value Object の作成

image.png

2. Entity の作成

image.png

Service の作成

image.png

Repository の作成

image.png

Application Service の作成

image.png

Application Service の分割

image.png

Controller の作成 と IoC の実装

image.png

ファクトリ( Factory )の実装

image.png

AOP の実装

ここを始めた段階で、 Laravel の 7.x 系を使っていたことに気づいて 6.x 系にダウングレードした。。

サークル 関連機能の作成

集約( Aggregation )の実装

仕様( Specification )の実装

Notification の実装

実装したコードの残課題

  • 最後の方は疲れてしまって、サークル機能で一部実装漏れがあります。
  • packages ディレクトリ配下の構成がもっと良くなる気がしています。
    • ex.) Domain に関連するテストコードはコチラにまとめた方が良いかも。
      • => Laravel の tests/ は Laravel のスコープだけテストする。
      • Laravel 側にまとめると、せっかく切り離したドメイン領域と繋がってしまう為。
    • ex.) tests/ 配下のテストコードの見直し。
      • tests/Unit 配下で DB接続までしてしまっているので、モックで対応する。
      • tests/Feature でクラス間の結合をテストする。
    • ex.) Domain Model の配置構成を改善する。
      • Model/ ディレクトリがあった方が Service と並列に捉えやすいかもしれない。
    • ex.) Infrastructure/ の位置付けに悩み。
      • いまの構成だとサブドメインごとに別々のものが出来上がりそう。
    • ★ 以下の "Implementing Domain-Driven Design" のサンプルが参考になりそう。
  • Domain Object は 抽象クラス を継承するとより分かりやすくなりそう。
    • ex.) Value Object の抽象クラスを作り継承するなど。
  • 拡張Exception の配置場所に悩んでいる。
    • => Domain/ と並列よりは、直下の方がスッキリした。
  • DB 接続以外の Infrastructure も実装してみたい。
    • ex.) SaaS 接続、メール送信、外部サービス認証、etc.
  • CI との接続
    • ex.) CircleCI との連動など。
  • 共通クラスをどこに配置するか悩み。

感想

  • 機能が全然ない割にコード量が膨大で驚き。
  • DDD 以前に習熟しておくべき技術要素が盛りだくさん。
    • TDD
    • AOP
    • Repository Pattern
    • 設計手法
    • git / ブランチ戦略(GitFlow) / GitHub
    • ...etc.
  • 成瀬さんがお話しされてた通り、チームに適用するときは何かしらのコード生成ツールを自作して導入しないと、メンバーはとっかかりづらい。
  • UnitTest 作りながら実装しないと盛大にデグレが発生する。
  • 事前の設計が非常に大事。

今後

繰り返しになりますが、『ドメイン駆動設計入門 ボトムアップでわかる!ドメイン駆動設計の基本』は非常に面白かったですし、入門書としてとても読みやすかったです。

本格的に DDD の道に入るべく、積読中の以下の本もちゃんと読もうと思います。

image.png
エリック・エヴァンスのドメイン駆動設計

image.png
実践ドメイン駆動設計

参考

anfangd
Web Application Developer
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした