Slide 1

Slide 1 text

No content

Slide 2

Slide 2 text

梶川 琢馬 𝕏 @kajitack 株式会社 TechBowl VPoT TechTrain の開発/メンター PHP カンファレンス新潟は 2 回目 佐渡島(200km)を自転車で 1 周したことがある スライドは X で公開します! x.com/kajitack 2/56

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

「もっと疎結合に」と言ったこと、 言われたことはありますか? 5/56

Slide 6

Slide 6 text

今日のゴール 「疎結合に」の代わりに、強度、距離、変動性で言える AI やチームと、設計の話を一歩深くできる 6/56

Slide 7

Slide 7 text

結合度 関数やクラス、システムなどのまとまり(コンポーネント)の関係性 ある特定の変更について片方を変えると、 もう片方も変えざるを得ないとき、 その 2 つは結合している 7/56

Slide 8

Slide 8 text

結合度が高いと? 変更の難易度が上がり、システムの柔軟性と 信頼性が低下する コンポーネントに変更を加えた場合、依存している 他のコンポーネントも一緒に変更やテストをしなければならない 頻度が高くなる 8/56

Slide 9

Slide 9 text

結合は悪? 結合はシステムをまとめ上げる接着剤 相互作用、つまり結合がなければ、 システムの目的を達成できない 結合をゼロにすることは不可能 目的を実現するための良い結合を目指す 9/56

Slide 10

Slide 10 text

結合の振り返り 片方の変更で、もう片方も変えざるを得ない関係が結合 結合がなければ、コンポーネントは協調できない 結合をなくすのではなく、目的に合う結合を考える 10/56

Slide 11

Slide 11 text

結合バランス 11/56

Slide 12

Slide 12 text

ソフトウェア設計の結合バランス 著者は Vlad Khononov(ヴラド・ホノノフ) 前著『Learning Domain-Driven Design』 (邦訳『ドメイン駆動設計をはじめよう』) 1 冊をまるまる「結合」に割いた本 12/56

Slide 13

Slide 13 text

結合の3 次元 強度 距離 変動性 13/56

Slide 14

Slide 14 text

強度、距離、変動性で色んなことが分かる 言葉 式 安定性 NOT (変動性 AND 強度) 変更コスト 変動性 AND 距離 モジュール性 強度 XOR 距離 複雑性 NOT (強度 XOR 距離) メンテナンスの労力 強度 × 距離 × 変動性 均衡度 (強度 XOR 距離) OR NOT 変動性 意味 上流の変更が下流へ連鎖しにくいこと 変動性か強度が低ければ安定 連鎖的な変更を合わせる実際のコスト 変動性が高く距離が遠いと高い 変更に要る認知負荷を小さく抑える能力 強度と距離が対照的だと高い 変更や理解に要る認知負荷 強度と距離が等しいと高い 依存関係を維持するために予想される労力 3 つのどれかが低ければ小さい 設計がモジュール性と複雑性のどちらに向かうか モジュラーか、変動性が低ければ高い 14/56

Slide 15

Slide 15 text

結合の強度 15/56

Slide 16

Slide 16 text

強度 共有する「知識」が多いほど、強度は高い 知識には、メソッド呼び出しの引数や共通のルールなど、さまざまな形がある 相手が公開していない 内部の知識を使う 相手の振る舞いと 前提の知識を持つ 相手のモデルの 知識を共有する 統合用に用意した 知識だけを使う 例:具象クラスの継承、 trait の use 例:new、静的 メソッド呼び出し 例:引数と戻り値 の型 例:interface への依存 (侵入結合) (機能結合) (モデル結合) (コントラクト結合) 共有が多い 共有が少ない 16/56

Slide 17

Slide 17 text

公開されていない内部の知識を使うと、強度はいちばん高い (侵入結合) class Customer { private string $status; } PHP 別のモジュール // $property = new ReflectionProperty(Customer::class, 'status'); $status = $property->getValue($customer); クラスの作者は、外から読まれていることを知らない プロパティ名を変える度に変更が必要 17/56

Slide 18

Slide 18 text

同じルールの知識を別々に持つと、強度はやや高い (機能結合) 注文モジュール // function isFreeShipping(Order $order): bool { return $order->totalCost > 1000; } PHP 配送モジュール // function isFreeShipping(Order $order): bool { return $order->totalCost > 1000; } 呼び出し関係はないのに、ルールが変わると両方を同時に直す必要がある 18/56

