Upgrade to Pro — share decks privately, control downloads, hide ads and more …

OpenAPI 3.0でmicroserviceのAPI定義を試みてハマった話

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers.

OpenAPI 3.0でmicroserviceのAPI定義を試みてハマった話

Avatar for Daichi Koike

Daichi Koike

June 22, 2019
Tweet

More Decks by Daichi Koike

Other Decks in Technology

Transcript

  1. 本日のおはなし • OpenAPI 3.0 の yaml ファイルを分割管理しようとし た • $ref

    で死にまくったので $ref を解決するツールをプロ ジェクトで自作した • 人間と機械でそれぞれ触る yaml を分けるという方法 をとった • OpenAPI のツールチェインはよく調べたほうがいい �2
  2. OpenAPI • Google、マイクロソフト、IBM などが立ち上げた Open API Initiative が提唱する REST API

    の I/F 記述フォー マット(OpenAPI Specification) • Swagger がベース • 3.0 と 2.0 で大分仕様が違っており、今回は 3.0 の話 • https://github.com/OAI/OpenAPI-Specification �3
  3. OpenAPI https://github.com/OAI/OpenAPI-Specification/blob/master/examples/v3.0/petstore.yaml �4 paths: /pets: get: summary: List all pets

    operationId: listPets tags: - pets parameters: - name: limit in: query description: How many items to return at one time (max 100) required: false schema: type: integer format: int32 responses: '200': description: A paged array of pets headers: x-next: description: A link to the next page of responses schema: type: string content: application/json: schema: $ref: "#/components/schemas/Pets"
  4. yaml で病むる • 膨大な数の API になるため yaml は分割して管理した い •

    評価のシステムの管理画面だけで 40 API ある(BFF 含めると 80 API)。管理系だけで1万行になった • yaml 分けたいけど entity とか再定義するのはミスも 多発するだろうしいい感じに使いまわしたい • 2.0 は yaml マージツールがある https:// www.npmjs.com/package/multi-file-swagger �11
  5. もともとはこう �15 paths: /users/{id}: get: - লུ - responses: "200":

    content: application/json: schema: $ref: "#/components/schemas/User" components: schemas: User: title: MyUser type: object properties: id: type: integer name: type: string
  6. components 以下を切り出している �16 paths: /users/{id}: get: - লུ - responses:

    "200": content: application/json: schema: $ref: "./entities.yaml#/User"
  7. merger で $ref 解決 �18 paths: '/users/{id}': get: - লུ

    - responses: '200': content: application/json: schema: title: MyUser type: object properties: id: type: integer name: type: string
  8. ちょっとまった • $ref で別ファイルの定義を参照するのは OpenAPI 3.0 の仕様にそもそもあるのでは?? • あります!! •

    ただしツールによってはサポートしていなかったり、多段 $ref はスルーされたりします • 他にも細かい制約があります �19 仕様: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#relative-references-in-urls
  9. kamakura/openapi.yaml �22 paths: /users/{id}: get: - লུ - responses: "200":

    content: application/json: schema: $ref: "./entities.yaml#/User"
  10. kamakura-bff/openapi.yaml �23 paths: /users/{id}: get: - লུ - responses: $ref:

    "../kamakura/openapi.yaml#/paths/ ~1users~1{id}/get/responses" response のステータスコード、entity の定義を $ref で 引き込みたい ~1 は / のエスケープ文字
  11. 多段 $ref 問題 • kamakura-bff/openapi.yaml -> kamakura/ openapi.yaml -> kamakura/entities.yaml

    この $ref を Swagger UI は解決できない • ツールによっては最初の $ref で解決できなくて死ぬも のもある �26
  12. $ref を解決できない 😇 �28 $ openapi-generator validate -i kamakura/openapi.yaml Validating

    spec (kamakura/openapi.yaml) Errors: -attribute paths.'/users/ {id}'(get).responses.responses.$ref is not of type `object` [error] Spec has 1 errors.
  13. 開発フロー • merger で生成された $ref 解決済みの yaml に対し て validation

    をかけている • 人間が触る yaml と機械が 触る yaml を分ける • テストで request/response の validate に使いたい ので pre-commit hook で $ref 解決済み yaml を生 成して commit している • invalid な yaml が生成されても validation をすり抜 けることもあるので Swagger UI で表示してみて問題 ないか確認するのが良い �29
  14. (再掲)kamakura-bff/openapi.yaml �31 paths: /users/{id}: get: - লུ - responses: $ref:

    "../kamakura/openapi.yaml#/paths/ ~1users~1{id}/get/responses"
  15. (再掲)kamakura-bff/openapi.yaml �32 paths: /users/{id}: get: - লུ - responses: $ref:

    "../kamakura/openapi.yaml#/paths/ ~1users~1{id}/get/responses" これ paths 以下を $ref で引き込めばいいのでは??
  16. ハマりポイント • JSON Schema と完全に同じではない • JSON Schema では valid

    だが OpenAPI では invalid になることがある!! • 各ツールの仕様の網羅具合がまちまちなのもあり根気 強く向き合う覚悟がいる �37
  17. 一部の定義だけ override したい • バックエンドサービスとのやり取りで request ID やトレー シング用の trace

    ID をヘッダで引き回したい • code generate した際に BFF とバックエンドサービス の struct の名前が重複するので title(これが go の struct の名前になる) を変更したい • サービス側の定義を $ref で引き込みつつ、該当箇所 だけ定義を上書きするというやり方でこれらを実現した い • allOf を使うことで引き込んだ定義に手を加えることが できる �39
  18. parameters を override したい �40 kamakura/openapi.yaml paths: /users/{id}: get: parameters:

    - name: id in: path required: true schema: type: integer format: int64 - name: request_id in: query required: true schema: type: string BFF 側で request_id を ヘッダに入れてバックエンド サービスに渡す
  19. parameters を override したい �41 kamakura-bff/openapi.yaml paths: /users/{id}: get: allOf:

    - $ref: "../kamakura/openapi.yaml#/paths/ ~1users~1{id}/get" parameters: - name: id in: path required: true schema: type: integer format: int64 BFF 側は allOf で引き込んだ parameters に対して 後から override している
  20. request_id が消えないぞ?? �42 merger ࣮ߦޙͷ yaml paths: /users/{id}: get: parameters:

    - name: id in: path required: true schema: type: integer format: int64 - name: request_id in: query required: true schema: type: string
  21. わざとらしく name: hoge にする �44 kamakura-bff/openapi.yaml paths: /users/{id}: get: allOf:

    - $ref: "../kamakura/openapi.yaml#/paths/ ~1users~1{id}/get" parameters: - name: hoge in: path required: true schema: type: integer format: int64
  22. ほげええええ �45 merger ࣮ߦޙͷ yaml paths: /users/{id}: get: parameters: -

    name: hoge in: path required: true schema: type: integer format: int64 - name: request_id in: query required: true schema: type: string
  23. 最終的にこうなる �46 paths: /users/{id}: get: summary: $ref: "../kamakura/openapi.yaml#/paths/~1users~1{id}/get/responses" description: $ref:

    "../kamakura/openapi.yaml#/paths/~1users~1{id}/get/description" responses: $ref: "../kamakura/openapi.yaml#/paths/~1users~1{id}/get/responses" parameters: - name: id in: path required: true schema: type: integer format: int64 上書きたい要素を引き込まずに再定義する
  24. openapi-generator generate • model、api client あたりの利用に留めておくのがよい という所感 • api client

    は interface は生成されないので generate されたコードに対して自前で interface を生 やすか生 struct をそのまま触ることになる • テストのときに mock するといったことがやりにくいので 悩ましい �49
  25. model_my_user.go �50 /* * kamakura BFF API * * No

    description provided (generated by Openapi Generator https:// github.com/openapitools/openapi-generator) * * API version: 1.0.0 * Generated by: OpenAPI Generator (https://openapi-generator.tech) */ package openapi type MyUser struct { Id int32 `json:"id,omitempty"` Name string `json:"name,omitempty"` }
  26. generate まわり • 2.0 では https://github.com/go-swagger/go- swagger というのがあるので 2.0 ではこちらを検討し

    てもよいかも • コードや GoDoc から yaml を生成するという方法もあり • 人間のドキュメンテーション用など厳密な定義が必要な い場合は今回取り上げた内容でも許容できるところもあ ると思うので、チームとしてどこまで OpenAPI に求める か次第 �51
  27. 本日のおはなし • OpenAPI 3.0 の yaml ファイルを分割管理しようとし た • $ref

    で死にまくったので $ref を解決するツールをプロ ジェクトで自作した • 人間と機械でそれぞれ触る yaml を分けるという方法 をとった • OpenAPI のツールチェインはよく調べたほうがいい �53