20210725のlaravelに関する記事は11件です。

Make Laravel Permission by yourself

Laravel Guardian Laravel Guardian makes it easy to perform permission https://github.com/cuongnd88/lara-guardian 1-Install cuongnd88/lara-repository using Composer. $ composer require cuongnd88/lara-guardian 2-Add the following service provider in config/app.php <?php // config/app.php return [ // ... 'providers' => [ // ... Cuongnd88\LaraGuardian\LaraGuardianServiceProvider::class, ] // ... ]; 3-Run make:guardian command php artisan vendor:publish --provider="Cuongnd88\LaraQueryKit\LaraQueryKitServiceProvider" php artisan make:guardian App/Traits provides QueryKit trait to empower Laravel models. App/Guardian/Traits has a trait to support Laravel Guardian. App/Http/Middlewares/GuardianMiddleware.php is to check user's permissions. App/Models provides 5 models such as Action, Role, Group, Permission, Role. database/migrations has 5 tables: actions, roles, groups, permissions, roles. Sample Usage Based on route's name, Lara Guardian checks user's permission. You must follow the rule in naming a route: $page.$action Route::group(['middleware' => ['guardian']], function(){ Route::get('/user', function(){ dump("Congratulation. You have the right permission"); })->name('user.read'); }); You have to assign the guard middleware in your app/Http/Kernel.php file. protected $routeMiddleware = [ . . . . 'guardian' => \App\Http\Middleware\GuardianMiddleware::class, ]; There is the relationship of Guardian's models MEMO: the alias of actions, pages tables is used to name a route, therefore you need to enter lower-case letters, dash symbol instead of space. Please add App\Guardian\Traits\HasGuardian.php into the model namespace App\Models; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; use App\Guardian\Traits\HasGuardian; class User extends Authenticatable { use Notifiable; use HasGuardian; } The HasGuardian trait provides: joinGroup($groupId) : user joins a group. public function joinGroup(Request $request) { $user = \App\Models\User::find(10); $user->joinGroup(2); } joinMultiGroups($groups) : user joins multi groups. public function joinManyGroups(Request $request) { $user = \App\Models\User::find(10); $user->joinMultiGroups([ ['group_id' => 1], ['group_id' => 3], ]); } hasPermissions(array $where = [], string $action = null, array $select = []) : show user's permissions. public function getUserPermissions(Request $request) { $user = \App\Models\User::find(10); $user->hasPermissions()->toArray(); } rightAccess(string $page = null, string $action = null) : check user has the permission to access. public function checkUserAccess(Request $request) { $user = \App\Models\User::find(10); if ($user->rightAccess('product', 'create')) { dump('Right Access'); } else { dump('Forbidden'); } } Import/Export data Currently, Lara Guardian imports array data (read files in config\guardian) into database, and exports data in DB to file by using simple command php artisan guardian --action[=ACTION] --model[=MODEL] --action= is import or export value. model= is one or three values actions|pages|groups. For example: php artisan guardian --action=import --model=actions App\Traits\QueryKit.php support these useful methods in importing/exporting guardian data: insertDuplicate(array $data, array $insertKeys, array $updateKeys) is insert new rows or update existed rows. The first argument consists of the values to insert or update, while second argument lists the column(s) that uniquely identify records within the associated table. The third argument is an array of the columns that should be updated if a matching record already exists in the database. $data = [ ['fullname' => 'AAAA', 'email' => 'aaaa@xxxx.com', 'age' => 20, 'address' => 'WWW'], ['fullname' => 'BBBBB', 'email' => 'bbbb@xxxx.com', 'age' => 25, 'address' => 'QQQQ'], ]; \App\Models\User::insertDuplicate( $data, ['fullname', 'email'], ['age', 'address'] ); except(array $columns) is to retrieve a subset of the output data. $exceptable = ['created_at', 'updated_at', 'deleted_at']; $data = app(User::class)->except($exceptable)->get()->toArray()
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel AWSのEC2・RDSでデプロイまでの道(途中)

全体像 1.EC2関連の準備 2.RDS関連の準備 3.EC2のpublicサブネットの中にapache, php, git, composer, その他Laravelの環境構築に必要なものをインストール 4.GitHubで管理の対象外となっている.envファイルの設定 5.php artisan migrateでテーブル作成 一番参考になった記事 https://qiita.com/nakm/items/0bcc6564538a0604b2ce EC2関連の準備 AWSの基本を コマンド
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Laravel] Carbon::now() で保存したデータを Carbon インスタンスとして扱う

DATETIMEカラムに Carbon::now() の値を保存したとします。 $table->dateTime('confirmed_at', $precision = 0)->nullable(); $post->confirmed_at = Carbon::now(); $post->save(); この状態では,データはただの文字列なので Carbon インスタンスとして機能しません。 $post->confirmed_at->format('Y/m/d'); // Call to a member function format() on string アクセサを定義する confirmed_at にアクセスした際に,自動的に Carbonインスタンスに変換する処理を書きます。 Post.php use Carbon\Carbon; /** * @param string $value * @return \Carbon\Carbon */ public function getConfirmedAtAttribute(string $value) { return Carbon::parse($value); } これで $post->confirmed_at が Carbon インスタンスを返すようになります。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【AWS】Laravel + Docker + RDS(MySQL) でデプロイした時のDB関連のエラーとその解決

はじめに AWS(EC2)にデプロイする際、DB関連のエラーで躓いたため記録として残します。 DB関連にフォーカスしているので、それ以外のインストールの手順は省いています。 前提 ・今からmigrateします ・EC2インスタンス作成済み ・sshログインできる 条件 macOS: "11.2.3 Big Sur" php artisan -V # > Laravel Framework 6.20.30 php -v # > PHP 8.0.8 nginx -v # > nginx version: nginx/1.12.2 mysql -h エンドポイント -u マスターユーザー名 -p データベース名 Enter password: # > Server version: 8.0.25 git --version # > git version 2.32.0 1. php artisan migrateでエラー MySQLをインストール ssh sudo yum -y install mysql artisanコマンドが使えるディレクトリに移動してmigrate php artisan migrate すると以下のエラーが... SQLSTATE[HY000] [2002] Connection timed out DBに接続をしようとしたけどできてない様子。 .envを確認して何回か修正したが、改善せず。 .envファイル 入れる値 DB_HOST エンドポイント DB_USERNAME マスターユーザー名 DB_PASSWORD マスターパスワード 解決 AWSのセキュリティグループ→インバウンドルールを確認すると、TypeがMySQLではなかったのが原因でした。 →MySQLに変更することで無事migrateすることができました。 php artisan migrate # migrate成功 Migration table created successfully. 念の為テーブル確認 ターミナル mysql -h エンドポイント -u マスターユーザー名 -p データベースの指定 -p の後は空白を開ける。空白を開けずに入力するとパスワードとなってしまいます。 以下が表示されれば接続成功。 ターミナル Welcome to the MariaDB monitor. Commands end with ; or \g. Your MySQL connection id is 174 Server version: 8.0.25 Source distribution Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. MySQL [(none)]> もし、-pを指定せずに接続しても以下のように指定することで見ることができます。 show databases;とすることで、全てのdatabaseが表示。 ターミナル MySQL [(none)]> show databases; +--------------------+ | Database | +--------------------+ | information_schema | | lantern ← このdetabaseを確認したい | mysql | | performance_schema | | sys | +--------------------+ lanternというdetabaseを見たかったらuseで指定する。 ターミナル MySQL [(none)]> use lantern Reading table information for completion of table and column names You can turn off this feature to get a quicker startup with -A Database changed MySQL [lantern]> ← lanternに変更された 参考 このエラーを解決するにあたって以下のサイトを参考にさせて頂きました。 2. php artisan db:seedでエラー Class Faker\Factory Faker\Factoryのクラスがないよと言われる。 解決 Herokuで同じようなことが起こっている人たちがいましたが、AWSでも同じようにすると解決できました。 結論から言うと、以下のようにfakerphp/fakerをrequire-devからrequireへ移動しました。 composer.json "require": { "php": "^7.2.5|^8.0", "doctrine/dbal": "^3.1", "fideloper/proxy": "^4.4", "intervention/image": "2.5.1", "laravel/framework": "^6.20.26", "laravel/tinker": "^2.5", + "fakerphp/faker": "^1.9.1" }, "require-dev": { "barryvdh/laravel-debugbar": "^3.6", "facade/ignition": "^1.16.15", "laravel/ui": "^1.0", "mockery/mockery": "^1.0", "nunomaduro/collision": "^3.0", "phpunit/phpunit": "^8.5.8|^9.3.3" - "fakerphp/faker": "^1.9.1" }, デプロイの際、パッケージのインストールで以下のように記述しました。 composer install --no-dev --prefer-dist --no-devとすることで、「開発環境のみ必要で本番環境で必要ないパッケージはインストールしない」とできます。 具体的には、phpunitやdebugbarなどは本番環境には必要ないためインストールされないようにしています。 「開発環境のみ」というのはrequire-devの部分です。 上記のコマンドを実行するとrequire-devで書かれているものはインストールされません。 修正後、gitにpushし本番環境でもcomposerをアップデートして反映させます。 composer update この後にもう一度php artisan db:seedをすると無事実行されました。 php artisan db:seed Database seeding completed successfully. 参考 このエラーを解決するにあたって以下のサイトを参考にさせて頂きました。 おわり この他にも、ディレクトリの記述ミスで実行できなかったのもあり、これを機にディレクトリをしっかり意識するようになりました。 初歩的なミスばかりですが、おかげでデプロイの概要が少しずつ理解できてきたかなと感じます! このまま自動デプロイ目指してやっていきます! 参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravelアプリをロリポップへ設置した

laravel6のwebアプリをロリポップ スタンダードプランで動かしたときに結構時間かかったのでメモを残す。 作業環境 Win10 home Laravel 6.20 PHP 7.4 MariaDB 10.6 SSHでロリポップへ接続 ロリポップの管理画面からSSHでの接続情報を入手して入力する。 (Git Bashを使用したが、PowerShellなどでも同じだと思う) ssh (アカウント)@(サーバー) -p(接続ポート) 入力例 ssh abcde-123456789@abc.lolipop.jp -p1234 次にパスワードを聞かれる。 自動生成された30文字くらいの文字列なので間違えないよう慎重に入力する。 (パスワードは入力しても何も表示されないので何文字入力したかもわからない) 正しいパスワードを入力すると下記のように表示される。 [(アカウント)@(サーバー番号) ~]$ 表示例 [abcde-123456789@abc123 ~]$ PHPのパスを通す この時点ではパスが通っておらずphp -vを実行してもPHPのヴァージョンを確認できない。 PHPのパスを通す $ export PATH="$PATH:/usr/local/php/7.4/bin" 上記を実行することでphp -vが通るようになる。 ただ、このままでは毎回パスを通さないといけなくて面倒なので、.bash_profileファイルを作成する。 .bash_profileを作成しviで開く $ vi ~/.bash_profile viコマンドで.bash_profileを開いたら、「i」キーを押して「挿入」モードにする。 .bash_profileへ下記を記入 PATH="$PATH:/usr/local/php/7.4/bin" 上記を入力して「esc」キーを押して:wqで保存する。 これで今後もパスが通った状態となる。 Composerをインストールする 下記コマンドを実行してwebディレクトリへ移動。 webディレクトリへ移動 $ cd web ここに下記を実行してcomposerをインストールする。 composerをインストール $ /usr/local/php/7.4/bin/php -r "eval('?>'.file_get_contents('https://getcomposer.org/installer'));" インストールしたら一応composer -vを実行してcomposerがインストールされているか確認する。 FTP接続してファイルを転送する 今回設置するWebアプリはGitHubなどに上げていないのでFTPで転送する。 Windows10のエクスプローラーを起動して、アドレスバーにロリポップのFTPサーバーアドレスを入力。 (パスワードなどのFTPサーバー情報は、ロリポップの管理画面からユーザー設定 → アカウント情報で入手する。FTPサーバーアドレスはWebDAVの欄に記載されている) WebDAVのアカウントとパスワードを入力して接続する。 任意のディレクトリを作成しそこへ必要なファイルを転送。 .git、node_modules、vendorの3ディレクトリ以外は全ファイルを転送した。 今回は.envファイルも転送したが、データベースの接続情報は開発環境のままなので、エディタでロリポップのデータベース用に内容修正。 composer updateを実行 SSH接続しているGitBashに戻り、composer updateを実行したがエラーで停止してしまった。 composerUpdateを実行 $ composer update 下記のようなエラーが出た。 エラー内容 Loading composer repositories with package information Warning from https://repo.packagist.org: Support for Composer 1 is deprecated and some packages will not be available. You should upgrade to Composer 2. See https://blog.packagist.com/deprecating-composer-1-support/ Updating dependencies (including require-dev) mmap() failed: [12] Cannot allocate memory mmap() failed: [12] Cannot allocate memory PHP Fatal error: Out of memory (allocated 679682048) (tried to allocate 8592000 bytes) in phar:///usr/local/bin/composer/src/Composer/Util/RemoteFilesystem.php on line 462 Fatal error: Out of memory (allocated 679682048) (tried to allocate 8592000 bytes) in phar:///usr/local/bin/composer/src/Composer/Util/RemoteFilesystem.php on line 462 メモリが足らんということらしいので、エラー内容から検索して下記を実行。 composerUpdateでメモリ不足を解消したコマンド $ php -d memory_limit=-1 ../composer.phar install 上記コマンドを実行すると、composer updateが再開し無事終了。 ログイン画面を表示することができた。 参考サイト 今回はいろいろ調べ回りましたが、主に下記2サイト様を参考にさせていただきました。 非常に助かりました。ありがとうございました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel 公式ドキュメントを読めるようにするための 用語集

