Share
Embed
設計の考え方 - インターフェースと腐敗防止層編 #phpconfuk / Interface and Anti Corruption Layer
2024/06/22 開催の「PHP カンファレンス福岡 2024」(https://phpcon.fukuoka.jp/2024/ )のトーク資料です。
詳細:https://fortee.jp/phpcon-fukuoka-2024/proposal/25f1e7b5-937f-4de9-a6de-b78fc9efd71c
Shohei Okada
June 22, 2024
Transcript
55 slides
-
① そのまま php-ulid を使った例 <?php use Ulid\Ulid; readonly class User
{ private function __construct( public string $id, public string $name, ) { } public static function create(string $name): static { $id = (string)Ulid::generate(); return new User($id, $name); } } -
<?php use Ulid\Ulid; readonly class User { private function __construct(
public string $id, public string $name, ) { } public static function create(string $name): static { $id = (string)Ulid::generate(); return new User($id, $name); } } ① そのまま php-ulid を使った例 -
② 1 枚薄い wrapper を噛ませた例 <?php namespace MyUlid; use Ulid\Ulid;
function ulid(): string { return (string)Ulid::generate(); } -
② 1 枚薄い wrapper を噛ませた例 <?php namespace MyUlid; use Ulid\Ulid;
function ulid(): string { return (string)Ulid::generate(); } -
② 1 枚薄い wrapper を噛ませた例 <?php namespace MyUlid; use Ulid\Ulid;
function ulid(): string { return (string)Ulid::generate(); } <?php use function MyUlid\ulid; readonly class User { private function __construct( public string $id, public string $name, ) { } public static function create(string $name): static { $id = ulid(); return new User($id, $name); } } -
② 1 枚薄い wrapper を噛ませた例 <?php namespace MyUlid; use Ulid\Ulid;
function ulid(): string { return (string)Ulid::generate(); } <?php use function MyUlid\ulid; readonly class User { private function __construct( public string $id, public string $name, ) { } public static function create(string $name): static { $id = ulid(); return new User($id, $name); } } -
User 以外にも ULID で採番していくとこう php-ulid User uses Post Comment ……
uses uses uses -
「そのとき」はやってくる • ライブラリのバージョンアップ • ライブラリに深刻な不具合が見つかった • ライブラリがメンテナンスされなくなった • 別のライブラリの方が性能が良いことが分かった ......etc.
そしてある日...... ※あくまで例えなので php-ulid がそれに該当するという話ではない -
影響は wrapper までに留められる(※) MyUlid User uses php-ulid uses Post Comment
…… uses uses uses ※ただし wrapper のインターフェース が変わらない前提 -
影響は wrapper までに留められる(※) MyUlid User uses php-ulid uses Post Comment
…… uses uses uses ※ただし wrapper のインターフェース が変わらない前提 -
影響は wrapper までに留められる(※) MyUlid User uses php-ulid uses Post Comment
…… uses uses uses ※ただし wrapper のインターフェース が変わらない前提 -
ここで言う「インターフェース」には 2 つの意味がある 広義のインターフェース = API(Application Programming Interface) 狭義のインターフェース =
プログラミング言語の機能として提供される抽象型 「インターフェース」とは -
プログラミング言語の機能として提供される抽象型 狭義のインターフェース <?php namespace Psr\Http\Client; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; interface
ClientInterface { /** * Sends a PSR-7 request and returns a PSR-7 response. * //(略) */ public function sendRequest(RequestInterface $request): ResponseInterface; } -
所定の形式で HTTP リクエストを行う処理がある • 実際の HTTP リクエスト部分には Guzzle を用いる •
wrapper を噛ませて利用者は Guzzle を意識しないよ うにしたい https://github.com/guzzle/guzzle 課題設定 2 -
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\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 に則っているので ここはそのまま使うという選択肢は充分ありえる -
PHP-FIG という組織が定める「標準勧告」 • オートローディングに関する仕様(PSR-4) • コーディングスタイル(PSR-1、PSR-12) • インターフェース(上記以外) あくまで第三者組織による「勧告」であり、強制力を 持つものではない
補足:PSR(PHP Standards Recommendations) -
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); } -
このような wrapper を噛ませても <?php use GuzzleHttp\Client; class MyHttpClient { public
function get(string $url): string { $response = (new Client())->get($url); return $response->getBody()->getContents(); } } -
実際に使おうとすると <?php use GuzzleHttp\Exception\GuzzleException; readonly class SomeService { public function
__construct(private MyHttpClient $client) { } public function getSomeDataFromWeb(string $url): string { try { $responseBody = $this->client->get($url); } catch (GuzzleException) { // なんらかのエラー処理 } // 後続処理 } } -
まだ GuzzleHttp へ直接依存してしまっている <?php use GuzzleHttp\Exception\GuzzleException; readonly class SomeService {
public function __construct(private MyHttpClient $client) { } public function getSomeDataFromWeb(string $url): string { try { $responseBody = $this->client->get($url); } catch (GuzzleException) { // なんらかのエラー処理 } // 後続処理 } } -
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 組み込みの例外に変換 • 独自定義の例外に変換 • 正常系として適切に処理 等の対応をしておきたい -
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\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に依存していないが...... -
裏にある詳細を隠蔽する仕組み • HTTP • TCP/IP • VM • コンパイラ •
......etc. 利用者が専門知識を持たずとも便利に暮らせるのは インターフェースが適切に設計されているから 身近な抽象化、インターフェース