Slide 19

Slide 19 text

モデルの知識をまるごと共有する場合、強度は低め (モデル結合) 顧客管理モジュール // class CustomerRepository { public function get(string $id): Customer { /* ... */ } } PHP 分析モジュール // $status = $repository->get($customerId)->status; 欲しいのはステータスだけなのに、Customer 全体の構造を知っている 19/56

Slide 20

Slide 20 text

統合用に用意した知識だけを使うと、強度はいちばん低い (コントラクト結合) 顧客管理モジュール // class CustomerRepository { public function getStatus(string $id): Status { /* ... */ } } PHP 分析モジュール // $status = $repository->getStatus($customerId); Customer の中身が変わっても、分析モジュールは影響を受けない 20/56

Slide 21

Slide 21 text

データベースの共有 A ↓ B ↓ A の DB(非公開) A ↓ B ↓ A の DB(読み取り可) A の DB を、B が 勝手に SELECT する A ↓ (侵入結合) 同じテーブル (機能結合) B が A のテーブルの 列をそのまま読む A ↓ B 向けの最小限の 列だけを読む (モデル結合) 公開用のビュー B ↓ B ↓ 同じ入力チェックを A と B の両方に書く (コントラクト結合) 21/56

Slide 22

Slide 22 text

非同期処理 非同期にすること自体は、強度を下げない 共有の仕方 専用の DTO を渡す コントラクト結合 Eloquent モデルをそのまま渡す モデル結合 別のリスナが終わっている前提で動く 機能結合 失敗したら呼び出し元で打ち消しが要る 機能結合 統合用ではないキューを直接読む 侵入結合 なぜその強度か 受け手は DTO の形だけを知る。送り手の内部モデルは漏れていない 受け手がテーブルの構造まで知ることになる 処理の順番を、受け手が前提にしている 全部成功か全部失敗か、という約束を両方で守る 統合のために用意されていない経路を使っている 22/56

Slide 23

Slide 23 text

強度が低いほうがなんとなく良さそうだけど、 それが疎結合ってことなの? 強度だけでは決まらないよ 次は距離について考えてみよ 23/56

Slide 24

Slide 24 text

結合の距離 24/56

Slide 25

Slide 25 text

距離 知識が移動する空間の広さ ファイルの中身からディレクトリ構成、外部との連携やシステム単位など 変 更 の コ ス ト 文 メソッド オブジェクト 名前空間 ライブラリ サービス システム 25/56

Slide 26

Slide 26 text

同じ知識を、サービス境界の向こうで共有すると 改善前。同じ計算が、別の境界にある 注文サービス // final class Tax { public function of(int $price): int { return (int) ($price * 0.1); } } PHP 請求サービス // final class Tax { public function of(int $price): int { return (int) ($price * 0.1); } } 同じ知識を、サービスの外で共有している 26/56

Slide 27

Slide 27 text

同じ知識は、同じ境界の近くで共有する 改善後。同じ境界の中で、1 つの計算を使う 同じサービスの中 // final class Tax { public function of(int $price): int { return (int) ($price * 0.1); } } PHP $orderTax = $tax->of($price); $billTax = $tax->of($price); 近くで共有すると、1 箇所を直せばよい 27/56

Slide 28

Slide 28 text

距離が遠いと、調整もリリースも分かれる 注文アプリ(リリース ) // A $rate = $this->billingApi->taxRate(); PHP 請求アプリ(リリース ) // B public function taxRate(): float { return 0.1; } 税率を変えるとき、2 つのリリースのタイミングを合わせるように調整が必要になる 28/56

Slide 29

Slide 29 text

距離が近いと、リリースの単位を同じにできる 同じアプリケーション // final class Tax { public function rate(): float { return 0.1; } } PHP $order->tax = $tax->of($price); 関係する変更を、同じリリースで反映できる 29/56

Slide 30

Slide 30 text

人、チーム、部門 勤務地やタイムゾーンも、距離になる (ソシオテクニカルな距離) 同じチームと、別チームでは、調整のコストが違う 同じチームの中での結合は、その場で直せる 別チームのサービスを変えるときは、予定を合わせ、デプロイも別になる コードの場所が近くても、担当する人が違えば距離は遠くなる 30/56

