はじめに

PHPチームの有澤です。

今まで自分が業務していく中で、サードパーティのAPIを使うことが多いのですが、 「こういう設計であれば使いやすくて助かる」と思うことが出てきたので、本記事で共有します😊

本題

商品情報を保存できるシステムを開発するとします。

その際に、以下のような「商品登録API」を実装するとします。

PUT /api/product


{
    "name": "おにぎり",
    "description": "ご飯を三角形・俵形・球状などに加圧成型した食べ物である。",
    "price": 100
}

ここでわかるのが、APIを受け取ったシステムのDBスキーマは、以下の構造になっていそうだと予想できます。

Product テーブル

name description price
“おにぎり” “ご飯を三角形・俵形・球状などに加圧成型した食べ物である。” 100

これらを踏まえた上で、何も考えずにAPIを実装した場合、以下の図の実装になるかと思います。

1モデルを使いまわしたシステム図

1モデルを使いまわしたシステム図

Product モデルをRequest、Response、DBスキーマの構造に使っているため、1モデルを使いまわしているシステム設計になります。 大まかな流れとしては「Productモデル(おにぎり)をWebアプリに送信して、ProductモデルをDBに保存した後、保存したProductモデルが返ってくる」イメージです。

ちなみに、商品情報を保存できる機能は、現状のシステムで実現できますが、 以下の仕様変更を加えてシステム設計を破綻させます。

仕様変更点

  • データベースにProductを登録する際、登録日時(createdAt)も保存する
  • Responseに「システム独自のステータスコード(statusCode)」を追加する

上記の仕様変更をモデルに加えた場合、システムの図は以下になります。

破綻したシステム図

破綻したシステム図

モデルを変更したことにより、以下の余計な変更が入ります。

Product Modelの構造を変更した際の意図しない影響

  • RequestにcreatedAtやstatusCodeが入っている
  • ResponseにcreatedAtが加わっている
  • データベースにstatusCodeが保存されている

1モデルを使い回す実装をしていたため、上記の余計な変更が加わるのは実は自然だと思いますが、 仕様変更の内容の割にインパクトが大きく、今後の仕様変更に耐えられない短命なシステムになりそうです。

さらに、Product(おにぎり)を送信しているにも関わらず、Product(おにぎり)は「createdAt」、「statusCode」というプロパティを持っています。 システム都合のプロパティに見えますが、これらはProduct(おにぎり)が持つ必要があったのでしょうか?

この問題が起きるのは、1モデルを使いまわすことで意図しない箇所に余計な変更が加わるためです。 これを避けるためには、共通して使われていた1モデルを分離させる必要があります。

モデルを分離した場合、以下の図になります。

Httpをモデリングしたシステム図

Httpをモデリングしたシステム図

モデルは1つから3つに増え、「Http Request Model」、「Http Response Model」というモデルが新たに加わりました。

これらのモデルはHttpを通じてAPIを実行する際の知識を持ちます。 APIを利用するクライアントの操作感にも直結したり、このモデルの知識を知ればAPIリファレンスも作成することができます。

これにより、仕様変更の際に加わる知識を、どのモデルに持たせておけば良いか判断しやすくなりました。

1モデルを使いまわしていた場合、「保存APIを実行する際に、StatusCodeを送信する必要がある仕様」になりますが、 それを避けるのに、無理やりロジックを加えることでstatusCodeにあたるプロパティを送信しなくて済む実装も可能ですが、保守性は乏しくなり、システムは破綻します。

危険な構造になっている設計だと思いますが、意外と見ることが多いのは気のせいでしょうか💧

最後に

サードパーティのAPIリファレンスを読む際に、RequestとResponseのプロパティがまとめて書かれていることがあり、 とても使いづらく、疲労困憊してしまうことがよくありました。

この問題は「HttpRequestのモデル、HttpResponseのモデル、Product(実体)としてのモデル」として分離すれば、こんな事態にはならかったのだろうと予想しています。

(ちなみに、SwaggerにはModelという括りがありますが、本記事の方法では「RequestModel」、「ResponseModel」を作ることになります)

また機会があれば、「仕様変更で破綻したシステム」と「仕様変更に耐えたシステム」のソースコードを書いて、どのくらい効果があるものかを紹介できたら良いなと思います😊