RACCOON TECH BLOG

株式会社ラクーンホールディングスのエンジニア/デザイナーから技術情報をはじめ、世の中のためになることや社内のことなどを発信してます。

OpenAPI、コードから書くかスキーマから書くか

みなさんこんにちは、川崎です。
OpenAPI Specification(以下OpenAPI Spec)をAPI仕様書を設計するフォーマットに採用するとき
この2つの選択肢があることはご存知でしょうか?

私はAPI仕様書を設計するフォーマットを決めるとき、OpenAPI Specをコードファーストまたはスキーマファーストとして採用するか非常に迷い、結果的にスキーマファーストを選択しました。
似たような悩みを持つ方は私以外にもいると思うので、今回はOpenAPI Specを採用したAPI仕様書におけるコードファースト、スキーマファースト両方のメリット・デメリットをご紹介しようと思います。

スキーマファーストとは

スキーマファーストはスキーマファイル(OpenAPIでいうとAPIが定義されたYamlもしくはJSON)からコードジェネレータを使ってコードを生成しAPIを開発する手法です。

代表的なコードジェネレータは

の二種類です。
それぞれのツールから生成されるコードには差がありますが、どちらもコード生成に使うテンプレートを変更すれば自分好みのコードを生成できるのでそこはあまり問題にならないかもしれません。
個人的には自分の使っている言語がカバーされていればどちらを選んでも問題ないと思います。

スキーマファーストのメリット

メリットはスキーマファイルがコードへの制約として機能する点でしょう。
一見煩わしく思うかもしれませんが、仕様をしっかり決めてから開発できると考えるとAPIのユーザーには良い影響を与えるはずです。

さらにコードとスキーマファイルが分離しているので、コードファーストと違ってAPI定義を変更した際にコードに変更が入りません。そのためアプリケーションのビルドサイクルへの影響も抑えられます。
schema-first

スケジュール管理においてはOpenAPIを定義してしまえば、サーバーサイドの開発と、APIを使うフロントエンドの開発を並列で行えることもメリットの一つでしょう。

スキーマファーストのデメリット

デメリットはスキーマから生成したコードが役に立たない、もしくはコード生成できないケースがあることです。
スキーマファーストで開発する際はコードジェネレータでスタブやAPIクライアントのコードを生成するのが一般的ですが、コードジェネレータによっては対応していないOpenAPI記述があったりします。

例えば代表的なコードジェネレータの一つであるOpenAPI GeneratorでOneOfの記述があるスキーマファイルをJavaのSpring用にコードを生成すると、OneOfで定義されたスキーマは生成できません。
これはバグとしてIssueが上がっているのでそのうち直るはずですが、それ以外にもbugのラベルがついたIssueが現状200件以上あるため、生成されたコードを全く書き換えずに使うのはやはり難しいでしょう。

// OneOfを使った場合UNKNOWN_BASE_TYPEとなる
default ResponseEntity<List<ProductResponse>> updateProductStock(@Valid @RequestBody UNKNOWN_BASE_TYPE UNKNOWN_BASE_TYPE)

とはいえ構文解読に問題がある場合を除けば、自分たちでコード生成のテンプレートをカスタマイズし、望んだ形のコードを生成することも可能です。

