Open68

LaravelでOpenAPIを利用する際のエコシステム

katzumikatzumi

モチベーション

LaravelでBaaSなAPI群を作成する所存。
クライアントもLaravelでフロントエンドからアクセスすることは想定していない。
APIは社内の複数サービスから利用するが、汎用的なプラットフォームとして社外利用も想定している。
扱うAPIの特性としてはざっくり以下

  • 完全ステートレスなAPIではなく、リクエストIDを発行してデータ蓄積からデータを加工・出力まで行う
  • 蓄積するデータの項目数が多く数十となり日別実績データも扱うので結構なデータ量となる
    データ項目が多く、相関チェックでのバリデーションもある
  • ベースとなる基本I/Fがあり、基本的な区分値は複数APIで利用する。扱うデータ種類によってAPIのエンドポイントをかえる
    データ種類は30Over
  • 3年ごとにAPIのバージョンが変わり、バージョンの管理も必要となる
    新しいバージョンのI/F公開からサービスインまでの時間的な制約が大きい(Draftは-2Month、ベータ版で利用できるようになるのは-1Monthぐらいと短期)

API仕様を公開共有するにあたり、標準仕様としてOpenAPIをベースで考える。
ただOpenAPI自体はバージョンによって記述方法が異なったり、関連するエコシステムが多数存在する為に、書き味が違ったり周辺ツール(主にLaravel)との整合性が出てくるので調査が必要。

調査を行う上で、以下の視点で掘り下げを行う

  • Stubサーバー構築
    I/F公開からベータ版利用までにクライアント側で利用を想定
  • 仕様書とコードとの整合性担保
  • テスティング
  • 開発サイクルへの組み込みやすさ
katzumikatzumi

https://zenn.dev/wim/articles/prism_mock_server_from_swagger

モックサーバーはPrismが使えそう。
examplesを複数書いて、クライアント側から指定することができるのは良さそう。
リクエストIDを受け付けてコードを発行して、次回リクエスト時にコードをリクエストに指定してもらいたいけれど、コードの生成とかは流石に難しいよね?

katzumikatzumi

https://zenn.dev/beijaflor/articles/d7664ce9e7976b

フロントでは扱う予定はないけれど参考まで
型定義は大変だよねーと思う。

protoファイルまでにしたら、型まで扱えるっぽいけれどOpenAPIはどうなんだろう?
gPRCだったり、GraphQLだといい感じにI/Fかけるかもだけれども、クライアント側の実装難易度もあがりそうなので、社外公開も考えるとちょっと難しそう

katzumikatzumi

https://techblog.zozo.com/entry/openapi3/go

goでの仕様書からコード生成する開発フローのイメージを完結にまとめてある
Laravelでクリーンアーキテクチャ採用しているので、コード生成うまくいくんだろうか?

OpenAPI Generatorを後で調べる

katzumikatzumi
原因は、既にプロパティ名とexample値が同一のobject型のプロパティがあることでした。
どうやら、OpenAPI Generatorはモデル自動生成時に同一のものがある場合は、YAMLファイル上でより上に定義されたものをDRYに生成してくれるようです。
ちなみに、あえてDRYにしたくない場合は、$refを使ったり、exmaple値を異なるものにすることでも回避できます。

自動生成する際にも、書き方を調整しないとうまく期待通りにならないのかー
まあ自動生成だから仕方がないか

katzumikatzumi

https://tech-blog.optim.co.jp/entry/2022/01/13/100000

providersとして登録し、スキーマの定義を行えば自動でスキーマを出力してくれる便利なツールとなります。

クラス属性を記述してくタイプ

このライブラリではスキーマは別ファイルで定義しなければなりません。

自動でModelまで作成してくれるコマンドが用意されていますが、今回はそれを利用せずに実装します。

自動でModelまで定義されると、今回の利用イメージとはマッチしそうにないので、別フアイルで定義するのが良さそう。
リクエストクラスと、レスポンスクラスに定義情報をDSLっぽくかけるのかな?

