SmithyでAPIリファレンス作成してみよう
はじめに
みなさんはじめまして、ニフクラのエンジニアをやっております@seumoと申します🐭
本記事は富士通クラウドテクノロジーズ Advent Calendar 2021の14日目の記事です。
13日目は @tunakyonn🤴のCDK for Terraform をニフクラ provider で試してみたという記事でした。
得意なプログラミング言語でクラウドのリソース管理を自動化できるのはとても便利ですね、私も今度Javaで試してみようかと思います。
Smithyとは
SmithyはAmazonが開発したIDL(インターフェース記述言語)に基づくツールセットであり、プログラミング言語やプロトコルに依存せずサービスのインターフェースを定義することができ、さらにその定義から複数のプログラミング言語のコードを自動生成できたりするものです。
SmithyでAPIリファレンスを作成してみる
それでは早速公開されているSmithyのドキュメントを見ながらAPIリファレンスを作成してみようと思います!
今回はニフクラのKubernetes Service HatobaのAPIの一部をSmithyで定義したらどうなるのか、検証してみます。
サービスの定義
- SmithyはShapeと呼ばれるモデルの定義によって構成されていきます
- Shapeとは名前付きオブジェクトのようなものでいくつかの型があり、まずはじめに定義するのがサービスのShapeとなります
Shapeは以下のフォーマットで記述することができます。
{型} {名前} {
{プロパティ名}: {値}
}
Kubernetes Service Hatobaのサービスを定義してみます。
service KubernetesServiceHatoba {
version: "v1"
}
上記例では、service型のshapeをKubernetesServiceHatobaという名前で定義し、プロパティとしてversionにv1を設定したという記述になります。
オペレーションの定義
- 次にそのサービスではどんなオペレーションが可能なのかを定義します
- 今回はKubernetes Service Hatobaが提供している機能の1つであるCreateFirewallGroupを例に定義していきます
service KubernetesServiceHatoba {
version: "v1",
operations: [
CreateFirewallGroup,
],
}
operation CreateFirewallGroup {
input: CreateFirewallGroupRequest,
output: CreateFirewallGroupResult,
}
- 先ほどのサービスShapeにoperationsというプロパティを追加しました
- さらにoperation型のShapeを新しく定義します
- operation型のShapeにはAPI操作の入力と出力のShapeを定義することができます
- まだ入力と出力のShapeの中身を書いていないため次の項で記述していきます
リソースの定義
- まずはinputのCreateFirewallGroupRequestのリソースから定義してみます
- structure型のShapeを用いて定義します
structure CreateFirewallGroupRequest {
FirewallGroup: RequestFirewallGroup,
}
structure RequestFirewallGroup {
Name: String,
Description: String,
}
-
CreateFirewallGroupのAPIでは、FirewallGroupをinputとして指定します
-
FirewallGroupのリソースをRequestFirewallGroupという名前のstructure型のShapeで定義し、NameとDescriptionという2つのstring型プロパティをもっているという記述になります
-
次にoutputのCreateFirewallGroupResultを定義してみます
structure CreateFirewallGroupResult {
FirewallGroup: FirewallGroup,
}
structure FirewallGroup {
Name: String,
Description: String,
Rules: ListOfRules,
}
list ListOfRules {
member: Rules,
}
structure Rules {
Description: String,
Direction: String,
Status: String,
CidrIp: String,
ToPort: Integer,
Id: String,
Protocol: String,
FromPort: Integer,
}
- outputとしてFirewallGroupが返却されるためそれを定義します
- FirewallGroupにはRulesという要素があり配列になっています
- 配列はlist型のShapeで定義し、list型のShapeはmemberプロパティにlistの要素を指定することができます、ここではRulesを指定しています
トレイトの定義
- CreateFirewallGroupというオペレーションに対してShapeの定義が一通り完了しました
- 次はこのShapeにトレイトを付けていきます
- Smithyではトレイトを利用してShapeに様々な追加情報を付与することができます
- Kubernetes Service HatobaのAPIはHTTPでリクエストできるAPIなので、HTTP binding traitsを付与してみます
- HTTP binding traitsはoperationのShapeに指定できます
@http(method: "POST", uri: "/v1/firewallGroups" , code: 201)
operation CreateFirewallGroup {
input: CreateFirewallGroupRequest,
output: CreateFirewallGroupResult,
}
-
トレイトは
@トレイト名(プロパティ)
で指定します -
上記例ではCreateFirewallGroupのAPIはPOSTメソッドで
/v1/firewallGroups
というuriでリクエストができるということを意味し、また正常系のステータスコードは201であるという定義となっております -
次にKubernetes Service HatobaはJSON形式でデータをやりとりするREST APIであるため、リソースをシリアライズするためのProtocol traitsをShapeに定義します
structure CreateFirewallGroupRequest {
@required
@jsonName("firewallGroup")
FirewallGroup: RequestFirewallGroup,
}
-
上記例では
FirewallGroup
という名前でプロパティを定義していますが、実際のAPIのインターフェースでのJSONのキー値はfirewallGroup
と小文字から始まるのでそれを@jsonName
トレイトで指定することができます -
また、Shapeの各プロパティにはConstraint traitsを指定することができます。
-
@required
はその名の通り必須パラメーターであることを意味します -
Constraint traitsは他にもlengthやrange、pattern、enumなど細かく値の形式を指定することができるものもあるので必要に応じて設定します
-
最後に応用編として、AWSの特別トレイトについて紹介します
-
SmithyはAmazonが開発したものなので、AWSのサービスが提供しているAPIで利用できるトレイトがいくつかあります
use aws.protocols#restJson1
use aws.auth#sigv4
@restJson1
@sigv4(name: "hatoba")
service KubernetesServiceHatoba {
version: "v1",
operations: [
CreateFirewallGroup,
],
}
-
AWSの特別トレイトを利用するにはまずuseで宣言します
-
AWSのAPIはサービスによって様々な呼び出し方がありますが、lambdaのようなペイロードがJSONでRest形式のものを
restJson1
というプロトコルと呼んでいるようです -
Kubernetes Service Hatobaも同じ形式なのでこれを指定します。
-
また、AWSのAPIはシグネチャーによって認証する形式となっておりサービスに
sigv4
トレイトを指定することでこれを表すことができます -
ニフクラのKubernetes Service HatobaのAPIの認証は、AWSのシグネチャー認証と互換性があるため同じように指定することができます。
完成形
- サービス、オペレーション、リソースを定義しトレイトを付与することで一通りAPIリファレンスが完成しました、全体は以下のようになります。
namespace KubernetesServiceHatoba
use aws.protocols#restJson1
use aws.auth#sigv4
@restJson1
@sigv4(name: "hatoba")
service KubernetesServiceHatoba {
version: "v1",
operations: [
CreateFirewallGroup,
],
}
@http(method: "POST", uri: "/v1/firewallGroups" , code: 201)
operation CreateFirewallGroup {
input: CreateFirewallGroupRequest,
output: CreateFirewallGroupResult,
}
structure CreateFirewallGroupRequest {
@required
@jsonName("firewallGroup")
FirewallGroup: RequestFirewallGroup,
}
structure CreateFirewallGroupResult {
@jsonName("firewallGroup")
FirewallGroup: FirewallGroup,
}
structure RequestFirewallGroup {
@required
@jsonName("name")
Name: String,
@jsonName("description")
Description: String,
}
structure FirewallGroup {
@jsonName("name")
Name: String,
@jsonName("rules")
Rules: ListOfRules,
@jsonName("description")
Description: String,
}
list ListOfRules {
member: Rules,
}
structure Rules {
@jsonName("description")
Description: String,
@jsonName("direction")
Direction: String,
@jsonName("status")
Status: String,
@jsonName("cidrIp")
CidrIp: String,
@jsonName("toPort")
ToPort: Integer,
@jsonName("id")
Id: String,
@jsonName("protocol")
Protocol: String,
@jsonName("fromPort")
FromPort: Integer,
}
APIリファレンスをビルドしてみる
- モデルができたのでこれをビルドしてみます!
- モデルをビルドするためにはSmithy Gradle Pluginを利用します
- まずはsmithy-build.jsonファイルを以下のように作成します
{
"version": "1.0"
}
- 次にbuild.gradle.ktsファイルを以下のように作成します
plugins {
id("software.amazon.smithy").version("0.6.0")
}
configure<software.amazon.smithy.gradle.SmithyExtension> {}
repositories {
mavenLocal()
mavenCentral()
}
dependencies {
implementation("software.amazon.smithy:smithy-model:1.15.0")
implementation("software.amazon.smithy:smithy-aws-traits:1.15.0")
}
- 完成形のモデルを
model/kubernetes-service-hatoba.smithy
パスにコピーしてからgradle build
コマンドを実行することでビルドが完了します
コードを自動生成してみる
- ビルドができるようになったので今度はSmithy Typescriptを利用してTypeScriptのコードを生成してみます
- まずsmithy-build.jsonを以下のように書き換えます
{
"version": "1.0",
"plugins": {
"typescript-codegen": {
"service": "KubernetesServiceHatoba#KubernetesServiceHatoba",
"package": "KubernetesServiceHatoba",
"packageVersion": "1.0.0"
}
}
}
- Smithy TypescriptのREADMEに記載の手順を参考にmaven localにpublishします
- build.gradle.ktsのdependenciesにsmithy-typescript-codegenを追加します
plugins {
id("software.amazon.smithy").version("0.6.0")
}
configure<software.amazon.smithy.gradle.SmithyExtension> {}
repositories {
mavenLocal()
mavenCentral()
}
dependencies {
implementation("software.amazon.smithy:smithy-model:1.15.0")
implementation("software.amazon.smithy:smithy-aws-traits:1.15.0")
implementation("software.amazon.smithy.typescript:smithy-typescript-codegen:0.8.0")
}
-
gradle build
コマンドを実行することで以下のようなAPIクライアントのコードが自動生成されました!
└── typescript-codegen
├── jest.config.js
├── package.json
├── src
│ ├── commands
│ │ ├── CreateFirewallGroupCommand.ts
│ │ └── index.ts
│ ├── index.ts
│ ├── KubernetesServiceHatobaClient.ts
│ ├── KubernetesServiceHatoba.ts
│ ├── models
│ │ ├── index.ts
│ │ └── models_0.ts
│ ├── runtimeConfig.browser.ts
│ ├── runtimeConfig.native.ts
│ ├── runtimeConfig.shared.ts
│ └── runtimeConfig.ts
├── tsconfig.es.json
├── tsconfig.json
└── tsconfig.types.json
OpenAPIに変換してみる
- Smithyはプロトコルに依存しないIDLですが、RestFulのAPIリファレンスを記述する場合はOpenAPIを利用している方も多いと思います
- なんとこのSmithyはOpenAPIに自動変換することも可能なので早速試してみます
- まずsmithy-build.jsonに以下のようにpluginsの設定を追加します
{
"version": "1.0",
"plugins": {
"openapi": {
"service": "KubernetesServiceHatoba#KubernetesServiceHatoba",
"protocol": "aws.protocols#restJson1"
},
"typescript-codegen": {
"service": "KubernetesServiceHatoba#KubernetesServiceHatoba",
"package": "KubernetesServiceHatoba",
"packageVersion": "1.0.0"
}
}
}
- build.gradle.ktsのdependenciesにsmithy-openapiを追加します
plugins {
id("software.amazon.smithy").version("0.6.0")
}
configure<software.amazon.smithy.gradle.SmithyExtension> {}
repositories {
mavenLocal()
mavenCentral()
}
dependencies {
implementation("software.amazon.smithy:smithy-model:1.15.0")
implementation("software.amazon.smithy:smithy-aws-traits:1.15.0")
implementation("software.amazon.smithy.typescript:smithy-typescript-codegen:0.8.0")
implementation("software.amazon.smithy:smithy-openapi:1.15.0")
}
-
gradle build
コマンドを実行することでOpenAPIのJSONが生成されました! - JSONファイルをSwagger UIで表示させたり、Amazon API Gatewayに設定したりといったことが可能です
- ただし異なるIDLなので全てをサポートしているわけではありません、詳しくはSmithy Guidesを参照してください
まとめ
- Smithyを利用してAPIリファレンスのモデル定義とモデルからコード生成やOpenAPIへの変換についてを紹介しました
- APIリファレンスを表計算ツールの方眼紙やwikiで記述している方も多いと思いますが、それだと記述レベルの統一や運用管理が難しいと思うので自動化の観点からもSmithyの利用を検討してみてください
おわりに
明日は@aokumaが「社内 GitLab の大型マイグレーションをした話」を書いてくれるそうなので楽しみにしましょう🐭
Discussion