OpenAPI 3.0 で
microservice の
API 定義を試みてハマった話
小池 大地
kamakura.go #5 2019/06/22
本日のおはなし
• OpenAPI 3.0 の yaml ファイルを分割管理しようとし
た
• $ref で死にまくったので $ref を解決するツールをプロ
ジェクトで自作した
• 人間と機械でそれぞれ触る yaml を分けるという方法
をとった
• OpenAPI のツールチェインはよく調べたほうがいい
�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
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"
導入プロジェクト
• 社内コーポレートエンジンの開発プロジェクト
• 名簿、評価、サイコロ給、スマイルなど複数のサービス
が連携して一つのプロダクトとして稼働する
• 各サービス、フロントエンドは別々の開発者が開発する
�5
構成
�6
導入経緯
• BFF、バックエンドサービス、フロントエンドが同時に開
発が進む
• 先に各サービス間の I/F を決め、レビューしてOKだった
ら実装に着手するというワークフロー
• よし API I/F を OpenAPI Spec で書こう
• code generate や request/response の validate
もできる
• 世は大 OpenAPI 時代!!
�7
各 I/F を OpenAPI で定義
�8
�9
yaml 管理問題
API は 2倍
• 認証は BFF が行うため全リクエストは BFF 経由する
• つまり API は 2倍 必要
�10
/users/1
yaml で病むる
• 膨大な数の API になるため yaml は分割して管理した
い
• 評価のシステムの管理画面だけで 40 API ある(BFF
含めると 80 API)。管理系だけで1万行になった
• yaml 分けたいけど entity とか再定義するのはミスも
多発するだろうしいい感じに使いまわしたい
• 2.0 は yaml マージツールがある https://
www.npmjs.com/package/multi-file-swagger
�11
components
3.0 では再利用する定義を components に集約できる
�12
https://www.openapis.org/news/blogs/2016/10/tdc-structural-improvements-explaining-30-spec-part-2
�13
3.0 にしました
プロジェクト自作 yaml merger
• node.js 製
• https://github.com/whitlockjc/json-refs を使い
別ファイルの $ref を解決してマージした yaml を生成
する
• @dameleon++
�14
もともとはこう
�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
components 以下を切り出している
�16
paths:
/users/{id}:
get:
- -
responses:
"200":
content:
application/json:
schema:
$ref: "./entities.yaml#/User"
entities.yaml
�17
User:
title: MyUser
type: object
properties:
id:
type: integer
name:
type: string
merger で $ref 解決
�18
paths:
'/users/{id}':
get:
- -
responses:
'200':
content:
application/json:
schema:
title: MyUser
type: object
properties:
id:
type: integer
name:
type: string
ちょっとまった
• $ref で別ファイルの定義を参照するのは OpenAPI
3.0 の仕様にそもそもあるのでは??
• あります!!
• ただしツールによってはサポートしていなかったり、多段
$ref はスルーされたりします
• 他にも細かい制約があります
�19
仕様: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#relative-references-in-urls
多段 $ref
名簿側の User の定義を BFF で使いまわしたい
�20
/users/1
たとえばこんなディレクトリ構成
�21
.
!"" kamakura
#   !"" entities.yaml
#   $"" openapi.yaml
!"" kamakura-bff
   $"" openapi.yaml
