Slide 1

Slide 1 text

設計の考え方 インターフェースと腐敗防止層編 2024/06/22 PHP カンファレンス福岡 2024

Slide 2

Slide 2 text

所属:株式会社ウィルゲート 登壇: 寄稿: 岡田 正平/おかしょい X: @okashoi GitHub: @okashoi

Slide 3

Slide 3 text

本セッションは以下を満たす人を想定聴講者としています • PHP のコードは一通り書ける(フレームワーク上でも可) • 機能追加等によって、実装上の設計を考える必要がある (考えたことがある) • クラスベースのオブジェクト指向プログラミングにおける 「継承」等の挙動はわかる おことわり

Slide 4

Slide 4 text

「腐敗防止層」という考え方 • 原典から簡略化して、コンセプトだけ説明 • システムを長生きさせるために意識したいこと このセッションで伝えること 出典: Eric Evans.『エリック・エヴァンスのドメイン駆動設計』(p.376). 翔泳社. Kindle 版.

Slide 5

Slide 5 text

• ライブラリを使うときに何を考慮するか • PHP であれば Composer、JavaScript であれば npm など でインストールするもの 考えてみよう

Slide 6

Slide 6 text

php-ulid というライブラリを使ったコード • ULID とは全世界で一意かつソート可能な識別子 • 注:php-ulid であることに特に意味はない (ライブラリを使ったコードなら何でもよかった) https://github.com/robinvdvleuten/php-ulid 課題設定 1

Slide 7

Slide 7 text

① そのまま php-ulid を使った例

Slide 8

Slide 8 text

Slide 9

Slide 9 text

② 1 枚薄い wrapper を噛ませた例

Slide 10

Slide 10 text

② 1 枚薄い wrapper を噛ませた例

Slide 11

Slide 11 text

② 1 枚薄い wrapper を噛ませた例

Slide 12

Slide 12 text

② 1 枚薄い wrapper を噛ませた例

Slide 13

Slide 13 text

どうしてわざわざそんなことを......?

Slide 14

Slide 14 text

そのまま使った例(ケース①)の現状 php-ulid User uses

Slide 15

Slide 15 text

User 以外にも ULID で採番していくとこう php-ulid User uses Post Comment …… uses uses uses

Slide 16

Slide 16 text

「そのとき」はやってくる • ライブラリのバージョンアップ • ライブラリに深刻な不具合が見つかった • ライブラリがメンテナンスされなくなった • 別のライブラリの方が性能が良いことが分かった ......etc. そしてある日...... ※あくまで例えなので php-ulid がそれに該当するという話ではない

Slide 17

Slide 17 text

影響範囲は利用箇所すべてに及ぶ php-ulid User uses Post Comment …… uses uses uses

Slide 18

Slide 18 text

影響範囲は利用箇所すべてに及ぶ php-ulid User uses Post Comment …… uses uses uses

Slide 19

Slide 19 text

wrapper を噛ませていた場合(ケース②) MyUlid User uses php-ulid uses

Slide 20

Slide 20 text

同様に増えていっても MyUlid User uses php-ulid uses Post Comment …… uses uses uses

Slide 21

Slide 21 text

同様に増えていっても MyUlid User uses php-ulid uses Post Comment …… uses uses uses

Slide 22

Slide 22 text

影響は wrapper までに留められる(※) MyUlid User uses php-ulid uses Post Comment …… uses uses uses ※ただし wrapper のインターフェース  が変わらない前提

Slide 23

Slide 23 text

ライブラリに限らず、以下についても当てはまる • フレームワークの機能 • データベースとのやりとり • 外部システムとのやりとり(Web API 等経由) この役割が「腐敗防止層」

Slide 24

Slide 24 text

影響は wrapper までに留められる(※) MyUlid User uses php-ulid uses Post Comment …… uses uses uses ※ただし wrapper のインターフェース  が変わらない前提

Slide 25

Slide 25 text

影響は wrapper までに留められる(※) MyUlid User uses php-ulid uses Post Comment …… uses uses uses ※ただし wrapper のインターフェース  が変わらない前提

Slide 26

Slide 26 text

ここで言う「インターフェース」には 2 つの意味がある 広義のインターフェース = API(Application Programming Interface) 狭義のインターフェース = プログラミング言語の機能として提供される抽象型 「インターフェース」とは