準備 用語 意味 仮想マシン サーバ仮想化、一台のコンピュータで複数のコンピュータをウド加須技術のこと ex) WindowsのPC上でMacを動かせたりする 環境変数 OSの変数、OSが動いている時に使える変数 キャッシュ ウェブページの情報を一時的に保存する仕組み(スマホやPC内にある)、その一時的なデータ プレースホルダー とりあえず入れておく仮の情報 ex) フォームの名前を書く欄に名前と書いてある スーバーグローバル変数 $_POST, $_GETなど オートローディング(auto loading) 自動的にファイルを読み込む仕組み api.phpファイル アクセス回数制限を提供するミドルウェアでステートレス セッションステート セッション状態、アプリケーションの状態 ステートレス 状態がない、すべての情報をはじめから投げてあげる必要がある ログファイル ログ(履歴、記録)が書かれたファイル 認可 Authorization=その人が権限を持っているかを確認すること、認証とは違う サービスプロバイダ サービスを提供するもの、サービスをコンテナと結合、イベントの登録、もしくはアプリケーションへやってくるリクエストを処理するために必要な用意をする、タスクを実行するなど、アプリケーションの事前準備を行う ★シリアライゼーション 複数の並列データを直列化して送信すること ★プロファイリング プロファイラ コンピュータプログラムが実行される様子を監視、記録し、動作順や実行時間などを集計、解析するプログラム TLS SSL(Secure Socket Layer)の進化版 SSL インターネット上でやり取りされるデータの解読、改ざんを防止するプロトコル(通信のルール) 構成の概念 ライフサイクル 用語 意味 カーネル(kernel) 機能の中核 ブラックボックス 使い方は分かるが中身の構造は分かっていない状態、利用者が内部構造や動作原理を知らなくても支障がない設計の装置やソフトウェア app.php Laravelアプリケーションのインスタンスを作成し、機能を実行する準備をする コンソール(Console) 入力、出力を備えた装置のこと、コンピュータと意思疎通する時に使う HTTPカーネルのhandleメソッド HTTPリクエストをコントロールするメソッド HTTPリクエスト Webサーバーへデータの送受信を要求すること、データを取得するGETメソッドとデータの保存、更新を行うPOSTメソッドなどがある。 サービスプロバイダ(Laravelアプリケーション全体の起動処理における、初めの心臓部) 用語 意味 bootstrap 準備、利用可能な状態になるまで自動的に実行される処理 Registerメソッド サービスコンテナの家次男う処理のみ行う Bootメソッド イベントリスナーやサービスプロバイダを登録 イベントリスナー イベントに対応して発動される処理(関数)のこと ロード 読み込むこと=使えるように準備すること ★遅延プロバイダ 遅延ロード Webサイトに表示される画像を一度に読み込まず、必要に応じて必要な分だけ読み込む ★サービスコンテナ結合 登録すること サービスコンテナについて分かりやすく解説している記事 https://laraweb.net/practice/2029/ ファサード=サービスコンテナに登録したクラスへ静的(::)なインターフェースを提供 return View::make('profile'); ファサード return view('profile');    ヘルパ関数 のようにファサードとヘルパ関数は同じ働きをすることがある。 Laravelの契約 契約vsファサード サードパーティパッケージを構築する場合はファサードの代わりに契約を使うのが好ましい |用語|意味| |--|--| |★キューする|| |★ドライバ|| |メーラー|電子メールの作成、閲覧、送受信を行うためのソフト| |パッケージベンダー|パッケージを提供している企業| |★契約の実装|依存を解決するクラスのコンストラクターでタイプヒントを指定する?? ex) protected $radias| |★イベントハンドラ|| |★イベントリスナー|| 基礎 用語 意味 フロントエンド 用語 意味 セキュリティ より深く知る データベース Eloquent ORM テスト 用語 意味 公式パッケージ 言語ファイル
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel 公式ドキュメントを読めるようにするための 用語集(途中)