// テンプレートの例
{{#useBeanValidation}}{{>beanValidation}}{{/useBeanValidation}}  public {{>nullableDataType}} {{getter}}() {
    return {{name}};
  }

  public void {{setter}}({{>nullableDataType}} {{name}}) {
    this.{{name}} = {{name}};
  }
// 上記テンプレートから生成されたコード
public Long getProductCode() {
return productCode;
}

public void setProductCode(Long productCode) {
this.productCode = productCode;
}

また、そもそもコードジェネレータが対応していないフレームワークやライブラリを選んだ場合、スキーマファーストの恩恵を受けることが難しいです。

コードファーストとは

コードファーストはOpenAPI Specとコードを関連付けるためのアノテーション(またコメント)を付与したコードからスキーマファイルを生成しAPIを開発する手法です。
コード上にあるアノテーションやコメントをOpenAPI Specと関連付けるライブラリを導入する必要があります。

各言語でライブラリが公開されています。

コードファーストのメリット

コードファーストではコードとスキーマファイルの関連付け方さえ知っていれば、スキーマファーストより早くアプリケーションのAPI仕様書を公開できるメリットがあります。
そのため社内サービスにAPI仕様書を用意したい場合や、迅速なデプロイを必要とする際に有効なアプローチだと思います。

コードファーストは静的型付け言語を使っていればいい感じにエンドポイントやスキーマを生成してくれると思うかもしれませんが、やはりアノテーション等を使った人力での関連付け方が必要です。

// Javaにおけるエンドポイント定義の例
@Operation(
        description = "パスパラメータで指定した商品を取得する。",
        security = @SecurityRequirement(name = "dealer-api-key"))
@ApiResponses({@ApiResponse(responseCode = "200",
        description = "指定した条件を満たす商品の一覧")})
@GetMapping(path = "/ProductSets")
public ResponseEntity<List<ProductResponse>> getProductList(

「関連付けの方法を学ぶコスト>API定義を書くコスト」となった場合は、コードファーストを続けるか一度考えたほうがいいかもしれません。

コードファーストのデメリット

コードファーストのデメリットはAPI仕様書がコードと一体化していることです。もしコードの変更時にGithub ActionやJenkinsのジョブが実行されるような仕組みがあった場合、誤字を直すだけでも不必要なデプロイが行われる可能性があります。

また複雑なエンドポイントを定義しようとすると、コードがアノテーションだらけになって可読性が落ちてしまうのもデメリットだと思います。

@Operation(description = "ユーザーIDをもとに注文を取得する",
        security = @SecurityRequirement(name = "user-api-key")
        parameters = {
                @Parameter(name = "orderDateFrom", in = ParameterIn.QUERY, required = true,
                        description = "指定された日付以降の注文を取得します。",
                        content = @Content(schema = @Schema(type = "string", format = "date"))),
                @Parameter(name = "orderDateTo", in = ParameterIn.QUERY, required = true,
                        description = "指定された日付以前の注文を取得します。",
                        content = @Content(schema = @Schema(type = "string", format = "date")))},
@ApiResponses({@ApiResponse(responseCode = "200",
        description = "注文データ")})
@GetMapping(path = "/orders")
public ResponseEntity<List<ProductResponse>> getUserOrders(

両者に共通する気をつけるべきこと

スキーマファースト、コードファーストに限らずAPI仕様書を定義するときは仕様と実装を乖離させないようにしましょう。
必要であればDreddのようなAPI定義と実装のテストツールを使って、仕様と実装の乖離を防ぐのが良いでしょう。

# インストール
npm i -g dredd
# 実行
dredd ./apidoc.yml http://localhost:8080

#実行結果
request: 
method: GET
uri: /products
headers: 
    User-Agent: Dredd/14.0.0 (Linux 4.19.104-microsoft-standard; x64)
    Authorization: Bearer 6fd9c141a19cf4292c1e781cf78ea98f
...
expected: 
headers: 
    Content-Type: application/json

body: 
"{\"errors\":[],\"type\":\"tooManyRequest\",\"message\":\"API rate limit exceed. please try again latar.\"}"
statusCode: 429
...

さらにどちらも結局はOpenAPI Spec(Schemaの書式、OpenAPIで使える型など)を知っている必要があるのには変わりません。
なので既存のサービスに導入する際は、サービスのエンドポイントをOpenAPIで定義できる(しやすい)のかなどは調査する必要があります。

まとめ

両方のメリット、デメリットを鑑みてご自身のプロジェクトに合った選択肢を選んでいただくのが良いかと思います。

個人的な感想としては迷ったらスキーマファーストを選択するのが良いと思いました。
というのも、スキーマファーストはコード生成に用いるテンプレートを変えることで、
コードジェネレータから生成するコードを自分のプロジェクトに合う形に変化させることができる柔軟性があるためです。
コードファーストを選ぶ場合は、戻り値や引数の型からある程度自動的にスキーマの型を定義してくれるようなものを使わないとアノテーションの記述コストが高いように感じます。

ラクーングループでは一緒に働く仲間を大募集しています。ラクーングループでもOpenAPIを利用していますのでOpenAPIに詳しい方も、そうではない方もラクーングループのサービスを一緒に良くしていきませんか?もしよろしければこちらの採用ページをぜひご覧ください!

一緒にラクーンのサービスを作りませんか? 採用情報を詳しく見る

関連記事

運営会社:株式会社ラクーンホールディングス(c)2000 RACCOON HOLDINGS, Inc