Slide 27

Slide 27 text

API(Application Programming Interface) • 関数であれば引数と戻り値など • 作り手と使い手の間の「決めごと」 • 「仕様」と言い換えてもよい 広義のインターフェース

Slide 28

Slide 28 text

プログラミング言語の機能として提供される抽象型 狭義のインターフェース

Slide 29

Slide 29 text

設計における重要な判断 以下のようなインターフェースは避けたい • 依存ライブラリの変更が、利用する側にも影響してしまう • 実装の詳細を知らないと正しく使えない (広義の)インターフェースをどう定めるか?

Slide 30

Slide 30 text

所定の形式で HTTP リクエストを行う処理がある • 実際の HTTP リクエスト部分には Guzzle を用いる • wrapper を噛ませて利用者は Guzzle を意識しないよ うにしたい https://github.com/guzzle/guzzle 課題設定 2

Slide 31

Slide 31 text

GuzzleHttp\ClientTrait /** * Create and send an HTTP GET request. * * Use an absolute path to override the base path of the client, or a * relative path to append to the base path of the client. The URL can * contain the query string as well. * * @param string|UriInterface $uri URI object or string. * @param array $options Request options to apply. * * @throws GuzzleException */ public function get($uri, array $options = []): ResponseInterface { return $this->request('GET', $uri, $options); }

Slide 32

Slide 32 text

• 第三者が定める標準を利用できないか検討する • 自分たちで設計する場合は対象を「抽象化」する • 利用する側が、内部実装や依存ライブラリの詳細を 意識しなくても良いようにする • 公開する操作を使うものだけに絞る (広義の)インターフェースをどう定めるか?

Slide 33

Slide 33 text

• 第三者が定める標準を利用できないか検討する • 自分たちで設計する場合は対象を「抽象化」する • 利用する側が、内部実装や依存ライブラリの詳細を 意識しなくても良いようにする • 公開する操作を使うものだけに絞る (広義の)インターフェースをどう定めるか?

Slide 34

Slide 34 text

GuzzleHttp\ClientTrait /** * Create and send an HTTP GET request. * * Use an absolute path to override the base path of the client, or a * relative path to append to the base path of the client. The URL can * contain the query string as well. * * @param string|UriInterface $uri URI object or string. * @param array $options Request options to apply. * * @throws GuzzleException */ public function get($uri, array $options = []): ResponseInterface { return $this->request('GET', $uri, $options); } URI の文字列か、PSR に則っているので ここはそのまま使うという選択肢は充分ありえる

Slide 35

Slide 35 text

PHP-FIG という組織が定める「標準勧告」 • オートローディングに関する仕様(PSR-4) • コーディングスタイル(PSR-1、PSR-12) • インターフェース(上記以外) あくまで第三者組織による「勧告」であり、強制力を 持つものではない 補足:PSR(PHP Standards Recommendations)

Slide 36

Slide 36 text

補足:PSR(PHP Standards Recommendations) https://www.php-fig.org/psr/

Slide 37

Slide 37 text

• 第三者が定める標準を利用できないか検討する • 自分たちで設計する場合は対象を「抽象化」する • 利用する側が、内部実装や依存ライブラリの詳細を 意識しなくても良いようにする • 公開する操作を使うものだけに絞る インターフェースをどのように定めるか?

Slide 38

Slide 38 text

GuzzleHttp\ClientTrait /** * Create and send an HTTP GET request. * * Use an absolute path to override the base path of the client, or a * relative path to append to the base path of the client. The URL can * contain the query string as well. * * @param string|UriInterface $uri URI object or string. * @param array $options Request options to apply. * * @throws GuzzleException */ public function get($uri, array $options = []): ResponseInterface { return $this->request('GET', $uri, $options); }

Slide 39

Slide 39 text

このような wrapper を噛ませても get($url); return $response->getBody()->getContents(); } }

Slide 40

Slide 40 text

