Upgrade to Pro — share decks privately, control downloads, hide ads and more …

ActiveRecordパターンの呪縛を学びほぐして挑むクリーンアーキテクチャへの入り口

katzumi
September 16, 2023

 ActiveRecordパターンの呪縛を学びほぐして挑むクリーンアーキテクチャへの入り口

ActiveRecordパターンに引きづられて設計が歪みがちになるのを学びほぐすことでクリーンアーキテクチャでの開発の良いスタートラインに立てるのではないでしょうか?

katzumi

September 16, 2023
Tweet

More Decks by katzumi

Other Decks in Technology

Transcript

  1. お願い 登壇者の励みになるので是非ともご意見やご感想など、フィードバック頂けると助かります mm あとでスライドを公開します 4 / 95 写真撮影、SNSでの実況について    🙆‍♀📷    🙅‍♂📹💸

       🙅📸👨‍👦‍👦 #trackb-1540-activerecordパターンの呪縛を学びほぐ して挑むクリーンアーキテクチャへの入り口 #phpcon_okinawa #track_b
  2. 今日お話すること・話さないこと 5 / 95 スコープ的なお話 🤫話さないこと 具体的な実装例 このセッションでは設計パターンの原理・原則に焦 点を当てます クリーンアーキテクチャへの移行手法

    オブジェクト指向の基本的な概念や用語 📣話すこと 設計の原理・原則及びパターンの適用条件 クリーンアーキテクチャの目指すゴール ActiveRecordパターン経験者が陥りがちなアンチパ ターン 今後のActiveRecordとの付き合い方 クリーンアーキテクチャとの相性の良い設計パター ン
  3. 10 / 95 フルスタックフレームワーク Laravel Eloquent ORM Symfony Doctrine ORM

    Yii framework ActiveRecord Ruby on Rails ActiveRecord Spring Boot Spring Data JPA ライブラリ Java Hibernate Golang GORM, XORM
  4. 11 / 95 Eloquent ORMでのモデル作成 scaffoldして こうなる(する) $ php artisan

    make:model User namespace App\Models; use Illuminate\Database\Eloquent\Model; class User extends Model { protected $fillable = ['username', 'email']; protected $table = 'users'; }
  5. 12 / 95 Eloquentモデルの使い勝手 use App\Models\User; // ユーザーを作成 $user =

    new User(); $user->username = 'john_doe'; $user->email = '[email protected]'; $user->save(); // ユーザーを更新 $user = User::find(1); $user->email = '[email protected]'; $user->save(); // ユーザーを削除 $user = User::find(1); $user->delete(); // ユーザーを検索 $users = User::all(); foreach ($users as $user) { echo $user->username . ': ' . $user->email . PHP_EOL; }
  6. 12 / 95 Eloquentモデルの使い勝手 // ユーザーを更新 $user = User::find(1); $user->email

    = '[email protected]'; $user->save(); use App\Models\User; // ユーザーを作成 $user = new User(); $user->username = 'john_doe'; $user->email = '[email protected]'; $user->save(); // ユーザーを削除 $user = User::find(1); $user->delete(); // ユーザーを検索 $users = User::all(); foreach ($users as $user) { echo $user->username . ': ' . $user->email . PHP_EOL; }
  7. 12 / 95 Eloquentモデルの使い勝手 // ユーザーを削除 $user = User::find(1); $user->delete();

    use App\Models\User; // ユーザーを作成 $user = new User(); $user->username = 'john_doe'; $user->email = '[email protected]'; $user->save(); // ユーザーを更新 $user = User::find(1); $user->email = '[email protected]'; $user->save(); // ユーザーを検索 $users = User::all(); foreach ($users as $user) { echo $user->username . ': ' . $user->email . PHP_EOL; }
  8. 12 / 95 Eloquentモデルの使い勝手 // ユーザーを検索 $users = User::all(); foreach

    ($users as $user) { echo $user->username . ': ' . $user->email . PHP_EOL; } use App\Models\User; // ユーザーを作成 $user = new User(); $user->username = 'john_doe'; $user->email = '[email protected]'; $user->save(); // ユーザーを更新 $user = User::find(1); $user->email = '[email protected]'; $user->save(); // ユーザーを削除 $user = User::find(1); $user->delete();
  9. 16 / 95 由来 Ruby on RailsのActiveRecordという名称は、マ ーチンファウラー氏の書籍「Patterns of Enterprise

    Application Architecture (PoEAA)」(2002年出版)で紹介された ActiveRecordパターンに由来しています。 1. パターンをまとめた設計カタログで、用語自 体はもっと先からあった模様 ↩︎ RailsのActiveRecordが初出ではない [1]
  10. 17 / 95 PoEAAで紹介されている設計カタログ 1. Domain Logic Patterns 2. Data

    Source Architectural Patterns 3. Object-Relational Behavioral Patterns 4. Object-Relational Structural Patterns 5. Object-Relational Metadata Mapping Patterns 6. Web Presentation Patterns 7. Distribution Patterns 8. Session State Patterns 9. Base Patterns 1. 各パターンの邦訳名は以下URLを参照 https://bliki-ja.github.io/pofeaa/CatalogOfPofEAA_Ja↩︎ 以下の9つのカテゴリに40種類のパターンをまとめられている [1]
  11. 18 / 95 PoEAAで紹介されている設計カタログ 1. Domain Logic Patterns 2. Data

    Source Architectural Patterns ← ココ! 3. Object-Relational Behavioral Patterns 4. Object-Relational Structural Patterns 5. Object-Relational Metadata Mapping Patterns 6. Web Presentation Patterns 7. Distribution Patterns 8. Session State Patterns 9. Base Patterns ActiveRecordパターンに言及されている領域
  12. 19 / 95 Data Source Architectural Patterns Table Data Gateway

    データベースのテーブルに対する操作を提供するオブジェクトのパターン Row Data Gateway データベースの行に対応するオブジェクトのパターン Active Record ← コレ! データベースの行に対応するオブジェクトで、自分自身を保存や削除できるパターン Data Mapper データベースの行とドメインオブジェクトの間のマッピングを担当するオブジェクトのパターン Data Source Architectural Patternsは、データベースとのやり取りをするためのパターンをまとめたもの
  13. 20 / 95 ActiveRecordとは? "An object that wraps a row

    in a database table or view, encapsulates the database access, and adds domain logic on that data. An object carries both data and behavior. " https://bliki-ja.github.io/pofeaa/ActiveRecord より データベースのテーブルやビューの列をラップし、データベースアクセスをカプセル化し、 ドメインロジックを追加するオブジェクト データと振る舞いの両方を持つオブジェクト “
  14. 22 / 95 データベースとのやり取りをするだけではない! Domain Logic Patterns ← 特にココ! ビジネスロジックの一部であるドメインロジックを実装するためのパターンをまとめたもの Object-Relational Behavioral

    Patterns オブジェクトとリレーショナルデータベースの間の振る舞いの問題を解決するためのパターンをまとめたも の Object-Relational Structural Patterns オブジェクトとリレーショナルデータベースの間の構造の問題を解決するためのパターンをまとめたもの Object-Relational Metadata Mapping Patterns オブジェクトとリレーショナルデータベースの間のマッピングをメタデータで管理するためのパターンをま とめたもの Data Source以外のパターンも混ざっている
  15. 25 / 95 悪い兆候 🚩Red flags Fat Model Fat Controller

    MVAC Service Object 前提が崩れ始めてきた際に出現するワード
  16. 27 / 95 関心事(=責務)を分けて考えると ActiveRecordパターンは Domain Model(Domain Logic Patterns) +

    Row Data Gateway(Data Source Architectural Patterns) 2つのパターンに分解できる。 業務ロジックを持ったRow Data Gateway。 ドメインの振る舞いとデータの永続化は別レイヤーの関心事 ActiveRecordパターンは敢えてレイヤーを分けずに密結合にしてDRY(Don't Repeat Your Self:繰り返し を避けること)に書けるように注力しています。 MEMO
  17. 35 / 95 SOLIDの原則 設計の原則の頭文字 S: 単一責任の原則(SRP: Single Responsibility Principle)

    O: 開放閉鎖の原則(OCP: Open-Closed Principle) L: リスコフの置換原則(LSP: Liskov Substitution Principle) I: インターフェース分離の原則(ISP: Interface Segregation Principle) D: 依存性逆転の原則(DIP: Dependency Inversion Principle)
  18. 36 / 95 SRP: 単一責任の原則 一つのモジュールやクラスは、一つの機能だけを持つべきであるという原則 これにより、コードの変更やテストがし易く再利用性が向上する 🚩 悪い兆候 多目的クラス,

    神クラス, 責務の不明確さ Single Responsibility Principle クリーンアーキテクチャでは、各層やコンポーネントが単一の責務を持つように設計されます レイヤーごとに責務を分けることで、変更が他のレイヤーに影響しないようにしています
  19. 37 / 95 OCP: 開放閉鎖の原則 ソフトウェア要素は、拡張には開いており、修正には閉じているべきであるという原則。 これにより、ソースコードを変えずに機能を拡張でき、保守性や再利用性が向上する 🚩 悪い兆候 条件分岐の乱用,

    責務過多 Open-Closed Principle クリーンアーキテクチャでは、内側の層が外側の層に対して開放されており、外側の層が内側の 層に対して閉鎖されます 内側のレイヤーは外側のレイヤーに影響されないようにし、外側のレイヤーは内側のレイヤーを 拡張することができます
  20. 38 / 95 LSP: リスコフの置換原則 派生クラスは、その基底クラスと置換可能であるべきであるという原則。 これにより、型の互換性や拡張性が保たれ、コードの再利用性や可読性が向上する 🚩 悪い兆候 事前条件の強化,

    事後条件の弱化 Liskov Substitution Principle クリーンアーキテクチャでは、インターフェースを用いて抽象化された内側の層が、具体的な実 装である外側の層に置き換えられるように設計されます 内側のレイヤーは外側のレイヤーの具体的な実装を知る必要がなくなります
  21. 39 / 95 ISP: インターフェース分離の原則 インターフェースは、分離できるものは分離するべきであるという原則 これにより、クライアントは不要なメソッドに依存しなくて済み、コードの可読性や保守性が向上する 🚩 悪い兆候 肥大化したインターフェース,

    不完全なインターフェース Interface Segregation Principle クリーンアーキテクチャでは、各層やコンポーネントが必要最低限のインターフェースを持つよ うに設計されます 内側のレイヤーが外側のレイヤーの具体的な実装を知る必要がなくなります
  22. 43 / 95 誤解されている内容 この通りに実装するのが正解 Clean Architectureの具体例として挙げられているだけ レイヤーは4層 レイヤーは4つ以外は認めないというルールはないと明記されている MVC1に近い構成にしないといけない

    そもそもWebアプリケーション以外 も想定している図なので盲信すると危険! 最近のWebアプリケーションの構成と乖離が出てくる 他にも色々あるけれど。。 1. 同心円の外側のDevices, UI, External Interfacesがソレ それぞれ、キーボードやNativeアプリ(GUI)、CLI、メールやイベント・キューとかもありそう ↩︎ やたら独り歩きしている感 [1]
  23. 45 / 95 各層(レイヤー間)の関係性 レイヤー 位置 抽象度 重要度 安定度 具体的な

    コンポーネント 役割 内側 高い (抽象) 高い 高い ビジネスルール ドメインモデル ソフトウェアの本質的部分や目的を表すもの 外側 低い (具象) 低い 低い データソース (データベース,Web API) ソフトウェアの技術的詳細や手段を表すもの 1. 抽象度とは、コンポーネントが具体的な実装や詳細から独立している程度を表す指標 ↩︎ 2. 重要度とは、システムが解決しようとしている課題や問題領域の関心事にどれだけ適合しているかを表す指標。 高い場合は修正の影響が大きい(テスト含めて) ↩︎ 3. 安定度とは、修正されにくい度合い又は、依存性関係が少なく他のモジュールの修正の影響を受けづらい度合い ↩︎ 円の内側は高レイヤーで外側は低レイヤーとする [1] [2] [3]
  24. 49 / 95 各層(レイヤー間)の 目指す関係性 レイヤー 位置 抽象度 重要度 安定度

    具体的な コンポーネント 役割 内側 高い (抽象) 高い 高い ビジネスルール ドメインモデル ソフトウェアの本質的部分や目的を表すもの 外側 低い (具象) 低い 低い データソース (データベース,Web API) ソフトウェアの技術的詳細や手段を表すもの 1. 抽象度とは、コンポーネントが具体的な実装や詳細から独立している程度を表す指標 ↩︎ 2. 重要度とは、システムが解決しようとしている課題や問題領域の関心事にどれだけ適合しているかを表す指標。 高い場合は修正の影響が大きい(テスト含めて) ↩︎ 3. 安定度とは、修正されにくい度合い又は、依存性関係が少なく他のモジュールの修正の影響を受けづらい度合い ↩︎ 円の内側は高レイヤーで外側は低レイヤーとする [1] [2] [3]
  25. 50 / 95 各層(レイヤー間)の 目指す関係性 重要度の高いものコアとし、中心に据える レイヤー 位置 重要度 抽象度

    安定度 具体的な コンポーネント 役割 内側 高い 高い (抽象) 高い ビジネスルール ドメインモデル ソフトウェアの本質的部分や目的を表すもの 外側 低い 低い (具象) 低い データソース (データベース,Web API) ソフトウェアの技術的詳細や手段を表すもの 1. 重要度とは、システムが解決しようとしている課題や問題領域の関心事にどれだけ適合しているかを表す指標。 高い = 内部品質の高さが求められるコアとなるもの↩︎ 2. 抽象度とは、コンポーネントが他の具体的な実装や詳細から独立させた度合いの指標 ↩︎ 3. 安定度とは、修正されにくい度合い又は、依存性関係を少なくし、他のモジュールの修正に閉じている状態の度合い ↩︎ [1] [2] [3]
  26. 53 / 95 複雑で不安定なものから切り離す手法 制御のフロー Application Domain Infrastructure 依存の向き x

    抽象が具象に依存している Application Domain Infrastructure 制御の流れと依存の向きは連動してしまう ※説明を単純にする為にレイヤーを3層にした例 PoEAA的に以下の関心事に対応 Application: Web Presentation Domain: Domain Logic Infrastructure: Data Source MEMO
  27. 62 / 95 アンラーニング、その前に… 新しい用語が複数出てきます 詳細な説明はしません クリーンアーキテクチャでの設計に興味を持ってもらうことが目標です 気になった用語はワードだけ覚えて、後で自分で調べるスタイルでお願いします 🙇‍♀ アンチパターンとその対処法になります

    以前の考え方や行動パターンと対比して新しいものに触れます 全てやらないとクリーンアーキテクチャにならないわけではありません 設計パターンなので、思考の流れを理解するのが大事です クリーンアーキテクチャの経験者は一緒に内省しましょう😅 1. The Clean Architectureの誤解で触れた通り ↩︎ 2. 登壇者は紹介するアンチパターンの全てに心当たりがあります ↩︎ ここからの注意点 [1] [2]
  28. 64 / 95 学びほぐし事例 の変革の必要性 テーブル設計 != モデル設計 論理設計と物理設計を同じと思っている テーブル設計の変更に影響を受ける

    Valueオブジェクト や集約 が表現されていない 振る舞いや制約がないか、分散してしまっている primitiveな型Onlyは要注意 テーブルの正規化対応できない 単純に関連モデルとして実装すると、データ構造が露出する 1. 日本語論理名を英語に翻訳するだけとか。。 ↩︎ 2. 同一性を持たず、属性が同じなら同じと扱うオブジェクト。例えば、日付や金額など ↩︎ 3. 一貫性を保つ必要があるドメインオブジェクトのまとまり。トランザクション境界にもなる ↩︎ ActiveRecordは必ず1対1にしていたけれど [1] [2] [3]
  29. 65 / 95 学びほぐし事例 のブレークスルー まずはドメインに向き合いましょう 1. ドメインの問題領域を理解する 2. ユビキタス言語を定義する

    3. ドメインオブジェクトを識別する オブジェクトの種類を決めValueオブジェクトや集約を識別する。 振る舞いや関係(協調・制約)も洗い出す ドメインファーストな設計をしましょう。永続化はレイヤーを分けて次に考えましょう
  30. 67 / 95 学びほぐし事例 の変革の必要性 状態を変更した際に副作用が発生する可能性がある 制約をすり抜けてしまう コードの可読性が悪い ドメインイベントを見落とす 手続き的にsetterを呼び出しされても意図が伝わらない

    ビジネスルールがあっても認識されない 1. 後からビジネスルールの追加があった場合などに対応漏れが発生する ↩︎ ActiveRecordはミュータブルなモデル [1]
  31. 70 / 95 学びほぐし事例 の変革の必要性 制約違反したオブジェクトが存在できてしまう 制約をすり抜けて整合性や一貫性が失われる validメソッドが実装されていても制約違反を発見するタイミングが遅い validメソッドの呼び忘れも起こる ビジネスルールがわからなくなる

    認知負荷が高くなる ドメインの制約を守る実装がそのドメインモデルを使う人任せになってしまう ビジネスルールが散らばってしまう ActiveRecordは制約違反があってもオブジェクト自体は存在できてしまう ドメインモデルがgetter・setterしかない場合にドメイン貧血症と言われます 個人的にはコンストラクタの制約がないことが問題だと感じています MEMO
  32. 73 / 95 学びほぐし事例 の変革の必要性 正規化したデータをうまく扱えない 書き込み時はコードのみ、名称は不要 逆に読み込み時には名称も一緒にロードして表示したい データ更新時に不要なデータまで必要になる モデルが複雑で関連するデータが多い場合に準備が大変

    一括更新になりがちでデータの不整合が発生する 不要なカラムまで更新して、同時更新されると不整合となる ActiveRecordではテーブルとの関連は原則1:1。分けれなくもないけれど普通はやらない
  33. 74 / 95 学びほぐし事例 のブレークスルー データの更新(コマンド)と参照(クエリ)を別のモデルに分ける コマンド・クエリ責務分離(Command-Query Responsibility Segregation, CQRS)の原則に従う。

    コマンドは正規化してデータの一貫性と整合性を保つ クエリは非正規化してデータ参照時のパフォーマンスを上げる 1. モデルだけでなくデータソースも別にし、スケーラビリティやパフォーマンスを向上させる場合もありま す。 ↩︎ ReadオブジェクトとWriteオブジェクトを分ける [1]
  34. 77 / 95 学びほぐし事例 のブレークスルー イミュータブルデータモデルで設計してみる 更新と削除を行わないテーブル設計 エンティティをイベントとリソースに分けてモデリングする コマンドのオブジェクトを分けるようにする 新規登録用のオブジェクトと更新用のオブジェクト(イベント対象と内容のみ)で分ける

    1. T字形ER データベース設計技法と相性が良いです ↩︎ 事実を失う操作(更新・削除)を行わせない、データの構造や関係とする [1] イミュータブルデータモデルは、イベントソーシングパターンとも相性が良いです。 イベントの量が多くなった際に分散処理しやすくなります。 MEMO
  35. 79 / 95 学びほぐし事例 の変革の必要性 データの関連を適切にクエリとして表現する必要がある 集約をクエリとして表現しないと、クエリ間 でもN+1が起こる 集約データをRepositoryから取得する際の戻り値がEager Loadになる

    Lazy Loadでエンティティを返すにはテクニックが必要 1. 個々のクエリを再利用してしまい、APIでもN+1で呼び出される危険性がある ↩︎ ActiveRecordはデフォルトでLazy Loadが前提になっているけれど [1]
  36. 83 / 95 ソフトウェア設計における戦術と戦略の違い 戦術(Tactics) 個々のモジュールやクラスなどの小さなスコープで適用できる設計テクニック(Design Pattern) インターフェースと実装の分離や情報隠蔽など → SOLID原則も含まれます 戦略(Strategy)

    システム全体や大きなスコープで適用できる設計テクニック(Architectural Pattern) モジュール間の依存関係や抽象化のレベルなど → クリーンアーキテクチャ 戦略には中長期的な視点が必要
  37. 84 / 95 ActiveRecordは戦術?それとも戦略? PoEAA的にはData Source Architectural Patterns データソースを中心にした設計で戦略的 ActiveRecordパターンは以下の設計思想・戦術を組み合わせるとより戦略的になる

    1. CoC (設定より規約) 2. DRY (繰り返しを避ける) 3. REST (RDBMSのCRUDとHTTPのリクエストメソッドに関連させる) 1. ActiveRecordのモデル名とエンドポイントのURIも関連させる ↩︎ 戦術を組み合わせることで戦略に昇華することができる [1]
  38. 88 / 95 今後のActiveRecord系ライブラリとの向き合い方 Infrastructure層のRepositoryでActiveRecordを使う ORマッパー・クエリービルダとして使う ただしドメインオブジェクトとActiveRecordオブジェクトへのマッピングが必要 ドメインオブエジェクトではなくDTO(Data Transfer Object),

    DPO(Data Persistence Object)の場合も あり 1. Data Mapperでマッピングするほうが良いが、関心事が混ざる問題がある ↩︎ 2. DPOはRepositoryのインターフェース側に定義させる ↩︎ ActiveRecordは戦術的に使うだけに留める [1] [2]
  39. 91 / 95 設計パターンの適用条件 ActiveRecordパターンの場合 (DRY / 開発効率スピード重視 / 作るものがシンプルで明確)

    データベースのテーブルとオブジェクトの構造がほぼ同じ データベースの操作が単純 ビジネスロジックが複雑でない クリーンアーキテクチャの場合 (SOLID / 変更容易性・拡張性重視 / 作るものが複雑で不明確、又は変更多い) ビジネスロジックやドメイン知識が重要 外部リソースやフレームワークに依存しないようにしたい 変更や追加に柔軟に対応したい 再利用可能なケース
  40. 93 / 95 よりクリーンなコードにしていく 複雑なものは下位層に隠す データソースだけでなく他のものも モデルの表現を豊かにする Valueオブジェクト・集約・ドメインイベントを識別して、振る舞いや関係を定義する 構造や振る舞いがわかりやすくする イミュータブルなモデル・データ構造にして一貫性を保つ

    CQRSの原則に従いスケーラビリティやパフォーマンスを高める 将来的な分散システム(マイクロサービス等)への移行も視野に入れておく 切り出しやすくして変化に強くする → 複雑さが下位層に隠れている状態
  41. 94 / 95 Note ツナギメエフエム Ep.52 ちょうぜつソフトウェア設計入門――PHPで理解するオブジェクト指向の活用 Patterns of Enterprise

    Application Architecture / Martin Fowler’s Bliki (ja) texta.fm 3.Low-Code Development texta.fm 4.Not Just ORM A Philosophy of Software Design を30分でざっと理解する TM(T字形ER)によるモデリング PHPではじめるCQRSっぽいやつ 現場で役立つシステム設計の原則 〜変更を楽で安全にするオブジェクト指向の実践技法 参考資料&紹介した記事など