kamakura/openapi.yaml
�22
paths:
/users/{id}:
get:
- -
responses:
"200":
content:
application/json:
schema:
$ref: "./entities.yaml#/User"
kamakura-bff/openapi.yaml
�23
paths:
/users/{id}:
get:
- -
responses:
$ref: "../kamakura/openapi.yaml#/paths/
~1users~1{id}/get/responses"
response のステータスコード、entity の定義を $ref で
引き込みたい
~1 は / のエスケープ文字
Swagger UI で開くと 😱
�24
merger で $ref を解決すると 😊
�25
多段 $ref 問題
• kamakura-bff/openapi.yaml -> kamakura/
openapi.yaml -> kamakura/entities.yaml この
$ref を Swagger UI は解決できない
• ツールによっては最初の $ref で解決できなくて死ぬも
のもある
�26
$ref 問題
• https://github.com/OpenAPITools/openapi-
generator
• generate した model を使っている
�27
$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.
開発フロー
• merger で生成された $ref 解決済みの yaml に対し
て validation をかけている
• 人間が触る yaml と機械が 触る yaml を分ける
• テストで request/response の validate に使いたい
ので pre-commit hook で $ref 解決済み yaml を生
成して commit している
• invalid な yaml が生成されても validation をすり抜
けることもあるので Swagger UI で表示してみて問題
ないか確認するのが良い
�29
�30
ところで
(再掲)kamakura-bff/openapi.yaml
�31
paths:
/users/{id}:
get:
- -
responses:
$ref: "../kamakura/openapi.yaml#/paths/
~1users~1{id}/get/responses"
(再掲)kamakura-bff/openapi.yaml
�32
paths:
/users/{id}:
get:
- -
responses:
$ref: "../kamakura/openapi.yaml#/paths/
~1users~1{id}/get/responses"
これ paths 以下を $ref で引き込めばいいのでは??
この形
�33
paths:
$ref: "../kamakura/openapi.yaml#/paths
この形
�34
paths:
$ref: "../kamakura/openapi.yaml#/paths
これはできません
paths 直下は $ref を使えない
�35
https://swagger.io/docs/specification/using-ref/
この形ならいける
�36
paths:
/users/{id}:
$ref: "../kamakura/openapi.yaml#/paths/
~1users~1{id}
ハマりポイント
• JSON Schema と完全に同じではない
• JSON Schema では valid だが OpenAPI では
invalid になることがある!!
• 各ツールの仕様の網羅具合がまちまちなのもあり根気
強く向き合う覚悟がいる
�37
�38
何はともあれ merger で
$ref を解決すればいけそう?
一部の定義だけ override したい
• バックエンドサービスとのやり取りで request ID やトレー
シング用の trace ID をヘッダで引き回したい
• code generate した際に BFF とバックエンドサービス
の struct の名前が重複するので title(これが go の
struct の名前になる) を変更したい
• サービス側の定義を $ref で引き込みつつ、該当箇所
だけ定義を上書きするというやり方でこれらを実現した
い
• allOf を使うことで引き込んだ定義に手を加えることが
できる
�39
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 を
ヘッダに入れてバックエンド
サービスに渡す
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 している
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
�43
実は parameters の先頭要素
だけ置き換わっている
わざとらしく 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
ほげええええ
�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
最終的にこうなる
�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
上書きたい要素を引き込まずに再定義する
yaml で病むる
• microservices をやろうとするとこうなってしまうので
は…?
• みなさんは一体どうやっているのか…もっといいやり方
あるのでは…?
�47
�48
code generate
openapi-generator generate
• model、api client あたりの利用に留めておくのがよい
という所感
• api client は interface は生成されないので
generate されたコードに対して自前で interface を生
やすか生 struct をそのまま触ることになる
• テストのときに mock するといったことがやりにくいので
悩ましい
�49
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"`
}
generate まわり
• 2.0 では https://github.com/go-swagger/go-
swagger というのがあるので 2.0 ではこちらを検討し
てもよいかも
• コードや GoDoc から yaml を生成するという方法もあり
• 人間のドキュメンテーション用など厳密な定義が必要な
い場合は今回取り上げた内容でも許容できるところもあ
ると思うので、チームとしてどこまで OpenAPI に求める
か次第
�51
お世話になっているもの
• request/response validation

https://github.com/getkin/kin-openapi
• mock server

https://github.com/danielgtaylor/apisprout
�52
本日のおはなし
• OpenAPI 3.0 の yaml ファイルを分割管理しようとし
た
• $ref で死にまくったので $ref を解決するツールをプロ
ジェクトで自作した
• 人間と機械でそれぞれ触る yaml を分けるという方法
をとった
• OpenAPI のツールチェインはよく調べたほうがいい
�53
�54
ありがとうございました

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