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

人には人それぞれのサービス層がある

 人には人それぞれのサービス層がある

PHPカンファレンス新潟2025 の発表資料になります
#phpcon_niigata #a

Avatar for shimabox

shimabox

May 31, 2025
Tweet

More Decks by shimabox

Other Decks in Programming

Transcript

  1. NAME: "しまぶ" SNS: "@shimabox" TAMASHII: "沖縄" COMPANY: "カオナビ" SKILL: -

    "PHP" - "Go" NIIGATA: "生まれてはじめて" whoami.yml 2 自己紹介
  2. 5 【おことわり】わたしが今回話そうとしているサービス層の定義 用語 主な役割 サービス層 • MVCにおけるControllerとModelの間にいる アプリケーション サービス (DDD)

    • ユースケースを実現する ◦ クリーンアーキテクチャでは UseCaseInteractor • 「〜したい」という目的を実行 • 例)「ユーザー登録をしたい」「請求書を発行したい」 • ドメインモデルと会話、協調する ドメインサービス (DDD) • エンティティや値オブジェクトに収まらないドメインロジック • 例) 送金サービス(2つの口座間の振替) • 作りすぎると貧血と呼ばれる ❌ ❌ 👈
  3. 19 わたしの回答 • ◦ 処理フローをひとつに束ねたものを置く場所 ◦ I/O が複数あるものを分離 • ◦

    処理フローがかんたんに呼び出せる ◦ 再利用できるとうれしい • ◦ でかい、if文が多い、過剰に分割されている 何のためにサービス層がある? どんなサービスクラスが必要? どんなサービスクラスが嫌?
  4. 22 みんなに聞いてみたサービス層 • ◦ ビジネスロジックの整理、システムの複雑さを管理 ◦ ワークフローを調整・実行する役 • ◦ 複雑な処理(単一のモデルで完結しないなど)を隠蔽

    ◦ 再利用されるビジネスロジック ◦ テストが書きやすい • ◦ 過剰分割、肥大、まるで神のような 何のためにサービス層がある? どんなサービスクラスが必要? どんなサービスクラスが嫌?
  5. サービス層の役割/目的 • (複雑な)ワークフローを隠蔽 • 再利用したい • テストが書きやすい ものを置くところ 26 わたしの見解

    わたしの出会ってきたサービス • DB処理が大量に詰め込まれたサービス • どこからでも呼ばれてしまう「神」 サービス • 取り急ぎ作られた「なんちゃって」 サービス • トランザクションスクリプト(手続 き)がひしめくサービス • 複数のサービスと手を組み、仲良く連 携するサービス • ドメイン層を支えるサービス
  6. // DB触る(SQL書く)やつはとりあえずここへ(昔はよくあった) // EloquentをラップしているRepositoryに似てる class UserService { private \PDO $pdo;

    public function __construct(\PDO $pdo) { $this->pdo = $pdo; } public function getUserById($id) { /* SELECT処理 */ } public function getAllUsers() { /* SELECT ALL */ } public function createUser($data) { /* INSERT処理 */ } public function updateUser($id, $data) { /* UPDATE処理 */ } public function deleteUser($id) { /* DELETE処理 */ } // 付随したものが増える public function getUsersByCondition($condition) { /* 条件付きSELECT */ } public function existsUser($id) { /* 存在チェック */ } // 以降永遠に続く } 35 💔DB処理が大量に詰め込まれたサービス
  7. // ユーザーに関するものならなんでもござれ(xxxManagerとか) class UserService { public function createUser(array $data) {

    … } public function updateUser($id, array $data) { … } public function deleteUser($userId) { … } public function validateUser(array $data) { … } public function sendWelcomeEmail($user) { … } public function generateReport($userId) { … } public function exportUserData($userId) { … } // ... さらに続く } 36 💔どこからでも呼ばれてしまう「神」サービス
  8. // とりあえずXXXサービスというところに書く // EloquentをラップしているRepositoryに似てる class UserService { // 何かやっていそうで何もやっていない //

    Pass-through public function find($id) { return User::find($id); } } 37 💔取り急ぎ作られた「なんちゃって」サービス
  9. class OrderService { public function processOrder(array $orderData) { // 100行以上の処理が続く...

    if ($orderData['type'] === 'premium') { if ($orderData['amount'] > 10000) { // 処理A if ( … ) { // 処理B // さらに処理が続く... } else { … } } } else if (...) { … } // まだまだ続く... } } 38 💔憎まれるサービスの代表例 (トランザクションスクリプト(手続き)がひしめくサービス)
  10. // 注文処理の複雑さを隠蔽 → 複雑な業務フローの隠蔽 final class OrderProcessingService { public function

    __construct( private PaymentGatwayInterface $payment, private InventoryService $inventory, private ShippingService $shipping ) {} public function processOrder(OrderRequest $req): Order { return DB::transaction(function () use ($req) { // 在庫確認 $this->inventory->reserve($req->items); // 決済処理 $payment = $this->payment->charge($req->paymentMethod, $req->amount); // 配送手配 $shipping = $this->shipping->arrange($req->shippingAddress, $req->items); // 注文作成 $order = Order::create([ 'user_id' => $req->userId, 'payment_id' => $payment->id, 'shipping_id' => $shipping->id, 'total_amount' => $req->amount, ]); return $order; }); } } 40 💖愛されるサービスの代表例 (複数のサービスと手を組み、仲良く連携するサービス)
  11. 41 💖愛されるサービスの代表例 (複数のサービスと手を組み、仲良く連携するサービス) • 責務が分離されている ◦ 各責務(在庫・決済・配送)を別のクラスに委譲 ◦ OrderProcessingService は全体の調整役に徹している

    • 処理の流れが明確 ◦ 在庫確認→決済→配送→注文作成 • 依存性注入(DI)の適切な利用 ◦ 決済方法を変更したい場合は PaymentGatewayInterface の実 装だけを変更すれば済む
  12. 通称:PofEAA • 9章に Service Layer のことが 書かれている 42 賢者の考えるサービス層 Patterns

    of Enterprise Application Architecture (Addison-Wesley Signature Series (Fowler))
  13. サービスレイヤー 3行まとめ 1. アプリケーションの「受付窓口」 2. 処理の流れ(アプリケーションロジック)を調整 し、ドメインロジックは別の層に任せる 3. 複数の入り口(Web、API等)での重複処理をな くす

    44 賢者の考えるサービス層 Patterns of Enterprise Application Architecture (Addison-Wesley Signature Series (Fowler)) 愛されるサービス なんとなく合っている
  14. 46 賢者の考えを拝借 A Philosophy of Software Design, 2nd Edition 深いモジュール

    と 浅いモジュール (インタフェースは小さくて、機能は多いものがよいを表す図)
  15. • 深いモジュール(利用者に優しい) ◦ シンプルなインターフェース ◦ 強力な機能 ◦ 複雑性を隠蔽 • 浅いモジュール(利用者が苦労)

    ◦ 複雑なインターフェース ◦ 薄い機能しか提供していない ◦ 複雑性が漏れる → 多くのメソッドが露出 47 賢者の考えを拝借 A Philosophy of Software Design, 2nd Edition
  16. 50 Facade サービス層 is ファサード(Not Laravel) 1. 受付窓口 2. 処理の流れを調整

    増補改訂版Java言語で学ぶデザインパターン入門 15章を読もう
  17. // 注文処理の複雑さを隠蔽 → 複雑な業務フローの隠蔽 final class OrderProcessingService { public function

    __construct( private PaymentGatwayInterface $payment, private InventoryService $inventory, private ShippingService $shipping ) {} public function processOrder(OrderRequest $req): Order { return DB::transaction(function () use ($req) { // 在庫確認 $this->inventory->reserve($req->items); // 決済処理 $payment = $this->payment->charge($req->paymentMethod, $req->amount); // 配送手配 $shipping = $this->shipping->arrange($req->shippingAddress, $req->items); // 注文作成 $order = Order::create([ 'user_id' => $req->userId, 'payment_id' => $payment->id, 'shipping_id' => $shipping->id, 'total_amount' => $req->amount, ]); return $order; }); } } 54 【再掲】複数のサービスと手を組み、仲良く連携するサービス
  18. 64 品質を測る3つの指標 1. 循環的複雑度(Cyclomatic Complexity) ◦ 条件分岐の複雑さを示す指標 ◦ 理想値: 10以下

    2. 結合度(Coupling) ◦ モジュール間の依存関係の強さ ◦ 理想: 疎結合 3. 凝集度(Cohesion) ◦ モジュール内の要素の関連性の強さ ◦ 理想: 高凝集 PhpMetrics などで計測可能 https://github.com/phpmetrics/PhpMetrics
  19. final class OrderProcessingService { public function __construct() {} public function

    processOrder(OrderRequest $req): Order { return DB::transaction(function () use ($req) { // 在庫確認 $this->inventoryReserve(...); // 決済処理 $payment = $this->paymentCharge(...); // 配送手配 $shipping = $this->shippingArrange(...); // 注文作成 $order = Order::create(...); return $order; }); } private function inventoryReserve(...) { /* 在庫を管理するロジック*/ } private function paymentCharge(...) { /* 決済を行うロジック */ } private function shippingArrange(...) { /* 配送を手配するロジック*/ } } 65 初手のFatを恐れない 初手のFatを 恐れない
  20. final class OrderProcessingService { public function __construct() {} public function

    processOrder(OrderRequest $req): Order { return DB::transaction(function () use ($req) { // 在庫確認 $this->inventoryReserve(...); // 決済処理 $payment = $this->paymentCharge(...); // 配送手配 $shipping = $this->shippingArrange(...); // 注文作成 $order = Order::create(...); return $order; }); } private function inventoryReserve(...) { /* 在庫を管理するロジック*/ } private function paymentCharge(...) { /* 決済を行うロジック */ } private function shippingArrange(...) { /* 配送を手配するロジック*/ } } 66 最初は大胆に、そして少しずつ整理でも (なんなら) 最初は コントローラー ↓ 複雑、責務が入り混 じってきたら ↓ サービス (責務を分離)
  21. class OrderService { public function processOrder(array $orderData) { // 100行以上の処理が続く...

    if ($orderData['type'] === 'premium') { if ($orderData['amount'] > 10000) { // 処理A if ( … ) { // 処理B // さらに処理が続く... } else { … } } } else if (...) { … } // まだまだ続く... } } 67 【再掲】トランザクションスクリプト(手続き)がひしめくサービス 窓口は一つ これでもいいの では?
  22. class OrderService { public function processOrder(array $orderData) { // 100行以上の処理が続く...

    if ($orderData['type'] === 'premium') { if ($orderData['amount'] > 10000) { // 処理A if ( … ) { // 処理B // さらに処理が続く... } else { … } } } else if (...) { … } // まだまだ続く... } } 68 みんなで仲良く メンテナー にも優しくあれ
  23. 81 みんなに聞いてみたサービス層 1. たくさんの質問をみんなへ 2. 質問を以下でグルーピング a. 質問1: サービス層とは何か?その役割は? b.

    質問2: サービス層はいつ必要? c. 質問3: サービス層の良い点と悪い点は? 3. 回答をまとめたものが、この後ツラツラと続きます
  24. 82 みんなに聞いてみたサービス層 質問1: サービス層とは何か?その役割は? • ビジネスロジックの実装場所 ◦ 「ビジネスロジックやワークフローを調整・実行する役」 ◦ 「ビジネスロジックの計算を担当し、基本的には外部システムに依存しない層」

    ◦ 「サービス層はビジネスロジックを持つもの、もっと言うと、インターフェース を実装するもの」 ◦ 「端的に言えば、ビジネスロジックをカプセル化するところ」
  25. 84 みんなに聞いてみたサービス層 質問1: サービス層とは何か?その役割は? • その他見解 ◦ 「MVCで言うところの、どれでもない奴」 ◦ 「1ファイルに長々と処理を書いている時に、処理を分割するためにサービスっ

    てとこに書く感じかな」 ◦ 「使い回し可能な業務仕様置き場」 ◦ 「そんな腑抜けた名前の層はない。アプリケーションが先でレイヤリングは後で しょう。」 👀
  26. 85 みんなに聞いてみたサービス層 質問2: サービス層はいつ必要? • 複雑さ・規模に応じて判断 ◦ 「コントローラーで書く処理が複雑になったら」 ◦ 「処理の規模が大きくないうちは使わず、Controllerが肥大化したら検討する」

    ◦ 「AIが読むのが辛くなってきたら(500行くらい)」 ◦ 「複雑な挙動でない場合は、いらない場合もあるかも」 ◦ 「サービス層は必要になるまで作らなくていいと思う」
  27. 88 みんなに聞いてみたサービス層 質問3: サービス層の良い点と悪い点は? • テスト容易性・保守性 ◦ 「ビジネスロジックのテストが容易である点。その他の汎化可能なロジックをラ イブラリとして切り出しやすくなる点。」 ◦

    「HTTPのRequestに依存せずにテストが書ける」 ◦ 「手続きとルールを分離できるのでテストコードが書きやすい」 ◦ 「細かいケースのUTを書いても実行速度に影響をそんなに与えない(通常な ら)」
  28. 89 みんなに聞いてみたサービス層 質問3: サービス層の良い点と悪い点は? • 過剰分割・肥大化のリスク ◦ 「サービス層以外も切りすぎて理解が出来なくなった時」 ◦ 「クラスが多すぎて管理が大変になったとき」

    ◦ 「一つのサービスクラスが大きくなりがちで、なんでもできちゃうサービスクラ スが生まれたこと」 ◦ 「名前が抽象的すぎてなんでも入ってるみたいな状態(つまり神クラス)」
  29. 90 みんなに聞いてみたサービス層 その他のご意見 • 「細かく切ることが正義じゃないと思う」 • 「人によって呼び名は変わる」 • 「そもそもサービスオブジェクトとアプリケーションサービスとドメ インサービスの違いもよくわからない・・・」

    • 「Repository というよく分からんデータアクセス層とORMを操る ソースコードとユースケースと言われるものが入り乱れたゴミ箱」 • 「先に層を語るやつは、何をやらせてもだめ」 💣 💣
  30. • Patterns of Enterprise Application Architecture (Addison-Wesley Signature Series (Fowler))

    ◦ https://www.amazon.co.jp/dp/B008OHVDFM • A Philosophy of Software Design, 2nd Edition ◦ https://www.amazon.co.jp/dp/B09B8LFKQL • 増補改訂版Java言語で学ぶデザインパターン入門 ◦ https://www.amazon.co.jp/dp/4797327030 • サービスクラスのありがたみを発見したときの思い出 ◦ https://speakerdeck.com/77web/sabisukurasunoarigatamiwofa-jian-sitato kinosi-ichu-number-phpcon-odawara 91 参考資料