クリーンアーキテクチャでのア ンチパターン PHPカンファレンス福岡全然野菜 June 22, 2023. v0.0.4 Press Space for next page
自己紹介 katzumiと申します 以下のアカウントで活動しています 祝初採択! コレの供養です katzchum k2tzumi katzumi
親の顔より見た図 The Clean Architecture
話す内容 DDD,クリーンアーキテクチャなプロジェクトで となった個人的な気づきです。 クリーンアーキテクチャの理解の解像度が低いが故に [1] やりがちな事象をまとめました。 あるあるーという生暖かい気持ちで見ていただけると 用語とか違っていましたらこっそり教えてください 1. 自分も含めて ↩︎
パターン ドメインモデルの貧血症 定義されているメソッドがコンストラクタとgetterのみ コンストラクタに制約もなし DTO?とモデルが区別ついてない?
パターン の問題点 多分他のレイヤーにロジックが散らばっている ドメインの制約がわからなくて関心事の認知負荷が大きい Valueオブジェクトに徹しようとし過ぎていている。。のか。。。? モデル間の変換処理を別の所でやっている?
パターン の処方 まあちゃんとドメイン設計頑張ろうぜ コンストラクタで制約を表現する所からはじめよっか 制約をつける必要がない。。DTOなら Array shapesにしてドメインモデルにしなくても。。(要stan) /** * @phpstan-type UserAddress array{street: string, city: string, zip: string} */ class User { /** * @var UserAddress */ private array $address; // is of type array{street: string, city: string, zip: string} }
パターン ReadオブジェクトとWriteオブジェクト を分けない テーブルの定義がそのままモデルのプロパティになっている データの永続化と読み込みの際のモデルが同じになっている
パターン の問題点 テーブル設計 != モデル設計 そもそもインフラ層の関心ごとをモデルに持ち込みたくないのに。。 データベースで正規化してデータをうまく扱えない 書き込み時はコードで扱いコードに対する名称は扱わない 逆に読み込み時は名称も表示したいケースがある → フロントエンドでコードに対する名称をN+1なAPIアクセスするなど。。 データの更新時に本来不要なデータが必要になる モデルが複雑で関連するデータが存在する場合(正規化されている場合もそう)に準備が必要になる 同タイミングにデータ更新された場合にデータが巻き戻ってしまう 不必要なカラムまで更新しにいきがちで、最悪キー情報も更新してしまっていた例も
パターン の処方 ReadオブジェクトとWriteオブジェクトを分ける コマンド・クエリ分離の原則(CQS)に従うこと レコードの区分値のみを更新する様な場合は事実(イベント)だけ保存できないか?も検討すること よくあるフラグのみを更新するような作りだと、更新対象以外のカラムにも影響が出やすい 独立したイベントとして扱いCQRSまで視野を入れたほうがいい
パターン ユースケース毎に適切なコマンドやクエ リが作成されていない インフラ層でデータの新規登録と更新用のメソッドが1つ 新規登録時にレコードを登録するコードもしくはIDが初番されてない なんでもできてしまう汎用的なコマンドが爆誕する 関連するテーブルが一つであればコマンドは一つという思い込み 全レコードを参照して集計するユースケースでも、一覧表示用のクエリを使い回している
パターン の問題点 新規と更新の制約は本質的に違うのにモデルを分けたいができない → コードが発行されていない状態をnullで表現とかするの? そもそもコードの初番をどこで行うの? データのパターンが多いし、責務が多いのでテストしづらい オブジェクトが大量にロードされてメモリ不足や速度低下が発生する
パターン の処方 適切にコマンドを分けてリポジトリの引数となるドメインモデルにそれぞれの制約をつける 必要に応じて集計関数を利用したクエリを定義する
パターン 集約を扱うモデルを適切に定義できてな い 集約モデルの独自コレクションクラスがなくて毎回Where条件が違う処理をインフラ層に定義しまくる 生のオブジェクト配列をユースケースでソート・フィルタリングしてる処理が分散してしまっている 集約モデルの独自コレクションでメモリ上で大量データをフィルタリングしてしまっている
パターン の問題点 クエリが増え過ぎてしまう ソート条件が変わるだけでもクエリ作るの辛い 汎用的に使えるクエリを定義できなくもないが、実装難しい オンメモリでのソートフィルタイングはパフォーマンス悪い 複数のユースケースに同じようなソートやフィルタリング処理がコピペ量産されてしまう ソースの可読性やテストが辛くなる
パターン の処方 集約のモデルをちゃんと定義してフィルターやソートができるようにする 逆にクエリに任せたほうが良い場合もあるので頑張り過ぎない DBの力を借りる ドメインロジックが漏れ出してしまっているという見え方もするかもなので慎重に行う
その他パターン 賛否両論ありそうなパターン ドメイン層のロジックにフレームワーク依存のコードが混在 → フレームワークを変えたくなったらどうするの? 逆に原理主義に走りすぎてフレームワークの力を使えていない → 流石にコレクションクラスはどこのフレームワークにもあるっしょ トランザクションを全然使えていない(パターン3の延長線) 関連するモデルを永続化する際に、汎用的なコマンドを複数組みわせてユースケースから呼び出しているケ ース → 一見問題ないように見えるけれど、途中のコマンドで失敗した場合に辛い。 要所要所ではトランザクションは使いたい 冪等性担保の配慮が全くされていない イミュータブルなモデルになってない
ご清聴ありがとうございます