katzumikatzumi

試した感じ、コメント内でannotationではくattributeで書くのは未来を感じた
ただスキーマを専用のクラスに書かないといけなくてプロダクションコードとのマッピングが出来なくてイマイチ。
SchemaFactoryを継承する必要があるけれど、リクエストクラスではFormRequestを継承するけれど多重継承できない。。
インターフェースにしておいてくれれば良さそう。
また各プロパティーをFactoryのbuildで一箇所にまとめるのではなく、プロパティのattributeに書けると嬉しい
今後のバージョンアップに期待したい

katzumikatzumi

https://qiita.com/amuyikam/items/e8a45daae59c68be0fc8

周辺ツールをカテゴリにまとめてくれている

API Specificationを先に書くことによって、フロントエンドとサーバサイドの同時開発ができる、コードの雛形の生成ができる、APIドキュメントを別途用意・メンテする必要がなくなるというメリットが享受できます。

今回のイメージとしてはAPI Specificationを先に書くパターンがマッチしているけれど
yamlで書くのがいいか、コードを書きつつソースに近いところ(Laravel Open APIだとattribute)で書くのがいいのか?
ここが悩みのタネになる。どちらがプロジェクトにマッチするか?
コード自動生成は個人的に懐疑的だが興味がある。手直ししたあとにドキュメントへの反映ってどうなんでしょう?

katzumikatzumi

https://nextat.co.jp/staff/archives/253

OpenAPIドキュメントでAPI実装を検証する

こちらのアプローチも正解なような気がする。

https://github.com/thephpleague/openapi-psr7-validator

これはプロダクションコード側のvalidatorかな
どっかでファイル読み込むからパフォーマンス的に気になるという記事を見た気がする。

katzumikatzumi

https://blog.asial.co.jp/2025

apidocみたいに外部ファイルではなく、Docコメントからアウトプットするパターン

L5-Swagger

以前はswagger-phpを使っていたけれど、ほとんど同じかな?
OpenAPIのバージョンが違うぐらい?

katzumikatzumi

内部的にはswagger-phpを使っていた。
artisanコマンドから呼び出せるのと、APIの公開(Swagger-UI)をやってくれる感じだった。
Redocがいいなー

katzumikatzumi

.envに L5_SWAGGER_GENERATE_ALWAYS をtureにしてページをリロードするだけで最新の状態を確認できるのが、なかなか体験的に良かった。
Redocの方が見た目が好きだけれども、書いた内容確認するのにはSeagger-UIでもそんなに変わらない。OpenAPIの対応バージョン的にも問題ない

katzumikatzumi

L5-Swaggerをベースにローカルの検証をして、外部公開用に出力したspecをCIでRedoc化してS3 Webホスティングする方法に落ち着いた

katzumikatzumi

https://stoplight.io/studio/

フルスクラッチで書くなら専用クライアントがあるといいよね。
書き味を試してみたい
ある程度書いてみてアウトプットして構文を覚えるのも良さそう。

katzumikatzumi
katzumikatzumi

swagger-phpでattributeで書く分にはIDEやphpstanとかの恩恵も受けられるので、専用のlinterが必要には感じなくなった。

katzumikatzumi

ReDocで文書化するのには問題なかったが、モックサーバーとして動かす際には厳密にチェックする必要があったのでlinterは必要だった

katzumikatzumi

出力するSpecの精度を高める為にこちらもGithub actions上でlintするようにした

katzumikatzumi

https://gift-tech.co.jp/articles/structured-openapi-schema/

ファイルを分割してスキーマを管理する

L5-Swaggerを使って書くと必然にスキーマが別ファイルになるイメージだと思うけれど
直接書く場合は、ここらへんを自分で管理しないといけないということか

スキーマを結合する

これもできるのね。
最終的に1ファイルにまとめて公開という流れが良いよね。

katzumikatzumi

https://github.com/OpenAPITools/openapi-generator/blob/master/docs/generators/php-laravel.md

