こんにちは。 kimihiro_nです。
Microsoftから「Kiota」というOpenAPIの定義からクライアントコードを生成するツールが公開されていたのでちょっと触ってみました。
learn.microsoft.com
Kiota の特徴
JSON、YAMLで書かれたOpenAPIのAPI仕様から、APIを呼び出すためのクライアント部分を自動生成してくれるツールです。
特徴としてはGoやPythonなど様々な言語への書き出しに対応していて、似たようなインターフェースで扱える点になります。
API仕様を一度共通の内部的なモデルに変換し、そこから各言語のクライアントを生成する面白いアプローチを取っています。
似たようなツールだとOpenAPI Generator という有名なものがありますが、
ツール自体が巨大になってきてしまっているのと、CLIからだとGo言語でクライアントのみのコードを生成する方法が分からなかった(サーバー側のコードも一緒に生成されてしまう)ため、クライアントに特化したKiotaを試してみることにしました。
Kiotaを使ってみる
インストール
learn.microsoft.com
インストール手順はこちら。
コミュニティ作成によるものですがbrewによるインストールもできます。
OpenAPI の用意
クライアントを作るOpenAPI仕様を用意します。
swagger : "2.0"
basePath : /api/v1
definitions :
main.ErrResponse :
properties :
detail :
type : string
type : object
main.Post :
properties :
content :
type : string
id :
type : integer
title :
type : string
required :
- content
- title
type : object
main.PostListResponse :
properties :
posts :
items :
$ref : '#/definitions/main.Post'
type : array
type : object
info :
contact : {}
title : ExampleWebAPI
version : "1.0"
paths :
/posts :
get :
consumes :
- application/json
description : get posts
parameters :
- description : limit
in : query
name : limit
type : integer
- description : offset
in : query
name : offset
type : integer
produces :
- application/json
responses :
"200" :
description : OK
schema :
$ref : '#/definitions/main.PostListResponse'
"500" :
description : internal server error
schema :
$ref : '#/definitions/main.ErrResponse'
summary : Get List of Posts
tags :
- posts
post :
consumes :
- application/json
description : add post
parameters :
- description : Post
in : body
name : post
required : true
schema :
$ref : '#/definitions/main.Post'
produces :
- application/json
responses :
"201" :
description : created
schema :
$ref : '#/definitions/main.Post'
"400" :
description : invalid params
schema :
$ref : '#/definitions/main.ErrResponse'
"500" :
description : internal server error
schema :
$ref : '#/definitions/main.ErrResponse'
summary : Create new post
tags :
- posts
投稿の一覧取得と投稿が出来る簡単なAPIです。
諸事情(後述)でOpenAPI 3.xではなくてOpenAPI 2.0の仕様に合わせて作っています。
2.0の方はSwaggerとも呼ばれてます。
Goクライアントの生成
こちらからKiotaのGoクライアントを生成してみます。
kiota generate -l go -d ./swagger.yaml -o ./client -n ${レポジトリ名}/client
-l
オプションでGo言語を、-d
オプションでOpenAPI(Swagger)ファイルの置き場を、-o
で出力先ディレクトリを、
そして -n
で生成されるクライアントのフルパッケージ名を指定します。
フルパッケージ名は生成されたコードのimportが正しく動くために必要で、go mod init
したときのレポジトリ名 + 出力時のディレクトリ(/client)を指定します。
生成されたファイル
実行するとこのようなファイルが生成されます。
これらのファイルは触らなくて大丈夫ですがimportのエラーが出ている場合は-n
オプションに渡している値が適切かどうかを確認してみてください。
Goクライアントを使ってみる
まずはAPIクライアントを初期化から。
package main
...
import "github.com/microsoft/kiota-abstractions-go/authentication"
import "github.com/microsoft/kiota-http-go"
import ${レポジトリ名}/client
func main()
ctx := context.Background()
authProvider := authentication.AnonymousAuthenticationProvider{}
adapter, err := http.NewNetHttpRequestAdapter(&authProvider)
if err != nil {
log.Fatalf("Error creating request adapter: %v \n " , err)
}
adapter.SetBaseUrl("http://localhost:8080/api/v1" )
apiClient := client.NewApiClient(adapter)
API認証が必要なケースに対応するためのauthProviderを生成し、そこから通信用のadapterを作っています。
今回は認証不要のAPIを叩くのでAnonymousAuthenticationProviderを利用しました。
adapterはAPI通信部分を吸収する部分で、ここで独自のHTTP通信クライアントを組み込んだりも出来ます。
OpenAPIにサーバーのエンドポイントの記載がない場合はこのようにadapterで指定します。
adapterからクライアントを生成したら準備完了です。
limit := int32 (3 )
params := posts.PostsRequestBuilderGetQueryParameters{
Limit: &limit,
}
result, err := apiClient.Posts().Get(ctx, &posts.PostsRequestBuilderGetRequestConfiguration{
QueryParameters: ¶ms,
})
リクエストを組み立ててAPIを呼び出す部分のコードはこのような形です。
APIごとに専用の構造体が生成されているのでそれを利用して組み立てる形になります。
パラメータが未指定の場合と区別するためすべてポインタ型で渡すようなインターフェースになっています。
数値から直接ポインタを取ることはできないので、一度代入が必要な点は少し手間になります。
lo.ToPtr みたいな関数を利用してもいいかもしれません。
result, err := apiClient.Posts().Get()
if err != nil {
var errResp *models.ErrResponse
if errors.As(err, &errResp) {
fmt.Printf("request error: %s \n " , *errResp.GetDetail())
return
}
fmt.Printf("Error getting inference result: %+v \n " , err)
return
}
for _, post := range result.GetPosts() {
fmt.Printf("id: %d, title: %s, content: %s \n " , *post.GetId(), *post.GetTitle(), *post.GetContent())
}
リクエストを行うと、パースされたレスポンスとエラーにアクセスすることが出来ます。
エラーはOpenAPIに定義されていれば専用のモデルが生成されるので、キャストすることでエラーオブジェクトにもアクセスすることが可能です。
定義されていないエラーの場合は汎用的なエラーが返ってきます。
正常時のレスポンスもパースされた状態で入っておりGetter経由で好きに取り出すことが出来ます。
JSONのパースなどを自分で記述しなくていいのはコード生成の大きなメリットです。
requestData := models.NewPost()
title := "Hello world"
requestData.SetTitle(&title)
content := "This is a test from kiota apiClient."
requestData.SetContent(&content)
result, err := apiClient.Posts().Post(ctx, requestData, &posts.PostsRequestBuilderPostRequestConfiguration{
Headers: header,
Options: []abstractions.RequestOption{
kiotaHttp.NewCompressionOptions(false ),
},
})
fmt.Printf("saved id: %d \n " , *result.GetId())
POSTを使って送信する例はこちら。
こちらも専用のモデルが定義されているのでSetterを利用して値をセットしていく形になります。
試してみたときのはまりどころとしては、デフォルトだとリクエストが圧縮されて送られてしまうことでした。
サーバー側でリクエストが弾かれてしまい、原因を探っていったところBodyをgzipして送信していることが分かりました。
リクエスト時のオプションとしてCompressionをfalseにセットしてあげることで Content-Encoding: gzip
に未対応なサーバーでも適切にリクエストを送ることが出来ます。
OpenAPIの生成もコードファーストでやってみる
OpenAPI 3.xではなくOpenAPI 2.0の形式でYAMLを生成していた部分の答え合わせなのですが、今回OpenAPIの生成もGoのコードから作ってみることにしました。
Python だとFastAPIのようなフレームワークで簡単に出力できますが、Goだとフレームワークと一体でOpenAPIを生成してくれるものは見当たりませんでした。
OpenAPIを用意してサーバー側のコードを生成する「スキーマファースト」なやり方であれば、OpenAPI Generatorをはじめとして複数候補がありますが、
OpenAPIを手で書くのが辛い、ファイル分割の対応具合がツールによってまちまちなどの理由で「コードファースト」なやり方を模索してました。
github.com
最終的にたどり着いたのがSwagというツールでした。
こちらはAPIサーバーにコメントの形でOpenAPIに必要な情報を埋め込むことでOpenAPIのスキーマを生成してくれるツールです。
gin, echo, fiberなど主要なWebフレームワークにも対応しています。
type Post struct {
ID int `json:"id" binding:"-"`
Title string `json:"title" binding:"required"`
Content string `json:"content" binding:"required"`
}
func (s *Server) AddPost(g *gin.Context) {
post := Post{}
if err := g.ShouldBindJSON(&post); err != nil {
g.JSON(http.StatusBadRequest, ErrResponse{Detail: err.Error()})
return
}
if err := s.postRepository.AddPost(g, &post); err != nil {
g.JSON(http.StatusInternalServerError, ErrResponse{Detail: "internal server error" })
return
}
g.JSON(http.StatusCreated, post)
}
このような形で各ハンドラにコメントを付与し、swagコマンドを叩く事でOpenAPIのスキーマを生成できます。
(上記はginの例になります。)
Postなどの構造体名をコメントに入れておくとOpenAPIに反映してくれるのでスキーマの更新漏れがなくて便利です。
欠点としては生成されるOpenAPIが2.0相当になってしまう点です。
3.0が公開されたのが2017年なので、OpenAPIまわりのツール対応状況を考えると3.xに移行しておきたいところです。
v2という形でSwagのOpen API 3.x対応が進められていますが現時点ではリリースされていません。
RCまでは来ているようなのでそのうち公開されると思いますが。
まとめ
SwagとKiotaを利用することで、システム間の連携を扱いやすくすることが出来ました。
OpenAPIを利用してスキーマを管理しつつ、プログラムの上ではOpenAPIを意識せず扱えるので改修やメンテナンスも行いやすくなりそうです。
弊社の場合PythonとGoをよく利用するので、「PythonのFastAPIでOpenAPIを生成してKiotaのGoでクライアント生成」、「SwagでOpenAPIを生成してKiotaのPythonクライアントを生成」みたいな言語を跨いでの連携もしやすそうです。
似たようなシステム連携をするのに「Protocol Buffers」も選択肢に上がると思いますが、REST APIでの資産が既にある場合、現状のコードベースを生かしつつSwagなどでOpenAPI化し、自動生成されたクライアントで連携を堅固にしていくのも実用的かと思いました。
今回解説用に作成したサンプルコードはこちら
github.com
The Go gopher was designed by Renée French .