準備 用語 意味 仮想マシン サーバ仮想化、一台のコンピュータで複数のコンピュータをウド加須技術のこと ex) WindowsのPC上でMacを動かせたりする 環境変数 OSの変数、OSが動いている時に使える変数 キャッシュ ウェブページの情報を一時的に保存する仕組み(スマホやPC内にある)、その一時的なデータ プレースホルダー とりあえず入れておく仮の情報 ex) フォームの名前を書く欄に名前と書いてある スーバーグローバル変数 $_POST, $_GETなど オートローディング(auto loading) 自動的にファイルを読み込む仕組み api.phpファイル アクセス回数制限を提供するミドルウェアでステートレス セッションステート セッション状態、アプリケーションの状態 ステートレス 状態がない、すべての情報をはじめから投げてあげる必要がある ログファイル ログ(履歴、記録)が書かれたファイル 認可 Authorization=その人が権限を持っているかを確認すること、認証とは違う サービスプロバイダ サービスを提供するもの、サービスをコンテナと結合、イベントの登録、もしくはアプリケーションへやってくるリクエストを処理するために必要な用意をする、タスクを実行するなど、アプリケーションの事前準備を行う ★シリアライゼーション 複数の並列データを直列化して送信すること ★プロファイリング プロファイラ コンピュータプログラムが実行される様子を監視、記録し、動作順や実行時間などを集計、解析するプログラム TLS SSL(Secure Socket Layer)の進化版 SSL インターネット上でやり取りされるデータの解読、改ざんを防止するプロトコル(通信のルール) 構成の概念 ライフサイクル 用語 意味 カーネル(kernel) 機能の中核 ブラックボックス 使い方は分かるが中身の構造は分かっていない状態、利用者が内部構造や動作原理を知らなくても支障がない設計の装置やソフトウェア app.php Laravelアプリケーションのインスタンスを作成し、機能を実行する準備をする コンソール(Console) 入力、出力を備えた装置のこと、コンピュータと意思疎通する時に使う HTTPカーネルのhandleメソッド HTTPリクエストをコントロールするメソッド HTTPリクエスト Webサーバーへデータの送受信を要求すること、データを取得するGETメソッドとデータの保存、更新を行うPOSTメソッドなどがある。 サービスプロバイダ(Laravelアプリケーション全体の起動処理における、初めの心臓部) 用語 意味 bootstrap 準備、利用可能な状態になるまで自動的に実行される処理 Registerメソッド サービスコンテナの家次男う処理のみ行う Bootメソッド イベントリスナーやサービスプロバイダを登録 イベントリスナー イベントに対応して発動される処理(関数)のこと ロード 読み込むこと=使えるように準備すること ★遅延プロバイダ 遅延ロード Webサイトに表示される画像を一度に読み込まず、必要に応じて必要な分だけ読み込む ★サービスコンテナ結合 登録すること サービスコンテナについて分かりやすく解説している記事 https://laraweb.net/practice/2029/ ファサード=サービスコンテナに登録したクラスへ静的(::)なインターフェースを提供 return View::make('profile'); ファサード return view('profile');    ヘルパ関数 のようにファサードとヘルパ関数は同じ働きをすることがある。 Laravelの契約 契約vsファサード サードパーティパッケージを構築する場合はファサードの代わりに契約を使うのが好ましい 用語 意味 ★キューする ★ドライバ メーラー 電子メールの作成、閲覧、送受信を行うためのソフト パッケージベンダー パッケージを提供している企業 ★契約の実装 依存を解決するクラスのコンストラクターでタイプヒントを指定する?? ex) protected $radias ★イベントハンドラ ★イベントリスナー 基礎 ルーティング 用語 意味 デリミタ セパレータ=データを区切るもの ex) , ユースケース ユーザとシステムのやり取りを表現したもの ワイルドカード * 曖昧 モデル結合ルート ルートへ直接モデルインスタンスを自動的に注入する 依存解決 依存注入=外部からインスタンスを渡す=タイプヒントして引数に入れる タイプヒント 引数の型を指定すること ex) オブジェクトなのか配列なのかコールバックなのか ★パターンフィルタ エンコード データを他の形式へ変換すること 文字エンコード コンピュータが使う0 or 1 の組み合わせに変えること ★ルート制限 特定の時間間隔内に実行できる呼び出しの最大数、スクレイピングの制限 ★PUT リソースの作成、置換 ★PATCH リソースの部分置換 ミドルウェア 用語 意味 パラメーター parameter=引数 CSRF保護 用語 意味 Axios PromiseベースのHTTP ClientライブラリでGETやPOSTのHTTPリクエストを使ってサーバからデータの取得、更新を行う webフック webアプリケーションでイベントが実行された際、外部サービスにHTTPで通知する仕組み コントローラ 用語 意味 シングルアクションコントローラー 一つのアクションだけを持つコントローラーのことで、 --invokable 命名 名前を付ける リクエスト 用語 意味 リクエストURLの取得 $request->path() トリム 切り取る事、必要な部分だけを取り出して不要な部分を排除すること ノーマライゼーション ノーマライズ=正規化=データの重複をなくす クエリストリング クエリ文字列=URLパラメーター=サーバーに情報を送るためにURLの末尾に付け足す文字列(変数)のこと、?の後に パラメーター=値 を付ける 送信データ ペイロード+ヘッダ+メタデータ ペイロード 送信データのうち、実際に意図されたメッセージの部分、オーバーヘッドと実際のデータを区別するために使われることが多い ヘッダ ソースコードに登場するすべてのデータや命令を宣言するために存在する文書ファイル メタデータ 本体であるデータに関する付加情報が記載されたデータのこと オーバーヘッド 付加的な処理 プロキシ 代理人、代わりにやる ★クッキー セッション webブラウザが閉じるまで一時的にサーバー側にデータを保存することが出来る フラッシュデータ 次のリクエストまでのセッションンに保存されるデータのこと ★クッキーをキューする トラフィック インターネットやLANなどのコンピュータなどの通信回線において、一定時間内にネットワーク上で転送されるデータ量のこと フォワーディング 外部から受信したデータを特定の宛先へ自動的に転送する機能のこと レスポンス 用語 意味 ビジネスロジック データの処理手順 ビュー 用語 意味 レンダ 情報を加工して表示すること ビューコンポーザ ビューがレンダーされるときに呼び出されるコールバックかクラスメソッドのこと、サービスプロバイダの中に組み込む、config/app.phpのproviders配列に追加を忘れずに ビュークリエイター ビューコンポーザとほぼ同じ働きをするが、ビューがレンダーされるまで待つのではなく、インスタンス化されるとすぐに実行される セッション 用語 意味 全アプリケーションンの初期起動 bootメソッド、ステートレスのため エラー処理 用語 意味 エンドユーザー 商品やサービスを実際に使う人のこと エラーの詳細表示 config/app.phpのdebugをtrue 例外ハンドラ reportメソッドやrenderメソッド reportメソッド 外部サービスへ送信するために使う renderメソッド 与えられた例外をブラウザーに送り返すHTTPレスポンスへ変換する instanceof あるPHPの変数が特定のクラスのオブジェクトのインスタンスであるかを調べる abortヘルパ 即座に例外を発生させ、その例外は例外ハンドラによりレンダーされる ログ 用語 意味 Monolog ログハンドラをサポートしているライブラリ、config/logging.phpが設定ファイル ★ログチャンネル 記録の通り道?? フォーマッター ハードディスクなどの記録装置を初期化するためのソフトウェア ドライバ デバイスドライバ、デバイスを動かすために必要なソフト チャネル チャンネル=データの通り道 フロントエンド Bladeテンプレート=Laravelのテンプレートエンジン、PHPへコンパイルされ、変更があるまでキャッシュする 用語 意味 コンパイル @yield yieldは産出する サブビュー @includeディレクティブを使い、ビューの中から簡単に他のBladeビューを取り込む オーバーヘッド コンピュータが何かをやった時に発生する付加、間接的な処理のこと ディレクティブ @から始まる ex)@section, @extends, @parent, @json, @alert, @component, @slot等 @parent コンテンツの上書きではなく追加したい時に使うとレイアウトの内容に置き換わる コンポーネント 使う時には@コンポーネント名、つまりディレクティブ名を自分で決めることが出来る スロット コンポーネントに入っているもので、複数定義できる @jsonディレクティブ Vueコンポーネントやdata属性を生成するためにも便利に使える レンダリングエンジン レンダリング(元の情報を整形して表示する)を行うソフトウェア Webブラウザ ホームページを見る時に使うソフト 構造化プログラミング 順次、選択、反復などの構造を組み合わせてプログラムを組み立てていく手法 制御構文 構造化プログラミングのための構文 メンタルモデル 規則 スタック 子ビューで必要なJavaScriptを指定する時に使い、@pushで定義して、@stackで に定義する フォーマットする ->format()= bootメソッド booting=初期起動 多言語化=ソフトウェアが様々な文字や言語を切り替えて使用できるように設定あるいは修理すること 用語 意味 ローカリゼーション 地域化する ex)時刻や通貨単位や日付の表記を修正 ローケル locale=言語や国・地域の設定のこと ex)タイムゾーン=標準時など サブディレクトリ ディレクトリ(フォルダ)の中に置いてあるディレクトリのこと、下位のディレクトリ 翻訳文字列の取得 言語ファイルから行を取得=ヘルパ関数を使用 ex)echo__('messages.welcome'); スカフォールド=scafflold=足場=アプリケーションの足場(ひな形を自動生成して簡単に作ることが出来る機能) BootstrapとVueのスカフォールドは、Composerを使いインストールするlaravel/uiパッケージに用意してある 用語 意味 プリプロセッサ ある中心的な処理を行うプログラムに対して、その前処理を行うプログラムのこと Laravel Mix Laravelに同梱されているビルドツール、現在主流のwebpackがベースになっている ビルドツール ソースコードから実行可能なアプリケーション作成するため、依存関係を解決し、バイナリーコードにコンパイルし、バイナリーコードをパッケージ化すること 依存パッケージ パッケージを使うために必要な別のパッケージ package.jsonファイル 必要なパッケージが記述されたファイル モジュール 名前空間が解決された(同じフォルダの中で変数の名前が被ってもOK)ファイル的な public/jsディレクトリ コンパイル済みのJavaScriptが出力される Vueコンポーネントを変更したら必ずすること npm run devコマンド or npm run watchで監視することで自動的にコンパイル ★macroable アセットコンパイル Laravel Mixは多くの一般的なCSSとJavaScriptのプリプロセッサを使用し、Laravelアプリケーションのために、構成過程をwebpackですらすらと定義できるAPIを提供している。 用語 意味 コンパイル 従来のcssやjavaScriptに変換することでBootstrapやVue.jsなどを動かせるようになるnpm run devで出来る Laravel Mix アセットパイプライン(アセットコンパイルの設定)を記述する設定ファイルであり、実行するAPI、 アセットパイプライン アセットコンパイルの設定 webpack.mix.js webpack設定ファイル(webpack.config.js)のラッパー、コンパイルやバンドルするファイルを記述していく ラッパー ある開発環境向けに提供されたプログラムなどを別の環境で利用できるようにするもの レイヤー レベル ex)低レイヤ、高レイヤ シームレス 複数の要素の繋ぎ目が気にならなずに利用できる状態 ネイティブコード コンピュータのCPU(MPU/マイクロプロセッサ)が理解できる形式で記述されたコンピュータプログラム サフィックス suffix=接尾辞 リテラル 定数と基本は同じだが、定数は入れ物でリテラルは中身を指す、ソースコードなどの中に特定のデータ型の値を直に記載したもの ex) 定数Aにリテラル値"test"をセットする 疎結合 依存度が低いかな? 蜜結合 依存度が高いかな?? セキュリティ 認証 Authentification=laravel/uiパッケージで行う、ルート定義で->middleware('auth');で付ける or コントローラーのコンストラクターで$this->middleware('auth'); 用語 意味 ガード 各リクエストごとにどのようにユーザーを認証するかを定義 プロバイダ ストレージ SSD データベーススキーマ ダッシュボード 認証のディレクティブ 認証済みユーザー・ユーザーIDへのアクセス コントローラメソッドでタイプヒントしたクラスは 自動的にインスタンスが依存注入される 現在のユーザーが認証されているか確認 if(Auth::check){} アクセス修飾子 public, protected, private protected public トレイト Remember me 論理値 基本認証 API認証=API Authentication 用語 意味 ゲート ルート的なものでモデルやリソースとは関連しないアクションに対して適応される ex)管理者画面にアクセスできるユーザーを制限させる ポリシー コントローラ的なもので特定のモデルやリソースに対するアクションを認可する時に使う 特定のモデルに対するアクション 作成、更新、削除、閲覧等モデルごとに個別のPolicyファイルを作る ファサード ::が付く ex) Auth:: Gate:: インターセプト intercept=妨害する、傍受する(通信内容を同意なしに受け取る) マップ 個々の構成要素に対して、別の集合の要素を規則に従って機械的に対応付けたり割り当てたりすること 認可=Authorization=ゲートとポリシーがある 用語 意味 ゲート ルート的なものでモデルやリソースとは関連しないアクションに対して適応される ex) 管理者画面にアクセスできるユーザーを制限させる ポリシー コントローラ的なもので特定のモデルやリソースに対するアクションを認可する時に使用する 特定のモデルに対するアクション 作成、更新、削除、閲覧等、モデルごとに個別のPolicyファイルを作る ファサード ::が付く ex) Auth::Gate:: インターセプト intercept=妨害する、傍受する(通信内容を同意なしに受け取る) マップ 個々の構成要素に対して、別の集合の要素を規則に従って機械的に対応つけたり割り当てたりすること 暗号化=encryption=データを見ても分からないように変換する 用語 意味 エンクリプタ encryptor=暗号化するプログラム シリアライズ データをそのまま保存したり送受信することが出来るように変換すること デシリアライズ シリアライズされたデータを元の状態や形式に復元する作業のこと ハッシュ 用語 意味 Bcryptハッシュ ストレッチ回数が調整できる、登録と認証で自動的に使用されている Argon2ハッシュ パラメータのチューニングが可能であれば使う チューニング 調整=いい感じになるようにする 平文 cleartext=クリアテキスト=暗号化されていない状態のデータ、そのままで内容を扱うことが出来るデータのこと パスワードリセット ForgotPasswordControllerとResetPasswordControllerで実装する より深く知る データベース 用語 意味 トランザクション データベースに対して行われる一つ以上の更新処理 COMMIT トランザクションを確定させる処理 ROLLBACK トランザクションを取り消す処理 schema 図形、概要 環境変数 パソコンの動作や表示に関する機能を調整すること リスナ 何らかのきっかけに応じて起動されるよう設定されたサブルーチンや関数、メソッドなどのこと サブルーチン vs 関数 関数は値を返すのに対してサブルーチンは値を返さない、他の処理から呼び出される関数がサブルーチン デバッグ バグの原因を探して取り除く作業 リッスン 通信機能を持つソフトウェアが外部からのアクセスに備えて待機すること ex) WebサーバはHTTPで通信を行うため、標準ではTCPの80番ポートをリッスンし、Webブラウザからアクセスされるのを待っている クエリビルダ=クエリを作成し実行するために使用する、DB::table('')最終的に->get('')する必要がある 用語 意味 クエリ query=問い合わせる=データに問い合わせる SQLインジェクション攻撃 SQLが注入される=アプリケーションが想定しないSWL分を実行させる攻撃 PDO PHP Data Object=データベースへの接続方法 スコープ定義演算子 :: 抽象化レイヤ PHPとデータベースの間に入って一つの命令で、それぞれのデータベースにいい感じにアクセスしてくれる層 ホワイトリスト 注意が不要である対象を列挙したリストのこと インターフェイス 接点、二つのものが接続・接触する箇所や、両者の間で情報が信号などのやり取りするための手順や規約を定めたもの chunk ひとまとまりのデータの塊 DB::rawメソッド エスケープなしのSQLであるため、SQLインジェクションに注意 ★サブクエリ エイリアス 別名 ダンプ dump=コンピュータの記録装置(メモリやスト―レージなど)に記録された内容をまとめて表示、印刷、記録することや、そのようにして映し出された内容 ペジネーション=クエリビルダ(DB::)or Eloquent ORMの後に付ける 用語 意味 シングルページ 次と前のリンクだけ ウェインドウ 画面上に出てくる小さい画面 レンダー もととなる情報を整理して表示すること レンダリング ディスプレイ上に表示できるようにすること、画像や映像、音声などを生成すること マイグレーション=基本的にスキーマビルダと一緒に使い、データベーススキーマを作成するために使用複数形で命名 用語 意味 php artisan:reset 全部のマイグレーションをロールバック php artisan:refresh 全部のマイグレーションをロールバックし、マイグレーションを実行する=down()を実行してからup()を実行する php artisan:fresh データベースから全テーブルをドロップ(削除)、次にmigrateコマンドを実行する=down()は実行されていない integer 整数 シーディング=テストデータをデータベースに設定する方法書いた後はcomposer dump-autoload、大量のモデルデータを入れたい時はファクトリを使う 用語 意味 レコード 行 カラム 列 Redis 用語 意味 クラスタ cluster=集合、群れ=ネットワークに接続した複数のコンピューターを一つのコンピュータシステムに結合し、処理や運用を効率化するシステム ノード redisインスタンスそのもの プーリング 資源を蓄える処理や操作 パイプライン 複数のプログラムンの入出力を繋ぐ仕組み ストリーミング ダウンロードと違って端末にデータを転送しないためストレージ容量を使わない、インターネットを使うためWi-Fiが必要 リッスン 通信機能を持つソフトウェアが外部からのアクセスに備えて期待すること ex) WebサーバはHTTPで通信を行うため、標準ではTCPの80番ポートをリッスンし、Webブラウザなどからアクセスされるのを待っている Eloquent ORMデータベース操作の実装、クエリビルダとしても動作する 用語 意味 デフォルトでEloqauentはcreated_atとupdated_atカラムをCarbonインスタンスへ変更する Eloquentの準備 用語 意味 chunkメソッド 大きな結果を操作する時のメモリを節約できる レイジーコレクション 必要な時に必要なだけ処理することでPHPのメモリ使用量を減らす 複数代入の脆弱性 リクエストに応じて予期しないHTTPパラメータが送られたときに起き、パラメータはデータベースのカラムを予期しないように変更してしまう。 ホワイトリスト 使用しても良いと判断した安全なアプリケーションやプログラムを定義したリスト ブラックリスト 受け取らないものを定義したリスト $fillableプロパティ 複数代入する属性の配列のホワイトリストとして動作 $guardedプロパティ 複数代入したくない属性の配列のブラックリストとして動作 プロパティ property=属性 ソフトデリート 論理削除=ゴミ箱に入っている状態で復元可能 物理削除 論理削除の反対 オブザーバ デザインパターンの一つ リレーション 用語 意味 ポリモーフィック ★N+1問題 Eagerロード N+1クエリ問題の解決策、withメソッドを使う 遅延ロード リレーションデータへ最初にアクセスするまで実際にはロードされない toggle 同じ操作や処理によって2つの状態が交互に切り替わるような仕組み コレクション 用語 意味 ★イテレーター 繰り返し処理の抽象化?? ミューテタ 用語 意味 アクセサ 値を取得するフォーマットを定義する、get○○Attribute(キャメルケースで書く)、使う時はスネークケースで書く ミューテタ 値を代入する際のフォーマットを定義する、get○○Attribute APIリソース 用語 意味 トランスレーション データの意味や内容を変えずに別のデータ形式に変換すること メタ情報 文字コード、title, description, keyword, viewport(スマホやタブレットなどのモバイル端末でWebページを適切なサイズで表示させるために必要な値などデータを表す属性や関連する情報を記述したデータのこと)、つまりデータ本体ではなくその説明のようなもの リソースの本質 特定のモデルを被ア列に変換する必要がある ラップ もともとの機能を持っている ★シリアライズ=直列化=複数の要素を一列に並べる操作や処理のことJSONでAPIを作成する場合にモデルとリレーションを配列やJSONに変換する->toArray();や->toJson(); ★テスト テストの準備 用語 意味 Unitディレクトリ 一つのメソッドをテスト Featureディレクトリ 幅広い範囲のテスト アサーション 条件を記述して実行時にチェックする仕組みアサーションを挿入するという phpunit.xml テストコードの場所を認識させる ||| |||
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel 公式ドキュメント 用語集

