Slide 1

Slide 1 text

Service Provider Service Container 2/16 Laravel JP Conference 前田 和人

Slide 2

Slide 2 text

こんな人におすすめ ■ MVCは何となくわかる ■ それ以外の機能はあまり知らない ■ サービスコンテナを使ったことがない ■ サービスコンテナで挫折した

Slide 3

Slide 3 text

このセッションの目標 ■ サービスコンテナをざっくり理解する ■ サービスコンテナに1つのサービスを結合する勇気が出 る

Slide 4

Slide 4 text

お話しないこと ■ サービスコンテナの細かい仕様・実装 ■ 豊富な実装例 ■ サービスディスカバリ

Slide 5

Slide 5 text

注意事項 ■ このセッションでは、理解することを最優先します ■ 場合によっては正確ではないことを記載している場合が あります

Slide 6

Slide 6 text

■ 時間の都合で削らせていただきます。すみません。 注意事項

Slide 7

Slide 7 text

注意事項 ■ このセッションでは、コードがかなり出てきます。 ■ 以下に資料があがってますので、 お手元で見ていただくことをおすすめします。

Slide 8

Slide 8 text

自己紹介 ■ 前田 和人 ■ @chiroruxxxx ■ 弁護士ドットコム株式会社

Slide 9

Slide 9 text

自己紹介 ■ 前田 和人 ■ @chiroruxxxx ■ 弁護士ドットコム株式会社

Slide 10

Slide 10 text

弁護士ドットコム

Slide 11

Slide 11 text

BUSINESS LAWYERS

Slide 12

Slide 12 text

本題

Slide 13

Slide 13 text

なぜサービスコンテナは難しいのか

Slide 14

Slide 14 text

なぜサービスコンテナは難しいのか サービス DI サービス コンテナ インター フェース 自動注入

Slide 15

Slide 15 text

サービス サービス DI サービス コンテナ インター フェース 自動注入

Slide 16

Slide 16 text

ユースケース ■ メール送信の例を考えてみましょう ■ メール送信にはSendGridを使います – ユーザが登録した際に、 SendGrid側にユーザ情報を保持 – ユーザに登録完了メールを送信 ■ 管理画面から選択したユーザを削除した際に、 SendGrid側のユーザ情報も削除

Slide 17

Slide 17 text

ユースケース ■ メール送信の例を考えてみましょう ■ メール送信にはSendGridを使います – ユーザが登録した際に、 SendGrid側にユーザ情報を保持 – ユーザに登録完了メールを送信 ■ 管理画面から選択したユーザを削除した際に、 SendGrid側のユーザ情報も削除

Slide 18

Slide 18 text

ユーザ情報の登録のコード // …ユーザのDB保存処理 // sendgridのcontactに登録 $sendGrid = new SendGrid(config('services.sendgrid.key')); $recipient = new Recipient( $user->first_name, $user->last_name, $user->mail_address ); $response = $sendGrid->client->contactdb() ->recipients()->post([$recipient]); if (!starts_with($response->statusCode(), '20')) { Log::error('ユーザを登録できませんでした。'); }

Slide 19

Slide 19 text

ユーザ情報の登録のコード // …ユーザのDB保存処理 // sendgridのcontactに登録 $sendGrid = new SendGrid(config('services.sendgrid.key')); $recipient = new Recipient( $user->first_name, $user->last_name, $user->mail_address ); $response = $sendGrid->client->contactdb() ->recipients()->post([$recipient]); if (!starts_with($response->statusCode(), '20')) { Log::error('ユーザを登録できませんでした。'); } SDKを初期化

Slide 20

Slide 20 text

ユーザ情報の登録のコード // …ユーザのDB保存処理 // sendgridのcontactに登録 $sendGrid = new SendGrid(config('services.sendgrid.key')); $recipient = new Recipient( $user->first_name, $user->last_name, $user->mail_address ); $response = $sendGrid->client->contactdb() ->recipients()->post([$recipient]); if (!starts_with($response->statusCode(), '20')) { Log::error('ユーザを登録できませんでした。'); } SDKを初期化 ユーザ情報を作成

Slide 21

Slide 21 text