Slide 31

Slide 31 text

疎結合とは、強度が低く、距離が遠いこと モジュール性 = 強度 XOR 距離 変更に要る認知負荷を小さく抑える能力 複雑性 = NOT (強度 XOR 距離) 変更や理解に要る認知負荷 強 度 高 強 度 低 高凝集 密結合 一緒に変わるものが近くにある 一緒に変わるのに遠い モジュラー 複雑 低凝集 疎結合 関係が薄いのに近くにある 関係が薄く、離れている 複雑 モジュラー 距離 低 距離 高 31/56

Slide 32

Slide 32 text

結合の変動性 32/56

Slide 33

Slide 33 text

変動性 今後そのコンポーネントが変わる見込み 受付 → 予定の変更 → 割り当て モデル結合、距離は遠い、変動性は低い 同じ結合でも、変わる見込みで改善の優先度が変わる 33/56

Slide 34

Slide 34 text

変動性の見積もり方は 2 つ ドメイン的に重要な変更なのか、他の変更に影響されていたりバグ修正が多かったりするのか ドメイン分析 ソースコード分析 見るもの サブドメインの種類(コア、支援、汎用) モジュールごとのコミット数 向く場面 これから変わりやすいかを予測する 既存のコードで、実際の変わり方を見る 注意 将来の予測は難しい 修正と機能追加を分けて数える 変更が少なくても、触れないから変わらないだけのことがある 34/56

Slide 35

Slide 35 text

結合バランスの論理式 モジュール性 = (強度 XOR 距離) バランス = モジュール性 OR NOT 変動性 = (強度 XOR 距離) OR NOT 変動性 モジュール性が高いか変動性が低ければバランスが取れている状態 35/56

Slide 36

Slide 36 text

結合バランスは、コードだけでは決められない 例:一般会員と法人会員の割引計算が、 現在は同じ式になっている 今後も一緒に変わるのか、 たまたま一致しているだけなのかは、 コードだけでは分からない コードから測れるのは、 依存関係や変更頻度まで 測れる事実と業務知識を合わせて判断する speakerdeck.com/kajitack/coupling-metor 36/56

Slide 37

Slide 37 text

結合バランスの振り返り 強度:共有する知識の量 距離:知識をどこで共有するか 変動性:今後変わる見込み この 3 つの組み合わせで、結合の状態と改善の優先度を判断する 37/56

Slide 38

Slide 38 text

実際のプロジェクトを整理してみる 38/56

Slide 39

Slide 39 text

これから紹介する事例 現在の構成と、これまでのリファクタリングを紹介する 適した構成は、各社のドメインやチーム構成によって変わる ここは強度が高いから、 距離を近づけようか? 変動性も高いね 先に見直そう 39/56

Slide 40

Slide 40 text

ケーススタディ 01 同じコードベースの設計 同じコードベースの中 たとえば、課題とユーザーが、同じアプリの中にある場合 書き方 ユーザーが、課題の内部テーブルを直接読む 侵入結合 同じ判定を、課題とユーザーの両方に書く 機能結合 ユーザーが、課題の Eloquent モデルをそのまま使う モデル結合 ユーザーが、課題の用意した DTO だけを使う コントラクト結合 なぜその強度か 課題が公開していない内部を使っている ルールが変わったら、両方を同時に直す必要がある 課題のテーブルの構造まで知っている 課題が公開すると決めた形だけを知っている 40/56

Slide 41

Slide 41 text

ケーススタディ 01 同じコードベースの設計 外側から内側へ依存する Presentation Application 依存は内側へ Domain ドメインは外側の 実装を参照しない Infrastructure 41/56

Slide 42

Slide 42 text

ケーススタディ 01 同じコードベースの設計 同じコードベースのまま アクセスの仕方を分けた 同じアプリケーション 書き込み ユースケース API 集約とリポジトリ 読み取り DB クエリサービス 書き込みは集約の整合性を扱い、読み取りは必要な形で取得する 42/56

Slide 43

Slide 43 text

ケーススタディ 02 管理画面の移行 管理画面が別リポジトリで 同じデータベースを共有していた 改善前。距離は遠く、強度は高い 利用者向け API Laravel 管理画面 別リポジトリ ↓ ↓ 同じデータベース 43/56

Slide 44

