AWSが公開したインターフェイス定義言語(IDL)Smithyのドキュメントを通し読みしてみた

2019.07.02

CX事業本部の阿部です。

今日はAWSが公開したSmithyというインターフェイス定義言語(IDL)のドキュメントを大筋さらってみたのでまとめてみます。 本当は「使ってみた」というタイトルにしたかったのですが、後述する理由もあり断念、一旦まとめの形にしています。

Smithyとは

Smithy

SmithyとはAmazonとAWS内で10年以上広く使われてきたインターフェイス定義言語です。 名前の由来は鍛冶場のことらしいです。アラン・スミシーのことかと思ったら、綴り違うんですね。

AWS環境だけで動作するものではなく、他のサービスにも対応します。そのために一般的なプロトコルに対するトレイトやツールなどが準備されています。

Smithyは大きな組織がAPIを通じてコラボレーションするために、以下の4つの特徴を持っています。

  1. Protocol-agnostic(不可知論的プロトコル) サービスの境界面の定義にフォーカスし、実行環境やライブラリなどに依存しない形で仕様が決められています。 シリアライズフォーマット(テキストなのかバイナリなのか)や接続プロトコルは気にしますが、サービスの実装がどうなっているか、についてはSmithy上では気にしません。
  2. Extensible Smithyモデルは拡張することができます。 振る舞いやシリアライゼーションなどに影響を与えるための多くの組み込みトレイトがあります、 そして機能追加のためのカスタムトレイトを追加することができます。
  3. Enforce standards & consistency(標準と一貫性の強要) カスタマイズ可能な標準と検証の制約により、APIはあらゆる種類の標準または規約に準拠するようになります。 ルールセットは、組織内のすべてのAPIに共有して適用できます。
  4. Resource based Smithyの言語仕様はリソースと運用をベースに定義されています。 リソースを明示的に定義すると、APIに関する豊富なメタデータが提供されます。 これを使用して、より高度なクライアントツールを構築し、モデルから成果物を派生させることができます。

マイクロサービスを志向しなくても、APIをベースにサブシステムがコラボレーションすることは増えてきました。APIの仕様記述を簡潔にして、仕様記述ベースでやり取りを行いながら設計を進めていくニーズは現場ではよくあることだと思いますので、このコラボレーションを周辺ツールまで含めたエコシステムを構築することで促進するのがSmithyの狙いと言えます。

Quick Startを触ってみた

さて、Smithyのシンタックスはどうなっているのでしょうか。公式のQuick Startから始めてみました。

シンタックスは、処理やリソース、トレイトの記述方法が私が今まで経験してきたJavaやPythonなどのプログラミング言語の記述に近くなるように設計されているな、と感じました。 そのため、コードを見れば意味が類推できるところが多く、基本的なシンタックスを把握するのはあまり苦になりませんでした。

ただ、よくわからなかったのはShapeの意味と扱いです。説明は原文では以下のようになっています。

Shapes are instances of types that describe the structure of an API. Shapes are defined inside of namespaces. Shape definitions in the IDL always start with the type name of the shape followed by the name of the shape.

おそらく time: Timestamp のような記載のことではないかと思っているのですが、なぜこれに明示的に名前が与えられているのか、が理解できてないのが現状です。もう少し使ってみれば何かつかめるかもしれませんが。

以下は、私が写経したQuick Startの内容にNext Stepの一部を追加したものです。私が理解のためにつけていたメモも合わせて記載しています。

namespace example.weather

/// サービスの仕様
/// 1. 各都市の天気予報を提供する
/// 2. CityリソースとForecastリソースがある
/// 3. Weatherサービスは複数のCityリソースをもち、Cityリソースは一つのForecastリソースをもつ
/// 4. サービスクロージャには ListCities/GetCities/GerForecast/GetCurrentTime オペレーションを含む

/// Provides weather forecast
servide Weather {
    version: "2006-03-01",
    resources: [City],
    operations: [GetCurrentTime]
}