ユーザ情報の登録のコード // …ユーザのDB保存処理 // sendgridのcontactに登録 $sendGrid = new SendGrid(config('services.sendgrid.key')); $recipient = new Recipient( $user->first_name, $user->last_name, $user->mail_address ); $response = $sendGrid->client->contactdb() ->recipients()->post([$recipient]); if (!starts_with($response->statusCode(), '20')) { Log::error('ユーザを登録できませんでした。'); } SDKを初期化 ユーザ情報を作成 ユーザ登録API

Slide 22

Slide 22 text

ユーザ情報の登録のコード // …ユーザのDB保存処理 // sendgridのcontactに登録 $sendGrid = new SendGrid(config('services.sendgrid.key')); $recipient = new Recipient( $user->first_name, $user->last_name, $user->mail_address ); $response = $sendGrid->client->contactdb() ->recipients()->post([$recipient]); if (!starts_with($response->statusCode(), '20')) { Log::error('ユーザを登録できませんでした。'); } SDKを初期化 ユーザ情報を作成 ユーザ登録API エラーの場合は ログ出力

Slide 23

Slide 23 text

メール送信のコード // 登録完了メール送信 $mail = new Mail( '[email protected]', $user->mail_address, '会員登録受付のお知らせ', new Content('text/plain', '会員登録を受付ました。') ); $response = $sendGrid->client->mail()->send()->post($mail); if (!starts_with($response->statusCode(), '20')) { Log::error('メールを送信できませんでした。'); } // …ビューの表示処理

Slide 24

Slide 24 text

メール送信のコード // 登録完了メール送信 $mail = new Mail( '[email protected]', $user->mail_address, '会員登録受付のお知らせ', new Content('text/plain', '会員登録を受付ました。') ); $response = $sendGrid->client->mail()->send()->post($mail); if (!starts_with($response->statusCode(), '20')) { Log::error('メールを送信できませんでした。'); } // …ビューの表示処理 メール作成

Slide 25

Slide 25 text

メール送信のコード // 登録完了メール送信 $mail = new Mail( '[email protected]', $user->mail_address, '会員登録受付のお知らせ', new Content('text/plain', '会員登録を受付ました。') ); $response = $sendGrid->client->mail()->send()->post($mail); if (!starts_with($response->statusCode(), '20')) { Log::error('メールを送信できませんでした。'); } // …ビューの表示処理 メール作成 メール送信API

Slide 26

Slide 26 text

メール送信のコード // 登録完了メール送信 $mail = new Mail( '[email protected]', $user->mail_address, '会員登録受付のお知らせ', new Content('text/plain', '会員登録を受付ました。') ); $response = $sendGrid->client->mail()->send()->post($mail); if (!starts_with($response->statusCode(), '20')) { Log::error('メールを送信できませんでした。'); } // …ビューの表示処理 メール作成 メール送信API エラーの場合は ログ出力

Slide 27

Slide 27 text

コードの状態 ■ 見やすさ – 処理をざっと眺めるときにはわかりづらい ■ ユーザ情報の保持とメール送信だけなのに長い

Slide 28

Slide 28 text

コードの状態 ■ 再利用性 – 「メールを送信する」という処理は色々な箇所で使 われそう – 毎回このコードをコピペする・・・?

Slide 29

Slide 29 text

コードの状態 ■ 保守性 – > 管理画面から選択したユーザを削除した際に、 SendGrid側のユーザ情報も削除 – 色々なところにSendGridのコードが出てくるのはつ らい – ライブラリの使い方が変わったら、修正箇所を洗い 出すだけでも大変

Slide 30

Slide 30 text

コードの状態 ■ つまり・・・? – 処理をべた書きすると、色々と大変そう ■ ⇒サービス(クラス)をつくろう!

Slide 31

Slide 31 text

サービスとは? ■ 同じ枠組みの処理をひとつのクラスにまとめたもの – 今回は、SendGrid関連という枠組みでまとめる ■ コンポーネントとも言う ■ コントローラもモデルもサービス ■ (このセッション内だけの用語です)

Slide 32

Slide 32 text