generatorのLaravelサポート状況
ここらへんは実際試してみないとわからないよね。

あんまり記事がないから試している人も少なさそう。
標準的なLaravelの書き方なら使えるのかな?
こういう場合にフルスタックなフレームワークだとコード生成のツールはバージョン上がっていたりすると大変だよね。

クリーンアーキテクチャを採用しているプロジェクトだとレールを外れているので、やるまえから微妙っぽさが漂う。

katzumikatzumi

やはりDDDのディレクトリ構成にマッチしなかった。
リクエストクラスとレスポンスクラスも自動生成までは作成されないっぽい
Controllerに直接validationが入る感じ。Laravelの対応バージョンも古い。

katzumikatzumi

関連ツール一覧
https://openapi.tools/

バージョンと対応言語をまとめてくれているだけでもありがたい
なんか前見たときよりも種類が増えている気がw

新しいツールを探すときはウォッチしても良さそう

katzumikatzumi

条件付き必須は検討中っぽい
https://github.com/OAI/OpenAPI-Specification/issues/256

他のドキュメントでは表現できるものはあるのかな?

katzumikatzumi

https://eng-blog.iij.ad.jp/archives/6813

しかし、残念ながら、今後は状況が変わるかもしれません。 discriminatorキーワードは現在策定中のOpenAPIの次期バージョンv3.1にて、非推奨となることが予定されているのです(OpenAPI v3.1 and JSON Schema 2019-09)。 v3.1はJSON Schemaの最新の仕様への準拠を目指しており、OpenAPI独自の拡張を撤廃する方向で仕様が検討されています(Update Schema Objects to JSON Schema Draft 2019-09)。 その一環として、discriminatorも非推奨となるようです。

ただ、discriminatorが使えなくなることによって、上述の問題が発生することは把握されており、非推奨化の見直しに関する議論も行われているようです(Deprecate discriminator?)。

discriminatorは今の所使わない方が良さそうかな。

katzumikatzumi

swagger-phpでattributeで書けるじゃん!
IDEのサポートも受けられるし、これでいいのでは?
https://zircote.github.io/swagger-php/#_2-update-your-code

katzumikatzumi

こちらを採用した。
Enum項目をattribute定義する際にPHP8.1のEnumと親和性があるかなーと思ったけれど、現状ではEnum定義を手動で書く必要があった。
swagger-php側でenumのクラスを指定したらいい感じにvaluesを展開してくれないかな。。
swagger-phpというよりもPHPのAttributeの仕様的な制約がある感じだけれども。
PHP8.2になるとある程度救えるか?

katzumikatzumi

https://github.com/danielgtaylor/apisprout

basePathが指定できた

apisprout -p 8080 --add-server http://localhost:8080/api --validate-server api-docs.json
katzumikatzumi

Prefer: example=hoge でexmapleを変更出来る模様
未指定の場合は複数のexampleがランダムでレスポンスされるっぽい
Prefer: code=404 とかは使えないっぽい?

katzumikatzumi

--validate-request オプションでリクエストのチェックも行ってくれる
間違ったパラメータだと該当のスキーマを表示してくれる!便利!

katzumikatzumi

Prefer: status=409 で大丈夫だった。
ここらへん微妙にツールで違うな

--validate-request でこちらの書き方が不味いのか invalid memory address or nil pointer dereference が発生しやすい。
あんまりメンテナンスされていないっぽいので安定してないのかもしれない

katzumikatzumi

mockサーバー探索の旅。
https://github.com/jormaechea/open-api-mocker

$ref があると駄目っぽい?