@readonly
operation GetCurrentTime() -> [GetCurrentTimeOutput]

structure GetCurrentTimeOutput {
    @required
    time: Timestamp
}



resource City {
    identifiers: {cityId: CityId},
    create: CreateCity,
    read: GetCity,
    update: UpdateCity,
    delete: DeleteCity,
    list: ListCities,
    resources: [Forecast],
}

// createライフサイクルのオペレーションはreadonlyをつけてはいけない
// クライアント側でidまで生成して登録する場合はinstanceオペレーションになる。このケースではidempotentトレイトをつけるべき idempotent => 冪等性のこと
// idはサービス側で与えるオペレーションをする場合はInputからIdを削除してcollectionオペレーションにすれば良い
@idempotent
operation CreateCity(CreateCityInput) -> CreateCityOutput

structure CreateCityInput {
    @required
    cityId: CityId,

    @required
    name: String,

    @required
    coordinates: CityCoordinates,
}

@readonly
operation GetCity(GetCityInput) -> GetCityOutput errors [NoSuchResource]

/// オペレーションに使う型
structure GetCityInput {
    @required
    cityId: CityId
}

structure GetCityOutput {
    // アウトプットにrequired使う場合は必ず値が入る
    @required
    name: String,

    @required
    coordinates: CityCoordinates,
}

structure CityCoordinates {
    @required
    latitude: Float,

    @required
    longitude: Float,
}

@error("client")
structure NoSuchResource {
    @required
    resourceType: String,
}

// updateオペレーションはreadonly禁止、instanceオペレーション限定
// idempotentなくてもいいのか?サーバーのリソースモデルとしてインスタンスはどれかにしかない感じ?
operation UpdateCity(UpdateCityInput) -> UpdateCityOutput errors [NoSuchResource]

structure UpdateCityInput {
    @required
    cityId: CityId,

    @required
    name: String,

    @required
    coordinates: CityCoordinates,
}

structure UpdateCityOutput {
    @required
    name: String,

    @required
    coordinates: CityCoordinates,
}

// deleteオペレーションはreadonly禁止、instanceオペレーション限定
// idempotent必須
@idempotent
operation DeleteCity(DeleteCityInput) -> DeleteCityOutput errors [NoSuchResource]

structure DeleteCityInput {
    @required
    cityId: CityId,
}

structure DeleteCityOutput {
    @required
    cityId: CityId,
}

@readonly @collection
@pagenated(inputToken: "nextToken", outputToken: "nextToken",
           pageSize: "pageSize", items: "items")
operation ListCities(ListCitiesInput) -> ListCitiesOutput

structure ListCitiesInput {
    nextToken: String,
    pageSize: Integer
}

structure ListCitiesOutput {
    nextToken: String,

    @required
    items: CitySummaries,
}

list CitySummaries {
    member: CitySummary
}

// referencesトレイトはstructureをresourceに紐付ける
@references(city: {resource: City, service: Weather})
structure CitySummary {
    @required
    cityId: CityId,

    @required
    name: String,
}


resource Forecast {
    indentifiers: {cityId: CityId},
    read: GetForecast,
}

@readonly
operation GetForecast(GetForecastInput) -> GetForecastOutput errors [NoSuchResource]

structure GetForecastInput {
    @required
    cityId: CityId,
}

structure GetForecastOutput {
    chanceOfRain: Float
}

/// "pattern" is a trait
@pattern("^[A-Za-z0-9 ]+$")
String CityId

その他シンタックスについて

主にはこの辺りを参照しています。

Smithy core specification HTTP protocol bindings

  • リソースのCRUDおよびリストなどのライフサイクル操作には専用のオペレーションがある
    • 指定すべきトレイトなどの仕様も決められている
    • 冪等性に対して必要な指定、など
  • プロトコルごとに紐づけるためのトレイトがある
    • 確認できたのはHTTP/HTTP2.0/MQTT
  • API GatewayやAWSとの統合も言語仕様として用意されている