サービスのコード class SendGridService { private $sendGrid; public function __construct() { $this->sendGrid = new SendGrid(config('services.sendgrid.key’)); } public function saveUser(User $user): void { // 元のコントローラから処理をコピー } public function sendMail(Mail $mail): void { // 元のコントローラから処理をコピー } }

Slide 33

Slide 33 text

コントローラのコード // …ユーザーのDB保存処理 // sendgridのcontactに登録 $sendGridService = new SendGridService(); $sendGridService->saveUser($user); // 登録完了メール送信 $mail = new Mail( '[email protected]', $user->mail_address, '会員登録受付のお知らせ', new Content('text/plain', '会員登録を受付ました。') ); $sendGridService->sendMail($mail); // …ビューの表示処理

Slide 34

Slide 34 text

コントローラのコード // …ユーザーのDB保存処理 // sendgridのcontactに登録 $sendGridService = new SendGridService(); $sendGridService->saveUser($user); // 登録完了メール送信 $mail = new Mail( '[email protected]', $user->mail_address, '会員登録受付のお知らせ', new Content('text/plain', '会員登録を受付ました。') ); $sendGridService->sendMail($mail); // …ビューの表示処理 サービス作成

Slide 35

Slide 35 text

コントローラのコード // …ユーザーのDB保存処理 // sendgridのcontactに登録 $sendGridService = new SendGridService(); $sendGridService->saveUser($user); // 登録完了メール送信 $mail = new Mail( '[email protected]', $user->mail_address, '会員登録受付のお知らせ', new Content('text/plain', '会員登録を受付ました。') ); $sendGridService->sendMail($mail); // …ビューの表示処理 サービス作成 ユーザ登録

Slide 36

Slide 36 text

コントローラのコード // …ユーザーのDB保存処理 // sendgridのcontactに登録 $sendGridService = new SendGridService(); $sendGridService->saveUser($user); // 登録完了メール送信 $mail = new Mail( '[email protected]', $user->mail_address, '会員登録受付のお知らせ', new Content('text/plain', '会員登録を受付ました。') ); $sendGridService->sendMail($mail); // …ビューの表示処理 サービス作成 ユーザ登録 メール作成

Slide 37

Slide 37 text

コントローラのコード // …ユーザーのDB保存処理 // sendgridのcontactに登録 $sendGridService = new SendGridService(); $sendGridService->saveUser($user); // 登録完了メール送信 $mail = new Mail( '[email protected]', $user->mail_address, '会員登録受付のお知らせ', new Content('text/plain', '会員登録を受付ました。') ); $sendGridService->sendMail($mail); // …ビューの表示処理 サービス作成 ユーザ登録 メール作成 メール送信

Slide 38

Slide 38 text

サービスのまとめ ■ 見やすさ – 詳細な処理がコントローラに書かれていないので 見やすい ■ 再利用性 – サービスのインスタンスを生成すれば コピペしなくて済む ■ 保守性 – 似た処理が同じところに集まったので メンテナンスしやすい

Slide 39

Slide 39 text

DI サービス DI サービス コンテナ インター フェース 自動注入

Slide 40

Slide 40 text

ユースケース ■ 今までSendGridのAPIキーは固定でした ■ 統計上の都合で、ユーザ側と管理者側でAPIキーを分け ることになりました

Slide 41

Slide 41 text

コードの状態 ■ 今はサービスのコンストラクタにAPIキーをべた書き public function __construct() { $this->sendGrid = new SendGrid(config('services.sendgrid.key')); }

Slide 42

Slide 42 text

コードの状態 ■ UserSendGridクラスとAdminSendGridクラスに分け る・・・? – 設定の数だけクラスが増えていくの・・・? ■ ⇒DIをしよう!

Slide 43

Slide 43 text

DIとは? ■ 何の略語かとか、日本語訳を載せると 途端に難しそうにきこえるアレ ■ DIとは、インスタンスの生成に必要な設定を引数で渡し てあげること ■ 今回で言うと、SendGridクラス

Slide 44

Slide 44 text

DIとは? ■ 無駄にクラスを作成したり、setterメソッドが無くなる – setterが存在すると、 「このインスタンスの状態は?」 をずっと考えながらコーディングしないといけない ■ インスタンスの設定を状況に合わせて指定できる

Slide 45

Slide 45 text

サービスのコード class SendGridService { private $sendGrid; public function __construct(SendGrid $sendGrid) { $this->sendGrid = $sendGrid; } // …その他の処理 }

Slide 46

Slide 46 text

サービスのコード class SendGridService { private $sendGrid; public function __construct(SendGrid $sendGrid) { $this->sendGrid = $sendGrid; } // …その他の処理 } 設定を受け取る

Slide 47

Slide 47 text

ユーザー側のコントローラのコード // sendgridのcontactに登録 $sendGrid = new SendGrid(config('services.sendgrid.user.key’)); $sendGridService = new SendGridService($sendGrid); $sendGridService->saveUser($user);

Slide 48

Slide 48 text

ユーザー側のコントローラのコード // sendgridのcontactに登録 $sendGrid = new SendGrid(config('services.sendgrid.user.key’)); $sendGridService = new SendGridService($sendGrid); $sendGridService->saveUser($user); 設定を生成

Slide 49

Slide 49 text

ユーザー側のコントローラのコード // sendgridのcontactに登録 $sendGrid = new SendGrid(config('services.sendgrid.user.key’)); $sendGridService = new SendGridService($sendGrid); $sendGridService->saveUser($user); 設定を生成

Slide 50

Slide 50 text

ユーザー側のコントローラのコード // sendgridのcontactに登録 $sendGrid = new SendGrid(config('services.sendgrid.user.key’)); $sendGridService = new SendGridService($sendGrid); $sendGridService->saveUser($user); 設定を生成 設定を渡す

Slide 51

Slide 51 text

管理者側のコントローラのコード // sendgridのcontactから削除 $sendGrid = new SendGrid(config('services.sendgrid.admin.key’)); $sendGridService = new SendGridService($sendGrid); $sendGridService->deleteUsers([$id]);

Slide 52

Slide 52 text

管理者側のコントローラのコード // sendgridのcontactから削除 $sendGrid = new SendGrid(config('services.sendgrid.admin.key’)); $sendGridService = new SendGridService($sendGrid); $sendGridService->deleteUsers([$id]); 設定を生成 設定を渡す

Slide 53

Slide 53 text

DIのまとめ ■ DIとは、インスタンスの生成に必要な設定を引数で渡し てあげること ■ 設定を外から渡すことで、インスタンスの設定を状況に 合わせて指定できる

Slide 54

Slide 54 text

サービス DI サービス コンテナ インター フェース 自動注入 サービスコンテナ サービスプロバイダ

Slide 55

Slide 55 text

コードの状態 ■ DIを使うと、サービスを使う側でnewする回数が増える ■ 引数が3つや4つになったらその分だけnewしないといけない – 見通しが悪くなりそう ■ サービスコンテナを使おう $sendGrid = new SendGrid('services.sendgrid.user.key'); $sendGridService = new SendGridService($sendGrid);

Slide 56

Slide 56 text

サービスコンテナとは? ■ サービスをDIするのをサポートするツール ■ 様々なサービスを入れられるグローバルな配列 ■ Laravelに出てくる「app」はサービスコンテナのこと key value sendgrid SendGridService ga GoogleAnalyticsService payment StripeService

Slide 57

Slide 57 text

サービスプロバイダとは? ■ サービスコンテナにデータを生成する方法を記述する場所 ■ アプリケーションの初期化時にロードされ、 サービスコンテナに情報を入れていく ■ コマンドで作成すると間違いがない – php artisan make:provider プロバイダ名 ■ config/app.php で読み込むのを忘れないこと

Slide 58

Slide 58 text

サービスプロバイダとは? サービスコンテナ サービスプロバイダ アプリケーション データ登録 データ取得

Slide 59

Slide 59 text

サービスプロバイダとは? サービスコンテナ サービスプロバイダ アプリケーション register resolve

Slide 60

Slide 60 text

サービスプロバイダのコード class SendGridServiceProvider extends ServiceProvider { public function register() { $this->app->bind('sendgrid', function ($app) { $sendGrid = new SendGrid(config('services.sendgrid.user.key')); return new SendGridService($sendGrid); }); } }

Slide 61

Slide 61 text

サービスプロバイダのコード class SendGridServiceProvider extends ServiceProvider { public function register() { $this->app->bind('sendgrid', function ($app) { $sendGrid = new SendGrid(config('services.sendgrid.user.key')); return new SendGridService($sendGrid); }); } } key

Slide 62

Slide 62 text

サービスプロバイダのコード class SendGridServiceProvider extends ServiceProvider { public function register() { $this->app->bind('sendgrid', function ($app) { $sendGrid = new SendGrid(config('services.sendgrid.user.key')); return new SendGridService($sendGrid); }); } } key value

Slide 63

Slide 63 text

コントローラのコード // sendgridのcontactに登録 $sendGridService = resolve('sendgrid'); $sendGridService->saveUser($user); key

Slide 64

Slide 64 text

コントローラのコード // sendgridのcontactに登録 $sendGridService = resolve('sendgrid'); $sendGridService->saveUser($user);

Slide 65

Slide 65 text

サービスコンテナのまとめ ■ DIを使うと、コントローラでnewがいっぱい出てくる ■ サービスをDIするのをサポートするツール ■ サービスコンテナはサービスの入ったグローバルな配列 ■ サービスの生成方法についてはサービスプロバイダに 記述する

Slide 66

Slide 66 text

サービス DI サービス コンテナ インター フェース 自動注入 自動注入

Slide 67

Slide 67 text

Tips: 自動注入 ■ サービスコンテナに登録するときのキーをクラス名にす ると、ちょっとラクに書けます! ■ コントローラで引数に取ると、 そのままサービスが生成されます。 ■ 自動注入されるサービスは、 引数の型をもとに判断されます。

Slide 68

Slide 68 text

サービスプロバイダのコード $this->app->bind(SendGridService::class, function () { $sendGrid = new SendGrid(config('services.sendgrid.user.key')); return new SendGridService($sendGrid); });

Slide 69

Slide 69 text

サービスプロバイダのコード $this->app->bind(SendGridService::class, function () { $sendGrid = new SendGrid(config('services.sendgrid.user.key')); return new SendGridService($sendGrid); }); クラス名に

Slide 70

Slide 70 text

コントローラのコード public function store(Request $request, SendGridService $sendGridService) { // …ユーザーのDB保存処理 // sendgridのcontactに登録 $sendGridService->saveUser($user); // …メール送信 // …ビューの表示処理 }

Slide 71

Slide 71 text

コントローラのコード public function store(Request $request, SendGridService $sendGridService) { // …ユーザーのDB保存処理 // sendgridのcontactに登録 $sendGridService->saveUser($user); // …メール送信 // …ビューの表示処理 } アクションの引数で渡せる

Slide 72

Slide 72 text

インターフェース

Slide 73

Slide 73 text

ユースケース ■ 開発環境の場合はSendGridに通信したくありません。 ■ できれば送信されたメールの情報が ログに流れてほしいです。

Slide 74

Slide 74 text

ユースケース ■ ローカル環境では、SendGridServiceを使いたくない。 ■ メールを送らないだけなので、やるべきことはほぼ同じ。 ■ インターフェースを使いましょう

Slide 75

Slide 75 text

インターフェースとは? ■ クラスのやりとりの概要だけを表すもの ■ 実際にどのように行うかは規定しない ■ 今回だと、 – メール送信 – ユーザー作成 – ユーザー削除 ■ が該当します。

Slide 76

Slide 76 text

インターフェースとは? コントローラ SendGridService メール送ってね

Slide 77

Slide 77 text

インターフェースとは? コントローラ SendGridService メール送ってね Interface Fake

Slide 78

Slide 78 text

インターフェースのコード interface MailerService { function saveUser(User $user): void; function deleteUsers(array $users): void; function sendMail(Mail $mail): void; }

Slide 79

Slide 79 text

SendGridServiceのコード class SendGridService implements MailerService { // …各処理 }

Slide 80

Slide 80 text

SendGridServiceのコード class SendGridService implements MailerService { // …各処理 } インターフェース を設定

Slide 81

Slide 81 text

FakeMailerServiceのコード class FakeMailerService implements MailerService { function saveUser(User $user): void { // …ログ出力処理 } function deleteUsers(array $users): void { // …ログ出力処理 } function sendMail(Mail $mail): void { // …ログ出力処理 } }

Slide 82

Slide 82 text

FakeMailerServiceのコード class FakeMailerService implements MailerService { function saveUser(User $user): void { // …ログ出力処理 } function deleteUsers(array $users): void { // …ログ出力処理 } function sendMail(Mail $mail): void { // …ログ出力処理 } } インターフェース を設定

Slide 83

Slide 83 text

MailerServiceProviderのコード class MailerServiceProvider extends ServiceProvider { public function register() { if (env('app_env') === 'local') { $this->app->bind(MailerService::class, function () { return new FakeMailerService(); }); } else { $this->app->bind(MailerService::class, function () { return resolve(SendGridService::class); }); } } }

Slide 84

Slide 84 text

MailerServiceProviderのコード class MailerServiceProvider extends ServiceProvider { public function register() { if (env('app_env') === 'local') { $this->app->bind(MailerService::class, function () { return new FakeMailerService(); }); } else { $this->app->bind(MailerService::class, function () { return resolve(SendGridService::class); }); } } } envが開発環境 だったら

Slide 85

Slide 85 text

MailerServiceProviderのコード class MailerServiceProvider extends ServiceProvider { public function register() { if (env('app_env') === 'local') { $this->app->bind(MailerService::class, function () { return new FakeMailerService(); }); } else { $this->app->bind(MailerService::class, function () { return resolve(SendGridService::class); }); } } } MailerServiceに Fakeを設定

Slide 86

Slide 86 text

MailerServiceProviderのコード class MailerServiceProvider extends ServiceProvider { public function register() { if (env('app_env') === 'local') { $this->app->bind(MailerService::class, function () { return new FakeMailerService(); }); } else { $this->app->bind(MailerService::class, function () { return resolve(SendGridService::class); }); } } } envが開発環境 でないなら

Slide 87

Slide 87 text

MailerServiceProviderのコード class MailerServiceProvider extends ServiceProvider { public function register() { if (env('app_env') === 'local') { $this->app->bind(MailerService::class, function () { return new FakeMailerService(); }); } else { $this->app->bind(MailerService::class, function () { return resolve(SendGridService::class); }); } } } MailerServiceに SendGridを設定

Slide 88

Slide 88 text

コントローラのコード public function store(Request $request, MailerService $mailerService) { // …ユーザーのDB保存処理 // sendgridのcontactに登録 $mailerService->saveUser($user); // …ビューの表示処理 }

Slide 89

Slide 89 text

コントローラのコード public function store(Request $request, MailerService $mailerService) { // …ユーザーのDB保存処理 // sendgridのcontactに登録 $mailerService->saveUser($user); // …ビューの表示処理 } 型を インターフェース に変更

Slide 90

Slide 90 text

インターフェースのまとめ ■ (インターフェースを変更しない限りは)処理を変更せず に実装を差し替えることができる ■ 自動注入と組み合わせることで強力に

Slide 91

Slide 91 text

まとめ

Slide 92

Slide 92 text

なぜサービスコンテナは難しいのか サービス DI サービス コンテナ インター フェース 自動注入

Slide 93

Slide 93 text

サービスのまとめ ■ 見やすさ – 詳細な処理がコントローラに書かれていないので 見やすい ■ 再利用性 – サービスのインスタンスを生成すれば コピペしなくて済む ■ 保守性 – 似た処理が同じところに集まったので メンテナンスしやすい

Slide 94

Slide 94 text

DIのまとめ ■ DIとは、インスタンスの生成に必要な設定を引数で渡し てあげること ■ 設定を外から渡すことで、インスタンスの設定を状況に 合わせて指定できる

Slide 95

Slide 95 text

サービスコンテナのまとめ ■ DIを使うと、コントローラでnewがいっぱい出てくる ■ サービスをDIするのをサポートするツール ■ サービスコンテナはサービスの入ったグローバルな配列 ■ サービスの生成方法についてはサービスプロバイダに 記述する

Slide 96

Slide 96 text

Tips: 自動注入 ■ サービスコンテナに登録するときのキーをクラス名にす ると、ちょっとラクに書けます! ■ コントローラで引数に取ると、 そのままサービスが生成されます。 ■ 自動注入されるサービスは、 引数の型をもとに判断されます。

Slide 97

Slide 97 text

インターフェースのまとめ ■ (インターフェースを変更しない限りは)処理を変更せず に実装を差し替えることができる ■ 自動注入と組み合わせることで強力に

Slide 98

Slide 98 text

ご清聴 ありがとうございました