[FATAL] Error while mocking schema: Expected a value of type `undefined | {parameters,responses,requestBody}

って言われる

katzumikatzumi
      parameters:
        - $ref: '#/components/parameters/HogeRequest'

があると駄目っぽい。
ReDocとかでは変換できているから問題ない記法っぽいけれど。。

katzumikatzumi

大変申し訳ございません。
components側の記載内容に誤りがありました mm
正しくした所、問題なく動きました。
swagger-ui側のvalidationは単体のコマンドってあるのかな?

katzumikatzumi

apisproutみたいにyml若しくはjsonをhttp経由で指定できるとうれしいなー。
ローカルファイルの変更監視はあるけれど、l5-swaggerの自動生成モードはローカルファイルを書き換えてくれない。

katzumikatzumi

swagger-php以外でattributeで定義できるcomposerがあった。
https://github.com/uderline/openapi-php-attributes

refを RefSchema::class クラス文字列で参照できるのいいな。少し直感的になる
オブジェクトをnestした定義を書く時にどれくらい直感でかけるだろう?
swagger-phpだと new Property しないといけなく(他に書き方ある?)、 Constant expression contains invalid operations と怒られるのがなくなると嬉しいが。

katzumikatzumi
katzumikatzumi

laravel-openapi-validatorを軽く触ってみた。
既存のテストと遜色なくテストできそう。
あえてメソッドチェーンにしなくても良い。設定もconfigのみ。
リクエストのエラーになるパターンの場合もvalidationを無効化できる
ドキュメント読んだ感じではspectatorが一番やれることが多そう。
勝手にテストされるのではなく、

assertValidRequest
assertValidResponse

といったassert系のメソッドが生やされるので、意図がわかりやすくなりそう。

katzumikatzumi

laravel-openapi-validatorを採用してみた。
リクエストこちらは設定後に勝手にリクエストとレスポンスが評価される。
コントローラーテストの品質の担保されている安心感がある。
残念な所として

https://github.com/kirschbaum-development/laravel-openapi-validator/blob/dev/src/ValidatesOpenApiSpec.php#L222-L233

勝手にAuthorizationを書き換えてしまう所。Overrideしてしまえば解決できたけれど、設定済みの場合は書き換えないでほしい

katzumikatzumi

mergeされました。
現状特に課題なしに使えていてgood!

katzumikatzumi

APIのりナリオテストが出来るツールを探している
postmanはopenapi specが読み込めたが、そこから環境変数の埋め込みも含めて手直ししないといけない。
シナリオをflowsで定義してみたが、newmanでCI実行できなさそう

他のツールでも良いのがあるのだろうか?
https://tech.connehito.com/entry/2017/05/02/152238

自前で全部書くのも何なんだし。openapiで書いているので、postmanみたいに一度流し込んでから仕様を満たしたパターンでシナリオを書きたい

katzumikatzumi

APIテストのアプローチ(種類)
https://parasoft.techmatrix.jp/what-is-api-testing-and-are-you-doing-it-right-2
どこを必要とするか?

コントラクトテスト

laravel-openapi-validatorでPHPUnit(Controllerテスト/コンポーネントテスト)ではできているけれどE2Eでもその仕様にあっているか?確認したい。
パターンによってレスポンスが変わってくる、その際にレスポンスの形式がspec通りかみたい。

コンポーネントテスト

PHPUnitである程度カバーしているけれど、ユースケースやビジネスルールはモックしてしまうので、厳密なテストにはなってない。
featureテストでモック無しでやればいいけれど

シナリオテスト

流石にここは、E2Eでテストを行うべきか。
Controllerのテストと実際にAPI呼び出した場合のパラメータの扱いが微妙に違うケースがある。
レスポンスを引き継いで次のAPIを呼び出すということをやりたい

katzumikatzumi

なんかAPI testingツールだけでもう一本スクラップが書けるような気がしてきたw

https://techblog.zozo.com/entry/test-api-with-karate
https://zenn.dev/reflex4qa/articles/6ef95c0956d626

karateが良さそう。
凄い読みやすい。DSLの力か。
OpenAPI specと絡ませてコントラクトテストってできないかな?
DSLで自動生成してくれても嬉しいけれど、scenarioのリクエストが正しい範囲内でレスポンスか?というのを透過的にチェックしてくれれば良さそう。