周辺ツールを使ってみる

で、ここまでドキュメントを流し読みして、ツール類の話が一切出てこないんですよね。Quick Startにもツールを利用する流れがなかったですし。

と思ったらガイドのカテゴリーにビルドファイルの仕様とGradleプラグインについての記載がありました。

Using SmithyBuild Smithy Gradle Plugin

ビルドファイルの仕様を机上で全て覚えるのは難しいな、と思って、Gradle Pluginを使ってみることにしました。

適当にGradleの空プロジェクトを作ってpluginを追加してみますが、ビルドできない。どうやらmaven centralにもjCenterにもプラグインがpublishされていないようです。

そこで、リポジトリをクローンして試してみるか、と思ってプラグインのビルドを試みたのですが、依存関係のある他のライブラリなどが解決できず、対象となるライブラリもそれらしきGithubのリポジトリも見つけられなかったので残念ながら断念しました。

* What went wrong:
Execution failed for task ':compileJava'.
> Could not resolve all files for configuration ':compileClasspath'.
   > Could not find software.amazon.smithy:smithy-model:0.7.0.
     Searched in the following locations:
       - file:/Users/abe.shinsuke/.m2/repository/software/amazon/smithy/smithy-model/0.7.0/smithy-model-0.7.0.pom
       - file:/Users/abe.shinsuke/.m2/repository/software/amazon/smithy/smithy-model/0.7.0/smithy-model-0.7.0.jar
       - https://repo.maven.apache.org/maven2/software/amazon/smithy/smithy-model/0.7.0/smithy-model-0.7.0.pom
       - https://repo.maven.apache.org/maven2/software/amazon/smithy/smithy-model/0.7.0/smithy-model-0.7.0.jar
     Required by:
         project :
   > Could not find software.amazon.smithy:smithy-build:0.7.0.
     Searched in the following locations:
       - file:/Users/abe.shinsuke/.m2/repository/software/amazon/smithy/smithy-build/0.7.0/smithy-build-0.7.0.pom
       - file:/Users/abe.shinsuke/.m2/repository/software/amazon/smithy/smithy-build/0.7.0/smithy-build-0.7.0.jar
       - https://repo.maven.apache.org/maven2/software/amazon/smithy/smithy-build/0.7.0/smithy-build-0.7.0.pom
       - https://repo.maven.apache.org/maven2/software/amazon/smithy/smithy-build/0.7.0/smithy-build-0.7.0.jar
     Required by:
         project :
   > Could not find software.amazon.smithy:smithy-cli:0.7.0.
     Searched in the following locations:
       - file:/Users/abe.shinsuke/.m2/repository/software/amazon/smithy/smithy-cli/0.7.0/smithy-cli-0.7.0.pom
       - file:/Users/abe.shinsuke/.m2/repository/software/amazon/smithy/smithy-cli/0.7.0/smithy-cli-0.7.0.jar
       - https://repo.maven.apache.org/maven2/software/amazon/smithy/smithy-cli/0.7.0/smithy-cli-0.7.0.pom
       - https://repo.maven.apache.org/maven2/software/amazon/smithy/smithy-cli/0.7.0/smithy-cli-0.7.0.jar
     Required by:
         project :

所感

ドキュメントをさっと通し読みしてみましたが、結局公開されているのは言語仕様だけ?という読み方しかできませんでした。 IDL自体が仕様記述言語であることを考えると、エコシステムを形成するツールは生命線という感じを拭えないのでこの辺りが充実してくるまではシンタックスを軽く押さえておく、程度でしか手を出せないのではないかな、と思います。 仕様は公開しているからエコシステムの充実は君たちでよろしく、というAWSからのプレッシャーなのかもしれませんが。

個人的にはシンタックスが好みなので、betaが外れて安定してくるのが待ち遠しいです。