Slide 44 text

ケーススタディ 02 管理画面の移行 同じリポジトリの Filament から ドメイン層を再利用する 改善後。コードを近づけ、確認する場所を同じドメイン層にまとめた 同じリポジトリ 利用者向け API Filament の管理画面 ドメイン層を、再利用する 44/56

Slide 45

Slide 45 text

ドメインイベント 購読側 発行側 ユーザー登録 起きた事実を発行 イベント ユーザー登録済み UserRegistered 何が起きたかを共有 メール送信 通知 プラン作成 発行側は購読側の処理を直接呼び出さない 45/56

Slide 46

Slide 46 text

ケーススタディ 03 イベントによる後続処理の分離 副作用を、その場で順番に全部呼び出す 改善前。本体が後続を直接知っている public function register(User $user): void { $this->users->save($user); $this->mailer->sendWelcome($user); $this->crm->sync($user); $this->notifier->notify($user); } PHP 46/56

Slide 47

Slide 47 text

ケーススタディ 03 イベントによる後続処理の分離 同期のまま強度を下げてから、非同期化する 改善後。先に事実の通知へ切り替える public function register(User $user): void { $this->users->save($user); $this->events->publish( new UserRegistered($user->id), ); } PHP 47/56

Slide 48

Slide 48 text

ケーススタディ 03 イベントによる後続処理の分離 キューを挟んでもモデル結合のままになりうる 改善前。送り側のモデルをキューに乗せる final class User { public string $name; public string $email; public string $passwordHash; } PHP $queue->push($user); 非同期にしただけでは強度は下がらない 48/56

Slide 49

Slide 49 text

ケーススタディ 03 イベントによる後続処理の分離 統合用の契約だけを流すと強度は下がる 改善後。キューに乗せるのは統合用の契約 final class UserRegistered { public function __construct( public string $userId, ) {} } PHP $queue->push(new UserRegistered($user->id)); コントラクト結合へ寄る 49/56

Slide 50

Slide 50 text

実践例の振り返り 同じコードベースでも、共有する知識によって強度は変わる 管理画面では、コードを近づけて同じドメイン層を再利用した イベントでは、通信方法より何を共有するかで強度が変わる 50/56

Slide 51

Slide 51 text

設計を学ぶ意味 51/56

Slide 52

Slide 52 text

複雑性は超線形に増え、 認知の限界は成長しない 複雑性 (超線形) 機能 (線形) 認知の限界 (成長しない) システムの規模 出典: 『ソフトウェア設計の結合バランス』図12.6をもとに作成 52/56

Slide 53

Slide 53 text

複雑性の爆増への対処 要素同士の関係が増えると、変更の影響を把握しにくくなる 技術的な層をまたいで一緒に変わるコードを、機能ごとにまとめる 強く関係するものの距離を縮め、別の機能とは分ける 強度や距離を調整することでモジュール性を保つ 53/56

Slide 54

Slide 54 text

AI エージェントによる開発では システムの理解がより大事 「変動性」を、リファクタリングの優先度やどこまで理解を手放せるかの判断に使える それらを統合した結合バランスは、コードからサービス、組織まで、 マイクロサービスやポートアダプターパターンなど特定のアーキテクチャではなく、 どの粒度の設計にも使える物差し 54/56

Slide 55

Slide 55 text

結合は抽象度が上がっても同じ考え方を使える メソッドからクラス設計、レイヤードアーキテクチャやマイクロサービスなど、 色んなレイヤーの設計に結合は現れる エンジニアの仕事の抽象度が上がって、ソフトウェアファクトリーのように 「システムをつくるシステム」を設計するときにも、設計の物差しが活きてくる ソフトウェア システム サービス コンポーネント クラス ファクトリー メソッド 55/56

Slide 56

Slide 56 text

ソフトウェア設計における結合バランス 「疎結合」は強度が低く距離が遠い、 「高凝集」は強度が高く距離が近い (変更容易性が高い=モジュール性) ソフトウェア設計では複雑性が爆発的に増える 形状を変え、強度と距離を調整してモジュール性を維持する 「変動性」は、リファクタリングの優先度や 理解を手放す判断に使える 結合バランスは、どの粒度の設計にも使える物差し ソフトウェアファクトリーのように 「システムをつくるシステム」を設計するときにも、 この物差しが活きる 56/56