準備 用語 意味 仮想マシン サーバ仮想化、一台のコンピュータで複数のコンピュータをウド加須技術のこと ex) WindowsのPC上でMacを動かせたりする 環境変数 OSの変数、OSが動いている時に使える変数 キャッシュ ウェブページの情報を一時的に保存する仕組み(スマホやPC内にある)、その一時的なデータ プレースホルダー とりあえず入れておく仮の情報 ex) フォームの名前を書く欄に名前と書いてある スーバーグローバル変数 $_POST, $_GETなど オートローディング(auto loading) 自動的にファイルを読み込む仕組み api.phpファイル アクセス回数制限を提供するミドルウェアでステートレス セッションステート セッション状態、アプリケーションの状態 ステートレス 状態がない、すべての情報をはじめから投げてあげる必要がある ログファイル ログ(履歴、記録)が書かれたファイル 認可 Authorization=その人が権限を持っているかを確認すること、認証とは違う サービスプロバイダ サービスを提供するもの、サービスをコンテナと結合、イベントの登録、もしくはアプリケーションへやってくるリクエストを処理するために必要な用意をする、タスクを実行するなど、アプリケーションの事前準備を行う ★シリアライゼーション 複数の並列データを直列化して送信すること ★プロファイリング プロファイラ コンピュータプログラムが実行される様子を監視、記録し、動作順や実行時間などを集計、解析するプログラム TLS SSL(Secure Socket Layer)の進化版 SSL インターネット上でやり取りされるデータの解読、改ざんを防止するプロトコル(通信のルール) 構成の概念 ライフサイクル 用語 意味 カーネル(kernel) 機能の中核 ブラックボックス 使い方は分かるが中身の構造は分かっていない状態、利用者が内部構造や動作原理を知らなくても支障がない設計の装置やソフトウェア app.php Laravelアプリケーションのインスタンスを作成し、機能を実行する準備をする コンソール(Console) 入力、出力を備えた装置のこと、コンピュータと意思疎通する時に使う HTTPカーネルのhandleメソッド HTTPリクエストをコントロールするメソッド HTTPリクエスト Webサーバーへデータの送受信を要求すること、データを取得するGETメソッドとデータの保存、更新を行うPOSTメソッドなどがある。 サービスプロバイダ(Laravelアプリケーション全体の起動処理における、初めの心臓部) 用語 意味 bootstrap 準備、利用可能な状態になるまで自動的に実行される処理 Registerメソッド サービスコンテナの家次男う処理のみ行う Bootメソッド イベントリスナーやサービスプロバイダを登録 イベントリスナー イベントに対応して発動される処理(関数)のこと ロード 読み込むこと=使えるように準備すること ★遅延プロバイダ 遅延ロード Webサイトに表示される画像を一度に読み込まず、必要に応じて必要な分だけ読み込む ★サービスコンテナ結合 登録すること サービスコンテナについて解説した記事 https://laraweb.net/practice/2029/ ファサード=サービスコンテナに登録したクラスへ静的(::)なインターフェースを提供 return View::make('profile'); ファサード return view('profile');    ヘルパ関数 のようにファサードとヘルパ関数は同じ働きをすることがある。 基礎 フロントエンド セキュリティ より深く知る データベース Eloquent ORM テスト 用語 意味 公式パッケージ 言語ファイル
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

MAMP環境でのLaravelとMySQLの接続エラー

今まではSQLiteを使用していましたが、今回はMySQLを使用することにしました。 しかし、開始早々接続エラーで苦戦したので、こちらに解決方法を記載しておきます。 環境 MAMP OS:macOS Laravel:8.51.0 PHP:7.4.12 MySQL:5.7.32 エラー画面 phpmyadminでDBを作り、いざ接続しようとしたらこのエラーが出ました。 「Connection refused」ということで、うまく接続できていないようです。 どうやら.envファイルとdatabase.phpファイルの記述が間違えているようでした。 MAMPの情報を確認する MAMPを起動した際に、こちらのページが表示されます。 こちらのページの下の方に「MySQL」という項目があるのでクリックします。 するとこちらの情報が出てきます。 このport、Username、Password、Socketがこのあと必要になるので、メモ等に控えておきます。 .envファイルの修正 修正前の記述がこちらです。 .env DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_USERNAME=root DB_PASSWORD= この中のDB_PORT、DB_USERNAME、DB_PASSWORDを先ほどメモしたものに変更します。 そして新たにDB_SOCKETの記述を追加します。 DB_DATABASEは自分で作成したデータベース名を入れます。 修正したものがこちらです。 .env DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=8889 DB_SOCKET=/Applications/MAMP/tmp/mysql/mysql.sock DB_DATABASE=データベース名 DB_USERNAME=root DB_PASSWORD=root database.phpの修正 次にdatabase.phpを修正します。 database.phpはconfigファイル内にあります。 その中でmysqlの記載部分を探します。 修正前がこちら。 .database.php 'port' => env('DB_PORT', '3306'), 'database' => env('DB_DATABASE', 'forge'), 'username' => env('DB_USERNAME', 'forge'), 'password' => env('DB_PASSWORD', ''), 第2引数部分を、メモした通りに変更します。 databaseは先ほど同様、自分で作成したデータベース名を入れます。 .database.php 'port' => env('DB_PORT', '8889'), 'database' => env('DB_DATABASE', 'データベース名'), 'username' => env('DB_USERNAME', 'root'), 'password' => env('DB_PASSWORD', 'root'), 以上を変更して、再起動をしてから再接続したところ、無事DB接続することができました。 ちなみにポート番号を変更するには? 今回DBの接続するにあたり、ポートが初期値のままだったことに気づきました。 MAMPのデフォルトのポートはApacheが8888、MySQLが8889です。 変更するにはMAMPのPreferencesのこちらの画面で変更することができます。 一般的なポートの設定をするにはSetWeb&MySQL Ports toの「80 & 3306」を押します。 するとApacheが80、MySQLが3306に変わります。 そうすることで、ローカル環境で接続する際に、URLが「http://localhost:8888/」から「http://localhost/」に変わります。 ちなみにデフォルトの番号に戻したいときは「MAMP default」で元に戻ります。 ポート番号を変更した際は、上記の.envとdatabase.phpの記載の修正を忘れずに。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravelで同じ実装をドメイン駆動設計(DDD)とMVCで比較してみた(フロントはReact)

