その辺のエンジニアの雑記

外資SIer→零細受託→SaaSベンチャー→フリーランス

Go向けのOpenAPIコード生成ライブラリ(ogen)を使った所感

はじめに

今回はogenに限った話しというよりか、GoにおいてOpenAPIのコード生成を使った実装に関する感想になりますのでご留意ください。

コードの自動生成ができると何がいいのかというのは以下の通りです。

  • ただの作業になりがちなモデルの作成が自動化できる
  • 仕様書通りにモデルや API インターフェースが自動生成されるので、バグが入りにくい
  • 仕様書通りのリクエスト・レスポンスかどうかを簡単にバリデーションできる
  • その結果、ビジネスロジックに集中する開発ができる

出典:[Go] OpenAPI コード自動生成でビジネスロジックに集中する開発へ #echo - Qiita

導入背景

現在参画しているプロジェクトに関して、MVPで最低限の機能をもって一ヶ月半ほどでプロダクトを短期リリースしたいという要件がありました。

フロント/バックエンドの実装もろもろを自分含め二人でこなす必要があったため、あとから方針転換しても負債となりにくいようなものという基準はありつつ、開発速度をあげられるライブラリは積極的に導入していく方針にしました。

ことGoのAPIに関しても同様の方針で、今回の要件を考慮するとコード自動生成のメリットを大きく享受できると感じこちらを導入してみることにしました。

導入にあたってはoapi-codegenやopenapi-generator-goなども検討しましたが、以下のような記事を参考にしたところ、できるだけ厳密にOpenAPIの定義を再現してくれるogenが最近は良さそうという結論になりました。

zenn.dev

ldej.nl

ただ、カスタマイズ性の高さという観点ではoapi-codegenが良さそうかなという印象です。これはデメリットの部分で後述するのですが、手動で手直しできる仕組みが用意されていないのは少し辛いものがあったりします。

前置きが長くなりましたが、ここからは一ヶ月ほど使った上でのメリデメを挙げてみたいと思います。

メリット

はじめに引用したような既出のメリット以外の観点で考えてみます。

OpenAPIの定義が厳密なドキュメントになる

いままでOpenAPI自体は使うことはあれど、それが実際のコードを厳密に反映しているわけではありませんでした。

それが、ogenで生成されたコードの利用を規約とすることで、必然的にOpenAPIの定義が先に必要になるためドキュメントが充実し、かつコード自体もその定義を高い精度で反映したものになります。

あくまでニュアンス的な話なので正確には違いますが、TDDにおけるテストコードと実際のコードの関係のように、先に書いたAPI定義が生成されるコードと対応しており信頼できるドキュメントになるようなイメージです。

手戻りの少ない開発フローが自然と取りやすくなる

先に述べた通りOpenAPIの定義が先に必要になるため、メンバー間での実装イメージの齟齬によりレビューで大量の変更が発生したり、それに伴う実装の手戻りが発生したりといったケースが起こりにくくなります。

これは別記事でも書こうと思っているのですが、オンスケで開発を進めていくうえで重要だと思っていることの一つに、作業単位を細かく分けるというのがあります。

例えば一つのAPIを新規実装するとなった場合、プルリクエスト(以後PR)はAPI実装という一つのタスク単位で考えるのではなく、まず「テーブル設計」や「マイグレーションファイル作成」で一つのPR、「OpenAPIの定義」で一つのPR、「APIの実装」で一つのPR(もしくはAPIの処理が複雑な場合は各処理単位で一つのPRでも良いぐらい)というように細かく分割すると手戻りが発生しにくくなると思っています。

この「手戻りができるだけ発生しないような進め方」というのが、少なくともAPI実装の範囲においては慣れていない人でも自然に取りやすくなるのかなと個人的には思っています。

いい加減なOpenAPIファイルを書かなくなる

OpenAPIを基準としてしっかりと型付けしたコードを生成してくれるため、できるだけ詳細に記述しようという意識が生まれます。

例えばdescriptionなどのように書かなくても大きな問題がないようなキーに関しても、ogenを利用していると生成されるコードに変化があるため、こういった補足的な情報もできるだけ書こうという意識が生まれますし、そのためにドキュメントを読み込むので普通に勉強になります。

デメリット

yamlファイルを分割管理している場合の相性

OpenAPIを記述しているyamlファイルですが、当初はmulti-file-swaggerを使ってファイルを分割して管理していたのですが、これで生成されるファイルがどうもogenと噛み合わせが悪いようで、ogenで適切にコード生成ができない問題が発生しました。

swagger-cliに乗り換えることで解決はしたのですが、oneOfanyOfといったキーで$refを利用しているとやはりcomponentsへのパスが適切に生成されず、ogenでコード生成時にエラーが起きるという問題が発生しました。

おそらくこの辺りは回避策があると思われますが、一旦はプリミティブ型であるstringintegerなどを指定することで妥協しています。

このようにファイル分割して管理しているとちょくちょくエラーに遭遇して時間を取られることがあります

カスタマイズがしにくい

これはogenに限った話かもしれないのですが、上述したようなエラーでどうしても回避できない問題があった場合や、ライブラリ側でまだ対応しきれていないOpenAPIのルールを反映させたい場合など、生成されたコードを手直ししたいケースが発生することがあります。

少なくともogenの場合は生成されるコードの構造を細かく設定することはできなさそうなので、ogenで生成されたコードと自前のコードを繋ぐ中間の構造体を手動で用意してあげる必要がありそうです。

今のところは妥協案を取ることで実際に上記の方法は行っていませんが、ゆくゆくは必要になってきそうかなと思っているので、ここはライブラリ選定の一つの観点として要検討かもしれません。

まとめ

挙げたようなメリデメはそれぞれあるものの、間違いなく開発速度の向上に貢献していますし、スキーマ開発という観点でも得られるものは大きいので、現状はおおむね導入して良かったかなと思っています。

今後さらにプロダクトが大きくなったときに見えてくる問題点や、逆によりよかったと思えることも出てくる気がしているので、その際はまた追記していきます。