実際に使おうとすると client->get($url); } catch (GuzzleException) { // なんらかのエラー処理 } // 後続処理 } }

Slide 41

Slide 41 text

まだ GuzzleHttp へ直接依存してしまっている client->get($url); } catch (GuzzleException) { // なんらかのエラー処理 } // 後続処理 } }

Slide 42

Slide 42 text

GuzzleHttp\ClientTrait /** * Create and send an HTTP GET request. * * Use an absolute path to override the base path of the client, or a * relative path to append to the base path of the client. The URL can * contain the query string as well. * * @param string|UriInterface $uri URI object or string. * @param array $options Request options to apply. * * @throws GuzzleException */ public function get($uri, array $options = []): ResponseInterface { return $this->request('GET', $uri, $options); } GuzzleHttp 固有の例外なので 腐敗防止層では適切にハンドリングして • PHP 組み込みの例外に変換 • 独自定義の例外に変換 • 正常系として適切に処理 等の対応をしておきたい

Slide 43

Slide 43 text

• 第三者が定める標準を利用できないか検討する • 自分たちで設計する場合は対象を「抽象化」する • 利用する側が、内部実装や依存ライブラリの詳細を 意識しなくても良いようにする • 公開する操作を使うものだけに絞る インターフェースをどのように定めるか?

Slide 44

Slide 44 text

GuzzleHttp\ClientTrait /** * Create and send an HTTP GET request. * * Use an absolute path to override the base path of the client, or a * relative path to append to the base path of the client. The URL can * contain the query string as well. * * @param string|UriInterface $uri URI object or string. * @param array $options Request options to apply. * * @throws GuzzleException */ public function get($uri, array $options = []): ResponseInterface { return $this->request('GET', $uri, $options); }

Slide 45

Slide 45 text

GuzzleHttp\ClientTrait /** * Create and send an HTTP GET request. * * Use an absolute path to override the base path of the client, or a * relative path to append to the base path of the client. The URL can * contain the query string as well. * * @param string|UriInterface $uri URI object or string. * @param array $options Request options to apply. * * @throws GuzzleException */ public function get($uri, array $options = []): ResponseInterface { return $this->request('GET', $uri, $options); } 確かに型情報としてはGuzzleHttpに依存していないが......

Slide 46

Slide 46 text

実態はライブラリ詳細 → wrapper を噛ませる場合は  ユースケースにあわせて  隠蔽しておきたいもの

Slide 47

Slide 47 text

• 第三者が定める標準を利用できないか検討する • 自分たちで設計する場合は対象を「抽象化」する • 利用する側が、内部実装や依存ライブラリの詳細を 意識しなくても良いようにする • 公開する操作を使うものだけに絞る インターフェースをどのように定めるか?

Slide 48

Slide 48 text

• 依存関係を逆転させられる • 具象(実装)を動的に切り替えられる • 継承とは別の切り口で多態性を実現できる ※単に「腐敗防止層」を実現するだけであれば必ずしも  使う必要はない (狭義の)インターフェースを使う利点

Slide 49

Slide 49 text

• 依存関係を逆転させられる • 具象(実装)を動的に切り替えられる • 継承とは別の切り口で多態性を実現できる ※単に「腐敗防止層」を実現するだけであれば必ずしも  使う必要はない (狭義の)インターフェースを使う利点

Slide 50

Slide 50 text

Laravel の config/session.php 具象(実装)を動的に切り替えられる https://github.com/laravel/framework/blob/02a0b5f6f83b13ea750f959364aefa324441a 354/config/session.php

Slide 51

Slide 51 text

具象(実装)を動的に切り替えられる https://www.php.net/SessionHandlerInterface

Slide 52

Slide 52 text

我々はこの端子の形を見て USB(Type-A)だと認識する 身近な抽象化、インターフェース

Slide 53

Slide 53 text

充電をするために両端の電気的特性を調べる必要はない → 利用者に詳細を意識させていないつくり 身近な抽象化、インターフェース

Slide 54

Slide 54 text

裏にある詳細を隠蔽する仕組み • HTTP • TCP/IP • VM • コンパイラ • ......etc. 利用者が専門知識を持たずとも便利に暮らせるのは インターフェースが適切に設計されているから 身近な抽象化、インターフェース

Slide 55

Slide 55 text

「腐敗防止層」という考え方 • 原典から簡略化して、コンセプトだけ説明 • システムを長生きさせるために意識したいこと このセッションで伝えること(再掲) 出典: Eric Evans.『エリック・エヴァンスのドメイン駆動設計』(p.376). 翔泳社. Kindle 版.