この記事はスターフェスティバル Advent Calendar 2025 23 日目の記事です。
カプセル化について思いを馳せるタイミングがちょこちょこあるので、今回は時間の許す限り API 設計をする際に個人的に意識していることを書いていこうと思います。
API?
この記事でいう API は WebAPI というよりはクラスのパブリックメソッドであるとかそっちがメインです。
こういったインタフェースのメソッド群や
<?php interface HogeInterface { public function doSomething(string $hoge): bool; }
クラスのパブリックプロパティ, メソッドなどを指して API と呼んでいます
<?php class Fuga { public int $value; public static function doSomething(); }
逆に private なものは API とは考えません
<?php class Piyo { private string $piyo; private function doSomething(); }
なぜ API 設計?
プログラミングにおいてAPIの設計はとても大切です。
API を設計する目的の1つは、ユーザー(そのAPIを扱う別の開発者)に対して何を公開し、何を隠蔽するのかという情報管理だと考えていますが、この情報管理は凝集度の向上や結合度の低下、複雑性の隠蔽などに効いてきます。
そのため、 API 設計が適切にされていないと関心を同一とする処理が複数の箇所に散らばったり、いわゆるドメイン貧血症みたいな状態になったりなど保守性が落ちていきます。
<?php class Cart { public array $items; // ... } class CartService { public function order(/** ... **/) { // いろんな箇所でこんな感じの処理を書きがち $totalAmount = 0; foreach ($cart->items as $item) { $totalAmount += $item->price; } // ... } }
なぜこれらが良くないのかという点についてはここでは説明しませんが、好ましい API を保つために意識していることをヒョイヒョイと書いていきます
意識していること
ユーザーにどう使わせたいか?
Tell, Don't Ask の原則を守り、凝集度や結合度を適切なレベルに維持することは重要です。そのために僕が意識しているのは「このクラスをユーザーにどう使わせると便利か?」という点です。
この意識を持たずに手続き的に実装を進めてしまった結果、以下のような API を作ってしまったことが過去にあります。
<?php class Cart { public function getItems(): array { /* ... */ } public function setItems(array $items): void { /* ... */ } }
このような API になってしまうと Cart の中身を操作する方法をユーザーが理解し、そのための処理を書く必要が出てきます。
<?php // 呼び出し側でカートの操作をする $items = array_filter( $cart->getItems(), fn($item) => !$item->getProductId() === $productId ); $cart->setItems($items);
単純にクラスの使い勝手が悪くなりますし、クラスの役割が小さくなり単なるデータの入れ物になってしまいがちです。
しかし「どんな使い方ができたら便利だろう」という意識で考えると僕の場合は以下のような API を思い付きます。
<?php class Cart { public function removeItem(string $productId): void { $this->items = array_filter( $this->items, fn($item) => !$item->getProductId() === $productId ); } }
これであれば呼び出し側は単に removeItem() を呼び出せば終わりますし、結果的に Tell, Don't Ask の原則にも適い、凝集度の向上・複雑性の隠蔽に繋がっています。
Getter を生やすことを過度に避けない
Tell, Don't Ask は良い原則ですが、極端に解釈をした結果 Getter は常に悪いアイデアであるという論をたまに見かけます。というか僕も一時期そう思っていました。
しかし、過度に Getter を避けてしまうとモジュール間の結合度が必要以上に上がってしまうリスクがあります。
僕の考えでは Getter が害を生むのは「そのクラスに実装すべき処理が、 Getter と共に呼び出し元で実装されている」場合です。また、そのリスクが上がってしまうためなるべく Getter を避けようという話だと理解しています。
これ自体は共感できますし、今でも安易に Getter を生やさないように気をつけています。しかし、ここから飛躍して「とにかく Getter を作ってはいけない」と考えてしまうと前述の結合度のリスクにつながります。
極端な例をあげると以下のようなコードになります。これは、 getEmail() という Getter を避けた上でメール送信がしたいといった状況です。
<?php class User { public function sendWelcomeEmail(Mailer $mailer): void { $mailer->send($this->email, 'Welcome!'); } }
こうなると User から Mailer への不要な依存が生まれてしまいますし、もしメールの内容に User 以外の情報が必要になったら引数が増え、結局その引数に Getter が必要になるかもしれません。
もう少し工夫をして新しい抽象概念の導入などを行えば Getter 自体をなくした上で過度な依存を避けることも可能ですが、であれば素直に getEmail() がある方がシンプルで自然に思います。
<?php $mailer->send($user->getEmail(), 'Welcome!');
そのため、 Tell, Don't Ask を意識して凝集度を高く維持するというのを前提にした上で API として自然な範囲で Getter を使用するというのが良いと考えています。
余談ですが、以下のように計算結果を返す Getter はよく、プロパティをそのまま返す Getter は良くないといった話もたまに聞きますが、どちらも変わらないと考えています。
<?php class User { public function getFullName(): string { return $this->familyName . $this->firstName; } }
メソッドの中で計算しているかどうかは API のユーザーからしたら関係がありません。
特定のユースケースを前提としない
A Philosophy of Software Design という書籍では深いモジュールという概念が紹介されており、「小さなAPIで多くの仕事をこなせる」ことを1つの特徴としてもっています。これは良い API の特徴の1つです。
もし API が特定のユースケースを前提としてしまうと、個々のシグネチャは小さくなったとしても、できる仕事も一緒に小さくなってしまいます。
メール送信を行うクラスにメールの種類ごとのメソッドが生えている状態だと例としてわかりやすいでしょうか
<?php interface Mailer { public function sendUserRegistered(string $to); public function sendDelivered(string $to); // ... }
この場合、ユースケースが増えるたびにメソッドを増やす必要があり、一つのメソッドにつき1つのユースケースにしか対応できません。これが直ちに悪いことであるとは言えませんが、メソッドの数が増えるということ自体は学習コストや認知負荷の向上に繋がります。 テスト時の Mock も少しごちゃつくかもしれません。
こういった場合、僕は特定のユースケースを前提とせず、ユースケースに対してオープンな API を検討することが多いです(Mailerだと自分で実装することあまりないな...w)
<?php interface Mailer { public function send(string $to, MailContent $content); }
こうすることで、1つのメソッドでさまざまなユースケースに対応できるようになり、小さなAPIで多くの仕事をこなせる状態に近づきます。
おわりに
ということで時間を過ぎたのでここらへんで切り上げたいと思います。 個人的には「ユーザーにどう使わせたいか?」が一番大切な気がしてます。みんなも良い API を作るために意識してることがあれば教えてください!