概要 Laravelで同じ実装をドメイン駆動設計(DDD)とMVCで比較してみました。フロントはReactで実装しています。設計思想はドメイン駆動設計(以下DDD)、アーキテクチャはクリーンアーキテクチャを採用しました。LaravelのWebアプリケーションをDDDやクリーンアーキテクチャで構築すると、MVCで構築するのと比べて実装量やファイル数が増えるのでは?また、処理速度も遅くなるのでは?という懸念を抱いておりました。ただ、それは実際に試してみないとわからないと思い、同じ機能をDDD、MVCで実装し、比較する事にしました。やるなら実践的なプロジェクトが望ましいし、イメージし易いと考えて、題材はECのデモサイトにしました。構築パターンは、DDD、軽量DDD、MVCの3パターンです。DDD、軽量DDDの違いは、DDDはクリーンアーキテクチャに忠実に実装したのに比べて、軽量DDDはPresenter、ViewModel、UseCaseDataを共通クラスに実装して実装量の削減をしています。 結果は下にまとめますが、やはり懸念していた通りパフォーマンスはMVC > 軽量DDD > DDDという結果で、ファイル数やステップ数もDDD > 軽量DDD > MVCと予測通りでした。ただ、パフォーマンスは思っていた程DDDが悪い結果では無かったです。 ちなみに便宜上「軽量DDD」という文言を使っておりますが、 本検証で述べている「軽量DDD」は、参考書籍「エリック・エヴァンスのドメイン駆動設計」でアンチパターンとされているDDDの技術要素だけ取り入れた「軽量DDD」と同意ではありません。(私の理解度が低くDDDを実践しきれていない可能性は否定できませんが・・・)本検証の「軽量DDD」は、ドメイン層については「DDD」の基本構造と全く同じであるためです(ドメインモデル→配列へのデータ変換用の実装は追加していますが、モデリングは全く同じ)。本検証における「軽量DDD」で実装を削っている箇所は、クリーンアーキテクチャのレイヤーをまたぐ際に、ドメインモデル→UseCaseモデル→ViewModelと2回データ変換する処理を、ドメインモデル→配列と1回のデータ変換に削減した箇所です。あとPresenter、InputData、OutputDataも共通クラス化して実装量削減しておりますが、これらもクリーンアーキテクチャのクラス群です。その為、ドメインモデルを削っている訳ではありません。 なお、フロントはbladeを使っているものの、ほぼ全てReactで実装しました。SPAにも出来たのですが、本プロジェクトの趣旨ではないのでSPAにはあえてしていません。Reactより使い慣れてるVue.jsの方が実装しやすかったのですが、いい機会だったのでついでにReactもかじってみました。material uiを使うことで、スマホアプリのようなリッチなUI表現に仕上がりました。かつ、レスポンシブにも対応しています。 ソースコード laravel-ddd-sample 実装するに当たって、nrslib/StrictLaraClean、shin1x1/laravel-ddd-samplのお二方のgithubのソースを大いに参考にさせて頂きました。とても勉強になりました。ドメインモデルは最初ただのDTOで、定義する事が目的化するだけだし、Eloquentで取得したデータをそのまま使えばこの実装は不要では?と思ってましたが大間違いでした。参考書籍「エリック・エヴァンスのドメイン駆動設計」によると、ドメイン層のモデリングと実装こそがDDDの真骨頂で、最も重要との事です。今考えたら「ドメイン駆動設計」なので、そりゃそうですよね・・・ドメイン駆動設計からドメインモデルを省いたら、「????駆動設計」になって駆動させる動力が無くなってしまう・・・(たこの入っていないたこ焼きみたいな・・・)。ドメインモデルはただのプロパティとそのget、setしかないDTOとは似て否なるもので、ドメインモデルにドメイン領域のビジネスルールや仕様等を凝集して実装していくと理解しました。DTOを作っているみたいに単調で面倒ですが、頑張って実装しました。実際やってみると、ドメインモデルが保持するプロパティと、それにまつわるメソッドが1つのクラスに凝集され、プロパティと操作の関連がひと目で分かる造りになりました。オブジェクト指向でプログラミングしているという実感を感じました(まだ不十分でしたらすみません・・・)。特にCart、Order、ProductGroupはプロパティと操作が一体となっており、とても制御・実装・拡張し易かったです。一方、仕様やルールの実装が無く、プロパティとそのget、setしかないValueaObjectがあるので、これらは共通クラスで実装削減してもいいかも知れません。 なお、参考書籍「エリック・エヴァンスのドメイン駆動設計」でレイヤ化アーキテクチャ(クリーンアーキテクチャの前身)を推奨している箇所があるのですが、DDDを実践するのにレイヤ化アーキテクチャは必須ではなく、あくまでもレイヤ化アーキテクチャはドメイン層を隔離する手段であり、ドメイン層を隔離出来るなら他のアプローチでもいいと書かれてました。どうしてもレイヤ化アーキテクチャのような技術的アプローチに着目して、それを用いることが目的化しがちですが、ドメイン層の隔離とそのモデリング、実装こそが最も重要との事です。ただ、DDDを実践するに当たってドメイン層の隔離は必須との事なので、何かしらの手段でドメイン層の隔離が必要です。本検証では、そのアーキテクチャにクリーンアーキテクチャを採用しています。 デモサイト 以下にデモサイトを構築しました。ちなみに注文確定して頂いても問題はありませんが、商品は届きませんのでその旨ご了承ください・・・注文確認メールを送信する実装はしているものの、デモサイトではサーバーエラーが出て届かないようにしています。 DDD http://www.take14.shop/ddd/product/list 軽量DDD http://www.take14.shop/lightddd/product/list MVC http://www.take14.shop/mvc/product/list 基本認証をかけているので、ご覧になりたい方は以下のID、パスワードをご利用下さい ID パスワード take14 UP6-hL$Z.8ghBn?c ミドルウェア ミドルウェア バージョン Apache 2.4.34 PHP 8.0.7 PostgreSQL 13.3 npm 6.14.12 フレームワーク・ライブラリ フレームワーク・ライブラリ バージョン Laravel 8.48.1 React 17.0.2 material-ui 4.11.4 typescript 4.3.4 sass 1.35.1 ディレクトリ構成 packages/Common 共通モジュール packages/DDDEcSample DDDのモジュール packages/LightDDDEcSample 軽量DDDのモジュール packages/MvcSample MVCのモジュール resources/ts ReactのTypeScriptコード resources/sass Reactで使用するsass tests/Unit/DDDEcSample DDDのUnitTest tests/Unit/LightDDDEcSample 軽量DDDのUnitTest tests/Unit/MvcSample MVCのUnitTest DDDModel UmbrelloというUMLツールで設計したクラス図、シーケンス図を保存した「Umbrelloモデルファイル.xmi」、それらの画像出力した画像ファイル プロジェクトの環境構築について マイグレーションファイル、シーダーファイルを作っているので、それを用いれば環境構築してデバッグ可能です。 以下コマンドで環境構築出来ます。事前に.env.exampleをコピーして.envを作成し、APP_KEY、DB_〜、MAIL_〜を適切に設定する必要があります。 composer install npm insall npm run dev php artisan migrate:fresh php artisan db:seed 機能概要 商品一覧 商品詳細 カート 注文 設計 Umbrelloというソフトでクリーンアーキテクチャのベースとなるクラス図、シーケンス図を作成し、PHPでエクスポートしたソースコードをベースに実装を進めました。本当にIF部分だけのスケルトン生成なので、わざわざクラス図から生成する必要も無いのですが、検証の意味でトライしてみました。クリーンアーキテクチャのクラス図と見比べつつクラスを作成出来たので、やって良かったと考えています。「DDDModel」というフォルダにその結果を収めております。 クラス図 正確にはクリーンアーキテクチャではクラス図左下のViewクラスが画面出力を行うのですが、Laravelのコントローラーはコントローラーのメソッドの戻り値でIlluminate\View\Viewを返却する作りとなっているため、そこは妥協してViewクラスはIlluminate\View\Viewを返却し、それをコントローラーのメソッドまで戻り値で引き継いでコントローラーで返却する造りとしています。やろうと思えばViewクラスでIlluminate\View\Viewのインスタンスをシングルトンのオブジェクトに保持させて、Laravelフレームワークを改造してそのIlluminate\View\Viewのインスタンスを使ってレンダリングさせることも出来ますが、そうしたトリッキーな事をするとフレームワークにどういう影響が出るかがわからないので、そこはフレームワークの習わしに従う選択をしています。参考文献「エリック・エヴァンスのドメイン駆動設計」にもフレームワークとは争うなと書いていたので、妥協可能なところは妥協するのがいい選択だと考えます。 シーケンス図 集約 CartはCartItemを保持。CartItemは、Productを保持するといった集約にしました。Orderも同じで、OrderItemを保持し、OrderItemはProductを保持するという集約にしています。当初CartItemはCartDetailという名称にしていましたが、DDDの要のユビキタス言語を実践するのであれば不自然な言葉使いだとはたと気付いて、CartItemにしました。それでも不自然かも知れませんが、DDD初学者の為ご容赦下さい・・・手続き型の実装に馴染んでる私の身としてはCartDetailという名前がしっくりしていたのですが、自然言語としては不自然な呼び名だなと思いました。 参考文献、書籍によると、集約に対する操作は原則集約ルートから行うとあったため、それに気をつけつつ実装しました(参考書籍「ドメイン駆動設計入門 ボトムアップでわかる! ドメイン駆動設計の基本」より)。(もし間違っていたら申し訳ありません) ドメインサービス カートアイテムを追加する処理を実装する際、CartReposutoryで取得した結果に、CartItemを追加する必要がありますが、その際Productを取得して設定する必要があります。ProductReposutoryを使ってProductを取得しますが、複数のRepositoryを操作する必要があり、それをUseCase層に実装するのはぎこちなさを感じました。カートに商品を追加する。という一つのUseCaseを実現するのに、UseCaseがProductIdからProductRepositoryを使ってProductを取り出し、それをCartオブジェクトに追加する。という複数のReoositoryを使った一連の操作を行うのですが、そうした一連の操作もドメイン知識なのではと思った為です。そこで、CartServiceというドメインサービスを作りました。今こうして書いていて、UseCaseでProductRepositoryからProductを取り出し、Cartに対してそれを追加するメソッドを実装してコールする形でもよかったなと思い直しており、ドメインサービスを作るまでも無かったのかも知れません、、、ただ、あくまでも検証なので、実装パターンを試すという意味ではこれはこれで良かったと考えています。なお、参考書籍ではドメインサービスは極力使わないで、ドメインモデルで表現・実装するべきである旨が強調して書かれていました。その気になれば、全部ドメインサービスに書けてしまう為です。それによってドメイン貧血症という、仕様やドメイン知識が何も実装されていないドメインモデルが出来てしまうそうです(参考書籍「ドメイン駆動設計入門 ボトムアップでわかる! ドメイン駆動設計の基本」より)。何をドメインモデルで実装し、どういった場合にドメインサービスを用いるかは、慎重な検討と熟練した判断や経験が必要になりそうです。 バリデーションチェック ドメインモデルのバリデーションチェックは、LaravelのValidatorを使用しても良かったのですが、あえてフレームワークには依存せず独自の実装を選択しました。それはそれで良かったのですが、注文のバリデーションで少し苦戦しました。ドメインモデルのコンストラクタでバリデーションし、エラーの場合はExceptionをthrowする実装としたのですが、例えば氏名、名前が必須入力なのに両方未入力でバリデーションエラーとなった場合、両方のエラーを検知して画面表示したいのですが、try catch 1つだと1個のエラーしか検知して画面表示出来ません。ユーザーにとってはエラーを一つずつしか解消出来ず、これではコンバージョン低下に繋がります。ドメインモデルをnewする毎にtry catchすればいいですが、ソースコードが冗長になりスマートではありません。そこで、ModelFactoryというドメインサービスを作りました。そのファクトリーにバリデーションしたいモデルクラス名を配列で渡したら、配列のモデルクラスのnewとバリデーションチェックを全部やってエラーがあればthrowまでやってくれるクラスにしました。エラーが無ければ、インスタント化したモデルクラスをModelFactoryから取得も出来ます。これでドメインモデルのバリデーションチェックと、そのインスタンス化をスマートに実装出来ました。 自動テスト 全機能に対するUnitTestを実装しました。TDDも勉強していた為、実装の過程で実践しました。何度かリファクタリングをしたのですが、その度にグリーン(自動テストオールOK)だったのが、レッド(自動テストオールNG)になりました。そこからグリーンにするまで修正したら、画面テストがキチンと動作する事を確認出来ました。UnitTestで非常に信頼性の高い検証を行える事が、この事から立証出来たと思います。キチンとUnitTestを実装したら、機能追加やリファクタリングを自信を持って行えると思います。 ステップ数 種別 ファイル数 ステップ数 DDD 152 3,444 軽量DDD 102 2,298 MVC 23 725 上記の通り、懸念していたとおりDDDはファイル数、ステップ数共に圧倒的にMVCと比較して多くなる結果となりました。一方でDDDは1クラスの凝集度は高く、ほとんど50ステップ未満なので、実装工数は数ほどのインパクトがあるわけではありません。DDDでは決まった形式でクラスを実装するケースも多く、ほとんどコピペやロジックの組み立ての時間がかからず作業的に実装するケースもあったので、ステップ数の割には工数はかかっていなかったりします。また、クラスの凝集度が高い分、どこに何が記載されているか、このクラスの役割は何かがひと目で把握しやすいメリットもあります。ステップ数、ファイル数が多い=悪ではないという事を言いたいのですが、それぞれメリット、デメリットがあります。詳細は以下の「メリット・デメリット」でまとめたいと思います。 パフォーマンス app/Http/Middleware/PerformanceLog.php 上記ファイルにコントローラー実行前後にログを出力し、処理時間、処理に使用したメモリを計測できるようにしました。その計測結果を以下にまとめます。 商品一覧のように大量のレコードをDBから取得して表示する箇所は、MVC > 軽量DDD > DDDという結果でした。これは、DDDの場合取得したレコードをドメインモデルに変換するコストがかかるためです。 一方意外だったのは、その他はほとんど軽量DDDに軍配が上がって軽量DDD > MVC > DDDという結果になった点です。詳しくは分析しておりませんが、MVCは手続き型で実装しているので、処理効率が悪い部分があるのでしょうか。もしくは単なる誤差という可能性もあります。いずれにしても気にするほどの差ではありません。商品一覧だけ100ms単位で差が出ているので、大量データ取得・表示のパフォーマンスに影響が出ると考えるのが良さそうです。なお、注文確定処理が2000ms前後と時間がかかっているのは、注文確定のお知らせメール送信処理が含まれるためです。 結果一覧 機能 DDD時間 DDDメモリ 軽量DDD時間 軽量DDDメモリ MVC時間 MVCメモリ 商品一覧画面表示 289ms 6299KB 178ms 5049KB 85ms 2735KB 商品詳細画面表示 63ms 650KB 18ms 600KB 23ms 627KB カート追加API 153ms 419KB 19ms 418KB 51ms 839KB カート画面表示 36ms 468KB 10ms 382KB 21ms 640KB 注文画面表示 20ms 468KB 9ms 382KB 20ms 640KB 注文バリデーションAPI 34ms 422KB 31ms 506KB 58ms 814KB 注文確定処理 2552ms 1334KB 2517ms 1061KB 1949ms 1376KB エビデンス DDD [2021-07-23 23:00:19] local.DEBUG: Packages\DDDEcSample\InterfaceAdapters\Controllers\ProductController@list メモリ 6298.953125KB [2021-07-23 23:00:19] local.DEBUG: Packages\DDDEcSample\InterfaceAdapters\Controllers\ProductController@list 288.98096084595ミリ秒 [2021-07-23 23:07:51] local.DEBUG: Packages\DDDEcSample\InterfaceAdapters\Controllers\ProductController@detail メモリ 649.6484375KB [2021-07-23 23:07:51] local.DEBUG: Packages\DDDEcSample\InterfaceAdapters\Controllers\ProductController@detail 62.504053115845ミリ秒 [2021-07-23 23:08:28] local.DEBUG: Packages\DDDEcSample\InterfaceAdapters\Controllers\Api\CartController@add メモリ 418.6953125KB [2021-07-23 23:08:28] local.DEBUG: Packages\DDDEcSample\InterfaceAdapters\Controllers\Api\CartController@add 153.38611602783ミリ秒 [2021-07-23 23:08:28] local.DEBUG: Packages\DDDEcSample\InterfaceAdapters\Controllers\CartController@list メモリ 468.3046875KB [2021-07-23 23:08:28] local.DEBUG: Packages\DDDEcSample\InterfaceAdapters\Controllers\CartController@list 36.375999450684ミリ秒 [2021-07-23 23:09:27] local.DEBUG: Packages\DDDEcSample\InterfaceAdapters\Controllers\OrderController@form メモリ 468.3046875KB [2021-07-23 23:09:27] local.DEBUG: Packages\DDDEcSample\InterfaceAdapters\Controllers\OrderController@form 19.876003265381ミリ秒 [2021-07-23 23:09:37] local.DEBUG: Packages\DDDEcSample\InterfaceAdapters\Controllers\Api\OrderController@check メモリ 421.9609375KB [2021-07-23 23:09:37] local.DEBUG: Packages\DDDEcSample\InterfaceAdapters\Controllers\Api\OrderController@check 33.753156661987ミリ秒 [2021-07-23 23:09:44] local.DEBUG: Packages\DDDEcSample\InterfaceAdapters\Controllers\Api\OrderController@save メモリ 1334.2734375KB [2021-07-23 23:09:44] local.DEBUG: Packages\DDDEcSample\InterfaceAdapters\Controllers\Api\OrderController@save 2552.1411895752ミリ秒 軽量DDD [2021-07-23 23:11:33] local.DEBUG: Packages\LightDDDEcSample\InterfaceAdapters\Controllers\ProductController@list メモリ 5049.25KB [2021-07-23 23:11:33] local.DEBUG: Packages\LightDDDEcSample\InterfaceAdapters\Controllers\ProductController@list 177.73199081421ミリ秒 [2021-07-23 23:11:36] local.DEBUG: Packages\LightDDDEcSample\InterfaceAdapters\Controllers\ProductController@detail メモリ 600.1328125KB [2021-07-23 23:11:36] local.DEBUG: Packages\LightDDDEcSample\InterfaceAdapters\Controllers\ProductController@detail 18.326997756958ミリ秒 [2021-07-23 23:11:38] local.DEBUG: Packages\LightDDDEcSample\InterfaceAdapters\Controllers\Api\CartController@add メモリ 417.765625KB [2021-07-23 23:11:38] local.DEBUG: Packages\LightDDDEcSample\InterfaceAdapters\Controllers\Api\CartController@add 19.247770309448ミリ秒 [2021-07-23 23:11:38] local.DEBUG: Packages\LightDDDEcSample\InterfaceAdapters\Controllers\CartController@list メモリ 382.2265625KB [2021-07-23 23:11:38] local.DEBUG: Packages\LightDDDEcSample\InterfaceAdapters\Controllers\CartController@list 10.499000549316ミリ秒 [2021-07-23 23:11:40] local.DEBUG: Packages\LightDDDEcSample\InterfaceAdapters\Controllers\OrderController@form メモリ 382.2265625KB [2021-07-23 23:11:40] local.DEBUG: Packages\LightDDDEcSample\InterfaceAdapters\Controllers\OrderController@form 9.2220306396484ミリ秒 [2021-07-23 23:11:49] local.DEBUG: Packages\LightDDDEcSample\InterfaceAdapters\Controllers\Api\OrderController@check メモリ 506.1171875KB [2021-07-23 23:11:49] local.DEBUG: Packages\LightDDDEcSample\InterfaceAdapters\Controllers\Api\OrderController@check 30.539989471436ミリ秒 [2021-07-23 23:11:53] local.DEBUG: Packages\LightDDDEcSample\InterfaceAdapters\Controllers\Api\OrderController@save メモリ 1061.2578125KB [2021-07-23 23:11:53] local.DEBUG: Packages\LightDDDEcSample\InterfaceAdapters\Controllers\Api\OrderController@save 2517.3659324646ミリ秒 MVC [2021-07-23 23:10:28] local.DEBUG: Packages\MvcSample\App\Http\Controllers\ProductController@list メモリ 2735.3828125KB [2021-07-23 23:10:28] local.DEBUG: Packages\MvcSample\App\Http\Controllers\ProductController@list 84.88392829895ミリ秒 [2021-07-23 23:10:31] local.DEBUG: Packages\MvcSample\App\Http\Controllers\ProductController@detail メモリ 627.4375KB [2021-07-23 23:10:31] local.DEBUG: Packages\MvcSample\App\Http\Controllers\ProductController@detail 22.572994232178ミリ秒 [2021-07-23 23:10:34] local.DEBUG: Packages\MvcSample\App\Http\Controllers\Api\CartController@add メモリ 838.9921875KB [2021-07-23 23:10:34] local.DEBUG: Packages\MvcSample\App\Http\Controllers\Api\CartController@add 50.947904586792ミリ秒 [2021-07-23 23:10:34] local.DEBUG: Packages\MvcSample\App\Http\Controllers\CartController@list メモリ 639.7109375KB [2021-07-23 23:10:34] local.DEBUG: Packages\MvcSample\App\Http\Controllers\CartController@list 20.750045776367ミリ秒 [2021-07-23 23:10:36] local.DEBUG: Packages\MvcSample\App\Http\Controllers\OrderController@form メモリ 639.7109375KB [2021-07-23 23:10:36] local.DEBUG: Packages\MvcSample\App\Http\Controllers\OrderController@form 20.180940628052ミリ秒 [2021-07-23 23:10:45] local.DEBUG: Packages\MvcSample\App\Http\Controllers\Api\OrderController@check メモリ 813.6875KB [2021-07-23 23:10:45] local.DEBUG: Packages\MvcSample\App\Http\Controllers\Api\OrderController@check 57.819128036499ミリ秒 [2021-07-23 23:10:53] local.DEBUG: Packages\MvcSample\App\Http\Controllers\Api\OrderController@save メモリ 1375.6796875KB [2021-07-23 23:10:53] local.DEBUG: Packages\MvcSample\App\Http\Controllers\Api\OrderController@save 1949.3370056152ミリ秒 メリット・デメリット DDD、軽量DDD、MVCのメリット・デメリットを比較した結果を以下にまとめます。DDDの記述ボリュームが多くなりましたが、DDDよりは、軽量DDDを採用するほうがいいと考えます。軽量DDDはDDDのメリットを全て継承しつつ、DDDのファイル数、ステップ数が多くなるデメリットをある程度軽減出来ている為です。MVCはファイル数、ステップ数が圧倒的に少なく、実装工数を考えるとMVC一択と判断しがちですが、メンテナンス性を考えると一口にはそうは言いきれません。また、DDDや軽量DDDはファイル数、ステップ数が多いですが、その数字=工数とも限りません。殆どが1クラス50ステップ未満の凝集度の高いクラスの為、実装効率が高く実行工数は数ほどのインパクトは無いと考えます。また、DDDでは決まった形式でクラスを実装するケースも多く、ほとんどコピペやロジックの組み立ての時間がかからず作業的に実装するケースもあったので、ステップ数の割には工数はかかっていなかったりします。なお、カートのセッション取得箇所を「SessionRepository::get」でgrepすると、DDD、軽量DDDは共に1箇所ですが、MVCは4箇所でした。設計次第で改善出来るかもしれませんが、こうした実装の重複はMVCや手続き型の実装では避けづらいです。処理の重複や類似の仕様が様々な箇所に分散して実装されていくと、変更しづらく影響範囲も読みづらいプロジェクトになっていきます。DDDが目指すのは変更や拡張をしやすい「しなやかな設計(参考文献「エリック・エヴァンスのドメイン駆動設計」より引用)」ですが、MVCや手続き型の実装ではそうした理想を目指すのが困難だと考えます。運用期間が短かったり、手早く短時間で仕上げたいWEBアプリケーションであれば、MVCでさっと作るのが向いているかもしれませんが、年単位で運用をしながら機能拡張・追加を繰り返して成熟させていきたいWEBアプリケーションやWEBサービスの場合は、DDDか軽量DDDで構築したほうが良さそうだと考えます。 DDD メリット クラスの凝集度が高くなり、1クラス当りのステップ数は少なくなって可読性、メンテナンス性が高くなる どこに何を実装するかの指針が決まっており、クラスの責務がはっきり分かる形で実装出来るため、ソースコードの重複を避けやすい。(完全には防げないが、重複が発生したらそれを検知してリファクタリングしやすい。例えば手続き型の実装だと関数単位で機能が実装されていって関数が増えていき、その中に重複処理があっても発見しにくくなる。) 依存関係逆転の原則を用いることで、疎結合な実装となる 疎結合な為UnitTestを実装しやすい 疎結合な為、DBの変更、フレームワークの変更、HTML・API・コンソール・ViewComposer等のUIやコントローラーの変更をしても、ユースケース層、ドメイン層は影響を受けずそのまま使える。 デメリット ファイル数、ステップ数が多くなる ドメインモデル→UseCaseData→ViewModelとレイヤーをまたぐ毎にデータの変換が必要となり、パフォーマンスが低下する 軽量DDD メリット 高凝集、疎結合、UnitTestの実装のしやすさはDDDのメリットを継承 DDDに比べてファイル数、実装量が削減される。 ドメインモデル→ViewModelを、ドメインモデルの配列化で実現し、DDDと比べて変換回数が少なくなってパフォーマンス改善した デメリット DDDに比べてステップ数が2/3になるが、それでもMVCの3倍以上のステップ数となる DDDと比べてパフォーマンス改善したものの、大量データ取得時のパフォーマンスはMVCと比較すると悪い MVC メリット DDD、軽量DDDと比べてステップ数、ファイル数が圧倒的に少ない(DDDの1/4以下、軽量DDDの1/3以下) DDDと比べてデータの変換が無い為、処理速度が早い傾向にある(特に大量データ取得時) デメリット コントローラー、モデルクラスにビジネスロジックが集中し、メンテナンス性が悪い神クラスが生まれやすくなる 関数単位で機能が実装されていって関数が増えてき、関数同士の関連がわかりづらくなったり、類似処理の重複を避けづらくなってくる フレームワークと密結合な実装となるため、UnitTestが実装し辛く、実装するには工夫が必要となる Eloquentのパフォーマンス 本記事の趣旨ではありませんが、Eloquentのパフォーマンスについて検証結果を述べます。商品一覧のデータ取得は最初Eloquentのモデルクラスで取得しておりましたが、DB::tableで取得する方式へ変更しました。理由はパフォーマンスの悪さです。どうもEloquentは大量データを取得する際のパフォーマンスが悪い模様です。理由はEloquentのインスタンスを作成するコストが嵩むからという見解と、whereInで大量にパラメータ指定する際のデータバインドのコストが原因という見解があるようです(参考サイト「laracasts」の記事より)。今回のクエリはwhereInを使ってなくても遅かったため、Eloquentのインスタンス化のコストが主な遅延要因ではないかと考えます。本記事のデモサイトでは、DB::tableでこの問題に対処しました。それでもほとんど使い方はEloquentのモデルクラスと同等なので支障はありませんでした。なお、この問題が本記事のパフォーマンス検証に影響しないように、DDD、軽量DDD、MVC全て、商品一覧だけはdb::tableを使用するコードでパフォーマンス計測しております。 React 仕事でVue.jsの採用経験があるためVue.jsのほうが慣れておりますが、いい機会なのでReactに挑戦しました。仕事でVue.jsかReactのどちらを使用するか検討した際、Vue.jsのほうがデザイナーにも受け入れてもらいやすそうだと感じてVue.jsを採用しました。あと、私自身JSXアレルギーがあって、Vue.jsのようにテンプレート記法の方が既存の技術(HTML、CSS、js、jQueryを用いた実装)に近い感覚で実装出来て、わかりやすく実装しやすいと思っていました。 ところが今回Reactに挑戦してみて、JSXアレルギーは誤解だったと考えを改めました。jsコードとHTMLをシームレスに書ける事の便利さに驚きました。JSXはプログラミングしている感覚でHTMLを記述出来ます。なんと言っても自由度の高さです。Vue.jsはHTMLはtemplate、jsロジックはscript、CSSはstyleタグ内に記述するように決まっています。そして、jsロジックの実装ルールも定まっていて、それに沿って実装します。Reactはというと・・・一定のルールはあるものの、かなり自由に実装できます。ただこれは副作用もあります。自由ということは、きちんとコーディング規約を作ったり実装方針や設計を定めないと、メンテナンス性の悪いコードも作れてしまいます。MVCはビューとロジックを別で実装する事で、高いメンテナンス性を実現し、プログラマーとデザイナーが分業しやすくなりました。ReactはHTMLとjsを一緒に書けるのは便利な反面、キチンと設計、実装しないとビューとロジックが混雑してデザイナー、プログラマーの分業に支障が出たり、可読性、メンテナンス性の低下を招くと思います。一長一短ですね・・・ あと、元々懸念していたデザイナーにはハードルが高い点は当たっています。デザイナーよりはプログラマー寄りのアーキテクチャーで、プログラマーは幸せになれますが、デザイナーは敷居が高く感じるかもしれません。ただ、ReactやVue.jsの登場で、フロントエンドで出来ることは大幅に増えました。一昔前はHTML、CSSで静的コンテンツを制作し、jsやjQueryで動的コンテンツをちょこっと色づけという感じでしたが、ReactやVue.jsでがっつりフロンドで動的コンテンツを実装し、サーバーサイドはREST APIだけ提供し、データの永続化とその復元といったバックエンド処理に徹するという構成も今では珍しくありません。昨今GraphQLという選択肢もあり、フロントエンドに実装の比重が移りつつあります(GraphQLを採用すると、今回検証したDDDも必要無くなるかも・・・)。SEO対策の点ではSPAは不安もあるのでその点十分考慮する必要はありますが、SSRという解決策も用意されています。その事を考えたら、デザイナーもガリガリとプログラミングに参画するのが時代の流れかも知れません。そうした時代の流れに乗るのであれば、Reactも十分選択肢になり得ると考えます。 今回の趣旨では無いものの、Reactに挑戦してみてReactが広く受け入れられている理由を肌で感じました。少し実装ルールを勉強したら、あとは直感的にどんどん実装していけました。ただ、初めてのReactなのでコード品質は悪いのでそこはご容赦下さい。本格的に導入するには、フォルダ設計、実装方針の策定、CSSのツールの選定等々、色々と事前に勉強・検討する必要がありそうです。 参考文献・記事・ソースコード エリック・エヴァンスのドメイン駆動設計 実践ドメイン駆動設計 (Object Oriented SELECTION) テスト駆動開発 ドメイン駆動設計入門 ボトムアップでわかる! ドメイン駆動設計の基本 ドメイン駆動設計 モデリング/実装ガイド nrslib/StrictLaraClean DDDパターンを活用した Laravelアプリケーション開発 shin1x1/laravel-ddd-sampl laracasts
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravelで同じ実装をドメイン駆動設計(ddd)とMVCで比較してみた(フロントはReact)

