本プロジェクトでは、成瀬允宣氏著の「ドメイン駆動設計入門」のLaravel 10を用いた実装例を示します。
当該書籍では、ドメイン駆動設計(Domain Driven Deplopment, DDD)を用いたWEBアプリケーションの開発例が示されており、素晴らしい著作であることは言うまでもありません。
しかしながら実装はC#で示されているため
、例えばPHPフレームワークの代表例であるLaravelなどでDDDによる実装を行いたい場合には、具体的な実装例が分かりづらくなってしまいます。
そこで本プロジェクトでは、「ドメイン駆動設計入門」のLaravelによる実装例を示すことで、Laravel開発者のDDD実装の手助けとなることを目指します。あくまでLaravelの実装例を示すことを目的としていますので、ドメイン駆動設計とは何かについては書籍をお読みください。
これが皆様の開発の手助けとなれば幸いです。ご意見等ございましたら、コメント欄やIssueでお伝えいただければと思います。
目次
Githubにソースコードはアップ済みです。
- Chapter 0: 開発環境のセットアップ
- Chapter 1: ドメイン駆動設計とは -> 書籍をお読みください。
- Chapter 2: システム固有の値を表現する「値オブジェクト」
- Chapter 3: ライフサイクルのあるオブジェクト「エンティティ」 ->ここ
- Chapter 4: 不自然さを解決する「ドメインサービス」
- Chapter 5: データにまつわる処理を分離する「リポジトリ」(TDD)
- Chapter 6: ユースケースを実現する「アプリケーションサービス」(TDD)
- Chapter 7: 柔軟性をもたらす依存関係のコントロール (TDD)
- Chapter 8: ソフトウェアシステムを組み立てる (TDD)
- Chapter 9: 複雑な生成処理を行う「ファクトリ」(TDD)
- Chapter 10: データの整合性を保つ (TDD)
- Chapter 11: アプリケーションを1から組み立てる (TDD)
- Chapter 12: ドメインのルールを守る「集約」(TDD)
- Chapter 13: 複雑な条件を表現する「仕様」(TDD)
- Chapter 14: アーキテクチャ (TDD)
- Chapter 15: ドメイン駆動設計のとびらを開こう (TDD)
Chapter 3: ライフサイクルのあるオブジェクト「エンティティ」
Docker環境で開発
あらかじめ、src
フォルダをVisual Studio Codeで開き、Command PaletteのDev Containers: Reopen in Container
でDocker環境に入っておきます。
Laravelにおけるエンティティの作成方針
書籍によれば、エンティティの性質は以下の通りです。
- 可変である
- 同じ属性であっても区別される
- 同一性により区別される
また、ライフサイクルのあるオブジェクトという説明もあります。
これらの意味で、エンティティは値オブジェクトと区別されます。
Laravelでは、Eloquentが採用されています。
これにより、エンティティクラスでデータベースに関する操作も定義され、面倒なデータベース操作が楽になります。
従って、Eloquentを使用して基本に忠実にエンティティを実装すれば良いです。
Userエンティティの作成
書籍Chapter6で使用されるリスト6.1のUserクラスを作成していきます。
あらかじめ、app/models/User.php
と、database/migrations/*_create_user_table.php
を削除しておきます。
Userクラスとデータベースのtableのスキーマを作成しましょう。
php artisan make:model User --migration
app/Models/User.php
とdatabase/migrations/2023_06_25_033245_create_users_table.php
が作成されたと思います。
後者のファイル名には作成日が反映されているはずです。
User.php
ではUserクラスを記述しますが、*_create_users_table.php
では、データベースのスキーマを定義します。
まずは、User.php
から実装し、その後に*_create_users_table.php
を記述していきましょう。
Userエンティティの実装
Userエンティティの実装は以下のようになります。
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Concerns\HasUuids;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use App\Casts\UserName as UserNameCast;
class User extends Model
{
use HasFactory;
use HasUuids;
protected $fillable = [
'username'
];
protected $casts = [
'username' => UserNameCast::class
];
}
Chapter 2では、UserIdの値オブジェクトを作成しましたが、きちんと独自のIdを作成するのは大変ですので、ここではLaravel built-inのUUIDを使いましょう。
LaravelのEloquentモデルでは、デフォルトではint型のidが使用され、自動でインクリメントされるようになっています。
代わりにstring型のUUIDを使用したい場合には、use HasUuids;
をクラス内に記述することで、UUIDが使用できるようになります。
とても便利ですね。
次に、Chapter 2で作成したUserName値オブジェクトをUserクラスがアトリビュートとして持てるようにします。
Laravelでは、fillable
にModelが持つプロパティを記述していきます。
ここでは、UserエンティティはUserName値オブジェクトを持つので、fillable
にusername
を追加します。
ここでusername
はDBのカラム名に対応します。
username
はUserName値オブジェクトとして渡されて欲しいので、Chapter 2で作成したUserNameクラスをcasts
に渡します。
なお、Laravelではfillable
の他にguarded
もあります。
fillable
に指定したアトリビュートは、後述のcreate
メソッドなどで指定した際にオブジェクトに反映されますが、guarded
に指定したものは無視され、オブジェクトに反映されません。
デフォルトではguareded=['*']
となっており、全てがguarded
に指定されます。
従って、id
はguarded
として指定され、createなどで任意に代入できないようになっているので、そのままにしておきます。
schemaの作成
以下のようにschemaを作成します。
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('users', function (Blueprint $table) {
$table->uuid('id')->primary();
$table->string('username')->unique();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('users');
}
};
前節でidとしてUUIDを使用することにしたので、その部分の指定を行っています。
また、usernameをstring型として保存するカラムも指定しています。
書籍では、username
をuniqueなもの仮定しているので、DBの方でもそのように指定しておきます。
動作確認
ちゃんとしたテストの作成はChapter 5で出てきますので、ここでは簡単に動作確認をしてみます。
まず、DBのschemaに変更を加えたので、それを反映させます。
php artisan migrate:fresh
以下で、usersのtableがどうなっているか確認してみましょう。
php artisan db:table users
users ..............................................................................................
Columns .......................................................................................... 4
Size ....................................................................................... 0.02MiB
Column ........................................................................................ Type
id string ................................................................................... string
username string ............................................................................. string
created_at datetime, nullable ............................................................. datetime
updated_at datetime, nullable ............................................................. datetime
Index ..............................................................................................
PRIMARY id ......................................................................... unique, primary
users_username_unique username .............................................................. unique
上記から、idとusrnameカラムが問題なく作成されていることが分かります。
親切にも、created_atとupdated_atのカラムも作成されています。
これらは一般的なDBの使用で有意義なものですので、そのまま残しておきましょう。
web.php
を以下のように変更し、簡単な動作確認をしてみます。
<?php
use Illuminate\Support\Facades\Route;
use App\ValueObjects\UserName;
use App\Models\User;
/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider and all of them will
| be assigned to the "web" middleware group. Make something great!
|
*/
Route::get('/', function () {
$username = new UserName("ReiRev");
$user = User::create(
[
"username" => $username,
'id' => 'hoge'
]
);
$users = User::all();
foreach ($users as $user) {
dump($user);
}
return view('welcome');
});
php artisan serve
でサーバーを起動し、localhostを開くと、問題なくUserが作成されていることが確認できると思います。
ここで、idも指定してcreateしていますが、実際のidはUUIDになっているはずです。
これは後述した通り、idがguardedになっており、createでは代入できるようになっているからです。
ブラウザをリロードして見ると、以下のようにエラーが表示され、usernameがuniqueなカラムになっていることが確認できます。
SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry 'ReiRev' for key 'users.users_username_unique'
まとめ
本記事では、LaravelのEloquentを使用してエンティティの作成を行いました。
Eloquentを用いることで、少ないコード量で実装を行うことができますので、有効活用していきましょう。