概要 Laravelで同じ実装をドメイン駆動設計(ddd)とMVCで比較してみました。フロントはReactで実装しています。設計思想はドメイン駆動設計(以下ddd)、アーキテクチャはクリーンアーキテクチャを採用しました。LaravelのWebアプリケーションをdddやクリーンアーキテクチャで構築すると、MVCで構築するのと比べて実装量やファイル数が増えるのでは?また、処理速度も遅くなるのでは?という懸念を抱いておりました。ただ、それは実際に試してみないとわからないと思い、同じ機能をddd、MVCで実装し、比較する事にしました。やるなら実践的なプロジェクトが望ましいし、イメージし易いと考えて、題材はECのデモサイトにしました。構築パターンは、ddd、軽量ddd、MVCの3パターンです。ddd、軽量dddの違いは、dddはクリーンアーキテクチャに忠実に実装したのに比べて、軽量dddはプレゼンター、ViewModel、UseCaseDataを共通クラスに実装して実装量の削減をしています。 結果は下にまとめますが、やはり懸念していた通りパフォーマンスはMVC > 軽量ddd > dddという結果で、ファイル数やステップ数もddd > 軽量ddd > MVCと予測通りでした。ただ、パフォーマンスは思っていた程dddが悪い結果では無かったです。 なお、フロントはbladeを使っているものの、ほぼ全てReactで実装しました。SPAにも出来たのですが、本プロジェクトの趣旨ではないのでSPAにはあえてしていません。Reactより使い慣れてるVue.jsの方が実装しやすかったのですが、いい機会だったのでついでにReactもかじってみました。material uiを使うことで、スマホアプリのようなリッチなUI表現に仕上がりました。かつ、レスポンシブにも対応しています。 ソースコード laravel-ddd-sample 実装するに当たって、nrslib/StrictLaraClean、shin1x1/laravel-ddd-samplのお二方のgithubのソースを大いに参考にさせて頂きました。とても勉強になりました。ドメインモデルは最初ただのDTOで、定義する事が目的化するだけだし、Eloquentで取得したデータをそのまま使えばこの実装は不要では?と思ってましたが大間違いでした。参考書籍「エリック・エヴァンスのドメイン駆動設計」によると、ドメイン層のモデリングと実装こそがdddの真骨頂で、最も重要との事です。今考えたら「ドメイン駆動設計」なので、そりゃそうですよね・・・ドメイン駆動設計からドメインモデルを省いたら、「駆動設計」になって駆動させる動力が無くなってしまう・・・(たこの入っていないたこ焼きみたいな・・・)。ドメインモデルはただのプロパティとそのget、setしかないDTOとは似て否なるもので、ドメインモデルにドメイン領域のビジネスルールや仕様等を凝集して実装していくと理解しました。DTOを作っているみたいに単調で面倒ですが、頑張って実装しました。実際やってみると、ドメインモデルが保持するデータと、それにまつわるメソッドが1つのクラスに凝集され、データと操作の関連がひと目で分かる造りになりました。オブジェクト指向でプログラミングしているという実感を感じました(まだ不十分でしたらすみません・・・)。ただ、仕様をやルールの実装が無く、プロパティとそのget、setしかないValueaObjectがあるので、これらは共通クラスで実装削減してもいいかも知れません。 なお、参考書籍「エリック・エヴァンスのドメイン駆動設計」でレイヤ化アーキテクチャ(クリーンアーキテクチャの前身)を推奨している箇所があるのですが、dddを実践するのにレイヤ化アーキテクチャは必須ではなく、あくまでもレイヤ化アーキテクチャはドメイン層を隔離する手段であり、ドメイン層を隔離出来るなら他のアプローチでもいいと書かれてました。どうしてもレイヤ化アーキテクチャのような技術的アプローチに着目して、それを用いることが目的化しがちですが、ドメイン層の隔離とそのモデリング、実装こそが最も重要との事です。ただ、dddを実践するに当たってドメイン層の隔離は必須との事なので、何かしらの手段でドメイン層の隔離が必要です。本検証では、そのアーキテクチャにクリーンアーキテクチャを採用しています。 デモサイト 以下にデモサイトを構築しました。 - ddd http://www.take14.shop/ddd/product/list - 軽量ddd http://www.take14.shop/lightddd/product/list - MVC http://www.take14.shop/mvc/product/list 基本認証をかけているので、ご覧になりたい方は以下のID、パスワードをご利用下さい ID パスワード take14 UP6-hL$Z.8ghBn?c ミドルウェア ミドルウェア バージョン Apache 2.4.34 PHP 8.0.7 PostgreSQL 13.3 npm 6.14.12 フレームワーク・ライブラリ フレームワーク・ライブラリ バージョン Laravel 8.48.1 React 17.0.2 material-ui 4.11.4 typescript 4.3.4 sass 1.35.1 ディレクトリ構成 packages/Common 共通モジュール packages/DDDEcSample dddのモジュール packages/LightDDDEcSample 軽量dddのモジュール packages/MvcSample MVCのモジュール resources/ts ReactのTypeScriptコード resources/sass Reactで使用するsass tests/Unit/DDDEcSample dddのUnitTest tests/Unit/LightDDDEcSample 軽量dddのUnitTest tests/Unit/MvcSample MVCのUnitTest DDDModel UmbrelloというUMLツールで設計したクラス図、シーケンス図を保存した「Umbrelloモデルファイル.xmi」、それらの画像出力した画像ファイル プロジェクトの環境構築について マイグレーションファイル、シーダーファイルを作っているので、それを用いれば環境構築してデバッグ可能です。 以下コマンドで環境構築出来ます。事前に.env.exampleをコピーして.envを作成し、APP_KEY、DB_〜、MAIL_〜を適切に設定する必要があります。 composer install npm insall npm run dev php artisan migrate:fresh php artisan db:seed 機能概要 商品一覧 商品詳細 カート 注文 設計 Umbrelloというソフトでクリーンアーキテクチャのベースとなるクラス図、シーケンス図を作成し、PHPでエクスポートしたソースコードをベースに実装を進めました。本当にIF部分だけのスケルトン生成なので、わざわざクラス図から生成する必要も無いのですが、検証の意味でトライしてみました。クリーンアーキテクチャのクラス図と見比べつつクラスを作成出来たので、やって良かったと考えています。「DDDModel」というフォルダにその結果を収めております。 クラス図 正確にはクリーンアーキテクチャではクラス図左下のViewクラスが画面出力を行うのですが、Laravelのコントローラーはコントローラーのメソッドの戻り値でviewを返却する作りとなっているため、そこは妥協してViewクラスはviewを返却し、それをコントローラーのメソッドまで戻り値で引き継いでコントローラーで返却する造りとしています。やろうと思えばViewクラスでviewを保持させて、Laravelフレームワークを改造してそのviewを使ってレンダリングさせることも出来ますが、そうしたトリッキーな事をするとフレームワークにどういう影響が出るかがわからないので、そこはフレームワークの習わしに従う選択をしています。参考文献「エリック・エヴァンスのドメイン駆動設計」にもフレームワークとは争うなと書いていたので、妥協可能なところは妥協するのがいい選択だと考えます。 シーケンス図 集約 CartはCartItemを保持。CartItemは、Productを保持するといった集約にしました。Orderも同じで、OrderItemを保持し、OrderItemはProductを保持するという集約にしています。当初CartItemはCartDetailという名称にしていましたが、dddの要のユビキタス言語を実践するのであれば不自然な言葉使いだとはたと気付いて、CartItemにしました。それでも不自然かも知れませんが、ddd初学者の為ご容赦下さい・・・手続き型の実装に馴染んでる私の身としてはCartDetailという名前がしっくりしていたのですが、自然言語としては不自然な呼び名だなと思いました。 参考文献、書籍によると、集約に対する操作は原則集約ルートから行うとあったため、それに気をつけつつ実装しました(参考書籍「ドメイン駆動設計入門 ボトムアップでわかる! ドメイン駆動設計の基本」より)。(もし間違っていたら申し訳ありません) ドメインサービス カートアイテムを追加する処理を実装する際、CartReposutoryで取得した結果に、CartItemを追加する必要がありますが、その際Productを取得して設定する必要があります。ProductReposutoryを使ってProductを取得しますが、複数のRepositoryを操作する必要があり、それをUseCase層に実装するのはぎこちなさを感じました。カートに商品を追加する。という一つのUseCaseを実現するのに、UseCaseがProductIdからProductRepositoryを使ってProductを取り出し、それをCartオブジェクトに追加する。という複数のReoositoryを使った一連の操作を行うのですが、そうした一連の操作もドメイン知識なのではと思った為です。そこで、CartServiceというドメインサービスを作りました。今こうして書いていて、UseCaseでProductRepositoryからProductを取り出し、Cartに対してそれを追加するメソッドを実装してコールする形でもよかったなと思い直しており、ドメインサービスを作るまでも無かったのかも知れません、、、ただ、あくまでも検証なので、実装パターンを試すという意味ではこれはこれで良かったと考えています。なお、参考書籍ではドメインサービスは極力使わないで、ドメインモデルで表現・実装するべきである旨が強調して書かれていました。その気になれば、全部ドメインサービスに書けてしまう為です。それによってドメイン貧血症という、仕様やドメイン知識が何も実装されていないドメインモデルが出来てしまうそうです(参考書籍「ドメイン駆動設計入門 ボトムアップでわかる! ドメイン駆動設計の基本」より)。何をドメインモデルで実装し、どういった場合にドメインサービスを用いるかは、慎重な検討と熟練した判断や経験が必要になりそうです。 バリデーションチェック ドメインモデルのバリデーションチェックは、LaravelのValidatorを使用しても良かったのですが、あえてフレームワークには依存せず独自の実装を選択しました。それはそれで良かったのですが、注文のバリデーションで少し苦戦しました。ドメインモデルのコンストラクタでバリデーションし、エラーの場合はExceptionをthrowする実装としたのですが、例えば氏名、名前が必須入力なのに両方未入力でバリデーションエラーとなった場合、両方のエラーを検知して画面表示したいのですが、try catch一つだと1個のエラーしか検知して画面表示出来ません。ユーザーにとってはエラーを一つずつしか解消出来ず、これではコンバージョン低下に繋がります。モデル毎にtry catchすればいいですが、ソースコードが冗長になりスマートではありません。そこで、ModelFactoryというドメインサービスを作りました。そのファクトリーにバリデーションしたいモデルクラスを配列で渡したら、配列のモデルクラスのバリデーションを全部やってエラーがあればthrowまでやってくれるクラスにしました。エラーが無ければ、インスタント化したモデルクラスをModelFactoryから取得も出来ます。これでドメインモデルのバリデーションチェックと、そのインスタンス化をスマートに実装出来ました。 自動テスト 全機能に対するUnitTestを実装しました。TDDも勉強していた為、実装の過程で実践しました。何度かリファクタリングをしたのですが、その度にグリーン(自動テストオールOK)だったのが、レッド(自動テストオールNG)になりました。そこからグリーンにするまで修正したら、画面テストがキチンと動作する事を確認出来ました。UnitTestで非常に信頼性の高い検証を行える事が、この事から立証出来たと思います。キチンとUnitTestを実装したら、機能追加やリファクタリングを自信を持って行えると思います。 ステップ数 種別 ファイル数 ステップ数 ddd 152 3,444 軽量ddd 102 2,298 MVC 23 725 上記の通り、懸念していたとおりdddはファイル数、ステップ数共に圧倒的にMVCと比較して多くなる結果となりました。一方でdddは1クラスの凝集度は高く、ほとんど50ステップ未満なので、実装工数は数ほどのインパクトがあるわけではありません。dddでは決まった形式でクラスを実装するケースも多く、ほとんどコピペやロジックの組み立ての時間がかからず作業的に実装するケースもあったので、ステップ数の割には工数はかかっていなかったりします。また、クラスの凝集度が高い分、どこに何が記載されているか、このクラスの役割は何かがひと目で把握しやすいメリットもあります。実装量、ファイル数が多い=悪ではないという事を言いたいのですが、それぞれメリット、デメリットがあります。詳細は以下の「メリット・デメリット」でまとめたいと思います。 パフォーマンス app/Http/Middleware/PerformanceLog.php 上記ファイルにコントローラー実行前後にログを出力し、処理時間、処理に使用したメモリを計測できるようにしました。その計測結果を以下にまとめます。 商品一覧のように大量のレコードをDBから取得して表示する箇所は、MVC > 軽量ddd > dddという結果でした。これは、dddの場合取得したレコードをドメインモデルに変換するコストがかかるためです。 一方意外だったのは、その他はほとんど軽量dddに軍配が上がって軽量ddd > MVC > dddという結果になった点です。詳しくは分析しておりませんが、MVCは手続き型で実装しているので、処理効率が悪い部分があるのでしょうか。もしくは単なる誤差という可能性もあります。いずれにしても気にするほどの差ではありません。商品一覧だけ100ms単位で差が出ているので、大量データ取得・表示のパフォーマンスに影響が出ると考えるのが良さそうです。なお、注文確定処理が2000ms前後と時間がかかっているのは、注文確定のお知らせメール送信処理が含まれるためです。 結果一覧 機能 ddd時間 dddメモリ 軽量ddd時間 軽量dddメモリ MVC時間 MVCメモリ 商品一覧画面表示 289ms 6299KB 178ms 5049KB 85ms 2735KB 商品詳細画面表示 63ms 650KB 18ms 600KB 23ms 627KB カート追加API 153ms 419KB 19ms 418KB 51ms 839KB カート画面表示 36ms 468KB 10ms 382KB 21ms 640KB 注文画面表示 20ms 468KB 9ms 382KB 20ms 640KB 注文バリデーションAPI 34ms 422KB 31ms 506KB 58ms 814KB 注文確定処理 2552ms 1334KB 2517ms 1061KB 1949ms 1376KB エビデンス ddd [2021-07-23 23:00:19] local.DEBUG: Packages\DDDEcSample\InterfaceAdapters\Controllers\ProductController@list メモリ 6298.953125KB [2021-07-23 23:00:19] local.DEBUG: Packages\DDDEcSample\InterfaceAdapters\Controllers\ProductController@list 288.98096084595ミリ秒 [2021-07-23 23:07:51] local.DEBUG: Packages\DDDEcSample\InterfaceAdapters\Controllers\ProductController@detail メモリ 649.6484375KB [2021-07-23 23:07:51] local.DEBUG: Packages\DDDEcSample\InterfaceAdapters\Controllers\ProductController@detail 62.504053115845ミリ秒 [2021-07-23 23:08:28] local.DEBUG: Packages\DDDEcSample\InterfaceAdapters\Controllers\Api\CartController@add メモリ 418.6953125KB [2021-07-23 23:08:28] local.DEBUG: Packages\DDDEcSample\InterfaceAdapters\Controllers\Api\CartController@add 153.38611602783ミリ秒 [2021-07-23 23:08:28] local.DEBUG: Packages\DDDEcSample\InterfaceAdapters\Controllers\CartController@list メモリ 468.3046875KB [2021-07-23 23:08:28] local.DEBUG: Packages\DDDEcSample\InterfaceAdapters\Controllers\CartController@list 36.375999450684ミリ秒 [2021-07-23 23:09:27] local.DEBUG: Packages\DDDEcSample\InterfaceAdapters\Controllers\OrderController@form メモリ 468.3046875KB [2021-07-23 23:09:27] local.DEBUG: Packages\DDDEcSample\InterfaceAdapters\Controllers\OrderController@form 19.876003265381ミリ秒 [2021-07-23 23:09:37] local.DEBUG: Packages\DDDEcSample\InterfaceAdapters\Controllers\Api\OrderController@check メモリ 421.9609375KB [2021-07-23 23:09:37] local.DEBUG: Packages\DDDEcSample\InterfaceAdapters\Controllers\Api\OrderController@check 33.753156661987ミリ秒 [2021-07-23 23:09:44] local.DEBUG: Packages\DDDEcSample\InterfaceAdapters\Controllers\Api\OrderController@save メモリ 1334.2734375KB [2021-07-23 23:09:44] local.DEBUG: Packages\DDDEcSample\InterfaceAdapters\Controllers\Api\OrderController@save 2552.1411895752ミリ秒 軽量ddd [2021-07-23 23:11:33] local.DEBUG: Packages\LightDDDEcSample\InterfaceAdapters\Controllers\ProductController@list メモリ 5049.25KB [2021-07-23 23:11:33] local.DEBUG: Packages\LightDDDEcSample\InterfaceAdapters\Controllers\ProductController@list 177.73199081421ミリ秒 [2021-07-23 23:11:36] local.DEBUG: Packages\LightDDDEcSample\InterfaceAdapters\Controllers\ProductController@detail メモリ 600.1328125KB [2021-07-23 23:11:36] local.DEBUG: Packages\LightDDDEcSample\InterfaceAdapters\Controllers\ProductController@detail 18.326997756958ミリ秒 [2021-07-23 23:11:38] local.DEBUG: Packages\LightDDDEcSample\InterfaceAdapters\Controllers\Api\CartController@add メモリ 417.765625KB [2021-07-23 23:11:38] local.DEBUG: Packages\LightDDDEcSample\InterfaceAdapters\Controllers\Api\CartController@add 19.247770309448ミリ秒 [2021-07-23 23:11:38] local.DEBUG: Packages\LightDDDEcSample\InterfaceAdapters\Controllers\CartController@list メモリ 382.2265625KB [2021-07-23 23:11:38] local.DEBUG: Packages\LightDDDEcSample\InterfaceAdapters\Controllers\CartController@list 10.499000549316ミリ秒 [2021-07-23 23:11:40] local.DEBUG: Packages\LightDDDEcSample\InterfaceAdapters\Controllers\OrderController@form メモリ 382.2265625KB [2021-07-23 23:11:40] local.DEBUG: Packages\LightDDDEcSample\InterfaceAdapters\Controllers\OrderController@form 9.2220306396484ミリ秒 [2021-07-23 23:11:49] local.DEBUG: Packages\LightDDDEcSample\InterfaceAdapters\Controllers\Api\OrderController@check メモリ 506.1171875KB [2021-07-23 23:11:49] local.DEBUG: Packages\LightDDDEcSample\InterfaceAdapters\Controllers\Api\OrderController@check 30.539989471436ミリ秒 [2021-07-23 23:11:53] local.DEBUG: Packages\LightDDDEcSample\InterfaceAdapters\Controllers\Api\OrderController@save メモリ 1061.2578125KB [2021-07-23 23:11:53] local.DEBUG: Packages\LightDDDEcSample\InterfaceAdapters\Controllers\Api\OrderController@save 2517.3659324646ミリ秒 MVC [2021-07-23 23:10:28] local.DEBUG: Packages\MvcSample\App\Http\Controllers\ProductController@list メモリ 2735.3828125KB [2021-07-23 23:10:28] local.DEBUG: Packages\MvcSample\App\Http\Controllers\ProductController@list 84.88392829895ミリ秒 [2021-07-23 23:10:31] local.DEBUG: Packages\MvcSample\App\Http\Controllers\ProductController@detail メモリ 627.4375KB [2021-07-23 23:10:31] local.DEBUG: Packages\MvcSample\App\Http\Controllers\ProductController@detail 22.572994232178ミリ秒 [2021-07-23 23:10:34] local.DEBUG: Packages\MvcSample\App\Http\Controllers\Api\CartController@add メモリ 838.9921875KB [2021-07-23 23:10:34] local.DEBUG: Packages\MvcSample\App\Http\Controllers\Api\CartController@add 50.947904586792ミリ秒 [2021-07-23 23:10:34] local.DEBUG: Packages\MvcSample\App\Http\Controllers\CartController@list メモリ 639.7109375KB [2021-07-23 23:10:34] local.DEBUG: Packages\MvcSample\App\Http\Controllers\CartController@list 20.750045776367ミリ秒 [2021-07-23 23:10:36] local.DEBUG: Packages\MvcSample\App\Http\Controllers\OrderController@form メモリ 639.7109375KB [2021-07-23 23:10:36] local.DEBUG: Packages\MvcSample\App\Http\Controllers\OrderController@form 20.180940628052ミリ秒 [2021-07-23 23:10:45] local.DEBUG: Packages\MvcSample\App\Http\Controllers\Api\OrderController@check メモリ 813.6875KB [2021-07-23 23:10:45] local.DEBUG: Packages\MvcSample\App\Http\Controllers\Api\OrderController@check 57.819128036499ミリ秒 [2021-07-23 23:10:53] local.DEBUG: Packages\MvcSample\App\Http\Controllers\Api\OrderController@save メモリ 1375.6796875KB [2021-07-23 23:10:53] local.DEBUG: Packages\MvcSample\App\Http\Controllers\Api\OrderController@save 1949.3370056152ミリ秒 メリット・デメリット ddd、軽量ddd、MVCのメリット・デメリットを比較した結果を以下にまとめます。dddの記述ボリュームが多くなりましたが、dddよりは、軽量dddを採用するほうがいいと考えます。軽量dddはdddのメリットを全て継承しつつ、dddのファイル数、ステップ数が多くなるデメリットをある程度軽減出来ている為です。MVCはファイル数、ステップ数が圧倒的に少なく、実装工数を考えるとMVC一択と判断しがちですが、メンテナンス性を考えると一口にはそうは言いきれません。また、dddや軽量dddはファイル数、ステップ数が多いですが、その数字=工数とも限りません。殆どが1クラス50ステップ未満の凝集度の高いクラスの為、実装効率が高く実行工数は数ほどのインパクトは無いと考えます。また、dddでは決まった形式でクラスを実装するケースも多く、ほとんどコピペやロジックの組み立ての時間がかからず作業的に実装するケースもあったので、ステップ数の割には工数はかかっていなかったりします。なお、カートのセッション取得箇所を「SessionRepository::get」でgrepすると、ddd、軽量dddは共に1箇所ですが、MVCは4箇所でした。設計次第で改善出来るかもしれませんが、こうした実装の重複はMVCや手続き型の実装では避けづらいです。処理の重複や類似の仕様が様々な箇所に分散して実装されていくと、変更しづらく影響範囲も読みづらいプロジェクトになっていきます。dddが目指すのは変更や拡張をしやすい「しなやかな設計(参考文献「エリック・エヴァンスのドメイン駆動設計」より引用)」ですが、MVCや手続き型の実装ではそうした理想を目指すのが困難だと考えます。運用期間が短かったり、手早く短時間で仕上げたいWEBアプリケーションであれば、MVCでさっと作るのが向いているかもしれませんが、年単位で運用をしながら機能拡張・追加を繰り返して成熟させていきたいWEBアプリケーションやWEBサービスの場合は、dddか軽量dddで構築したほうが良さそうだと考えます。 ddd メリット クラスの凝集度が高くなり、1クラス当りのステップ数は少なくなって可読性、メンテナンス性が高くなる どこに何を実装するかの指針が決まっており、クラスの責務がはっきり分かる形で実装出来るため、ソースコードの重複を避けやすい。(完全には防げないが、重複が発生したらそれを検知してリファクタリングしやすい。例えば手続き型の実装だと関数単位で機能が実装されていって関数が増えていき、その中に重複処理があっても発見しにくくなる。) 依存関係逆転の原則を用いることで、疎結合な実装となる 疎結合な為UnitTestを実装しやすい 疎結合な為、DBの変更、フレームワークの変更、HTML・API・コンソール・ViewComposer等のUIやコントローラーの変更をしても、ユースケース層、ドメイン層は影響を受けずそのまま使える。 デメリット ファイル数、ステップ数が多くなる ドメインモデル→UseCaseData→ViewModelとレイヤーをまたぐ毎にデータの変換が必要となり、パフォーマンスが低下する 軽量ddd メリット 高凝集、疎結合、UnitTestの実装のしやすさはdddのメリットを継承 dddに比べてファイル数、実装量が削減される。 ドメインモデル→ViewModelを、ドメインモデルの配列化で実現し、dddと比べて変換回数が少なくなってパフォーマンス改善した デメリット dddに比べてステップ数が2/3になるが、それでもMVCの3倍以上のステップ数となる dddと比べてパフォーマンス改善したものの、大量データ取得時のパフォーマンスはMVCと比較すると悪い MVC メリット ddd、軽量dddと比べてステップ数、ファイル数が圧倒的に少ない(dddの1/4以下、軽量dddの1/3以下) dddと比べてデータの変換が無い為、処理速度が早い傾向にある(特に大量データ取得時) デメリット コントローラー、モデルクラスにビジネスロジックが集中し、メンテナンス性が悪い神クラスが生まれやすくなる 関数単位で機能が実装されていって関数が増えてき、関数同士の関連がわかりづらくなったり、類似処理の重複を避けづらくなってくる フレームワークと密結合な実装となるため、UnitTestが実装し辛く、実装するには工夫が必要となる Eloquentのパフォーマンス 本記事の趣旨ではありませんが、Eloquentのパフォーマンスについて検証結果を述べます。商品一覧のデータ取得は最初Eloquentのモデルクラスで取得しておりましたが、DB::tableで取得する方式へ変更しました。理由はパフォーマンスの悪さです。どうもEloquentは大量データを取得する際のパフォーマンスが悪い模様です。理由はEloquentのインスタンスを作成するコストが嵩むからという見解と、whereInで大量にパラメータ指定する際のデータバインドのコストが原因という見解があるようです(参考サイト「laracasts」の記事より)。今回のクエリはwhereInを使ってなくても遅かったため、Eloquentのインスタンス化のコストが主な遅延要因ではないかと考えます。本記事のデモサイトでは、DB::tableでこの問題に対処しました。それでもほとんど使い方はEloquentのモデルクラスと同等なので支障はありませんでした。なお、この問題が本記事のパフォーマンス検証に影響しないように、ddd、軽量ddd、MVC全て、商品一覧だけはdb::tableを使用するコードでパフォーマンス計測しております。 React 仕事でVue.jsの採用経験があるためVue.jsのほうが慣れておりますが、いい機会なのでReactに挑戦しました。仕事でVue.jsかReactのどちらを使用するか検討した際、Vue.jsのほうがデザイナーにも受け入れてもらいやすそうだと感じてVue.jsを採用しました。あと、私自身JSXアレルギーがあって、Vue.jsのようにテンプレート記法の方が既存の技術(HTML、CSS、js、jQueryを用いた実装)に近い感覚で実装出来て、わかりやすく実装しやすいと思っていました。 ところが今回Reactに挑戦してみて、JSXアレルギーは誤解だったと考えを改めました。jsコードとHTMLをシームレスに書ける事の便利さに驚きました。JSXはプログラミングしている感覚でHTMLを記述出来ます。なんと言っても自由度の高さです。Vue.jsはHTMLはtemplate、jsロジックはscript、CSSはstyleタグ内に記述するように決まっています。そして、jsロジックの実装ルールも定まっていて、それに沿って実装します。Reactはというと・・・一定のルールはあるものの、かなり自由に実装できます。ただこれは副作用もあります。自由ということは、きちんとコーディング規約を作ったり実装方針や設計を定めないと、メンテナンス性の悪いコードも作れてしまいます。MVCはビューとロジックを別で実装する事で、高いメンテナンス性を実現し、プログラマーとデザイナーが分業しやすくなりました。ReactはHTMLとjsを一緒に書けるのは便利な反面、キチンと設計、実装しないとビューとロジックが混雑してデザイナー、プログラマーの分業に支障が出たり、可読性、メンテナンス性の低下を招くと思います。一長一短ですね・・・ あと、元々懸念していたデザイナーにはハードルが高い点は当たっています。デザイナーよりはプログラマー寄りのアーキテクチャーで、プログラマーは幸せになれますが、デザイナーは敷居が高く感じるかもしれません。ただ、ReactやVue.jsの登場で、フロントエンドで出来ることは大幅に増えました。一昔前はHTML、CSSで静的コンテンツを制作し、jsやjQueryで動的コンテンツをちょこっと色づけという感じでしたが、ReactやVue.jsでがっつりフロンドで動的コンテンツを実装し、サーバーサイドはREST APIだけ提供し、データの永続化とその復元といったバックエンド処理に徹するという構成も今では珍しくありません。昨今GraphQLという選択肢もあり、フロントエンドに実装の比重が移りつつあります(GraphQLを採用すると、今回検証したdddも必要無くなるかも・・・)。SEO対策の点ではSPAは不安もあるのでその点十分考慮する必要はありますが、SSRという解決策も用意されています。その事を考えたら、デザイナーもガリガリとプログラミングに参画するのが時代の流れかも知れません。そうした時代の流れに乗るのであれば、Reactも十分選択肢になり得ると考えます。 今回の趣旨では無いものの、Reactに挑戦してみてReactが広く受け入れられている理由を肌で感じました。少し実装ルールを勉強したら、あとは直感的にどんどん実装していけました。ただ、初めてのReactなのでコード品質は悪いのでそこはご容赦下さい。本格的に導入するには、フォルダ設計、実装方針の策定、CSSのツールの選定等々、色々と事前に勉強・検討する必要がありそうです。 参考文献・記事・ソースコード エリック・エヴァンスのドメイン駆動設計 実践ドメイン駆動設計 (Object Oriented SELECTION) テスト駆動開発 ドメイン駆動設計入門 ボトムアップでわかる! ドメイン駆動設計の基本 ドメイン駆動設計 モデリング/実装ガイド nrslib/StrictLaraClean DDDパターンを活用した Laravelアプリケーション開発 shin1x1/laravel-ddd-sampl laracasts
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む