Amazon Web Services ブログ

開発者プレビュー : Smithy を利用した Ruby SDK コード生成

このブログは、Matt Muller によって書かれた Developer Preview: Ruby SDK code generation using Smithy を翻訳したものです。

この記事について

AWS SDK For Ruby チームは smithy-ruby の開発者プレビューを発表できることをうれしく思っています。このツールは Smithy モデリングを使用し、あなたが作成しているサービス API のための Ruby SDK を “ホワイトラベル“ (ある企業が生産した製品やサービスを他の企業が自社のブランドとして販売するマーケティング用語です。ここではサービス API に対応した SDK を Ruby をはじめ多言語で簡単に展開できる特性のことを指します。)として生成できます。今後リリースされる AWS SDK For Ruby では、Smithy でのコード生成が採用される予定です。

Smithy とは何か?

Smithy は開発者が複数の言語でクライアントとサーバを構築できるようにするインターフェース定義言語とツール群です。Smithy のモデルはサービスを、リソース・オペレーション・シェイプの集合体として定義します。Smithy モデルによって API プロバイダは様々なプログラミング言語によるクライアントとサーバ、API ドキュメント、テスト自動化、およびサンプルコードを生成することができます。Smithy の詳細については、Smithy のドキュメントを参照してください。

Ruby SDK に含まれるもの

components-of-a-code-generated-ruby-sdkコード生成された Ruby SDK のコンポーネント

Smithy を使用して Ruby SDK を生成したコードには、汎用コンポーネントとプロトコル固有のコンポーネントが含まれます。これらのコンポーネントは、順不同で以下の通りです。

  • Validators (private) – Rubyの入力タイプをSmithyモデルに対して検証するクラス群。
  • Builders (private, プロトコル) – 入力からプロトコル固有のリクエストを生成するクラス群 (例: JSON over HTTP)。
  • Stubs (private, プロトコル) – スタブデータを使用してプロトコル固有のレスポンスを作成するクラス群。
  • Parsers (private, プロトコル) – プロトコル固有のレスポンスをデータ構造にパースするクラス群 (例: XML over HTTP)。
  • Types (public) – 構造体の型を表すクラス群 (Plain Old Ruby Objects)。
  • Errors (public, プロトコル) – エラーの型とプロトコル固有のエラークラスを表すクラス群。
  • Params (private) – ハッシュ型の入力を、クライアントの操作で使用される厳格な型に変換するモジュールのセットです。
  • Paginators (public) – ページ分割された操作を自動的に行うために使用されるクラス群。
  • Waiters (public) – 操作が目的の状態になるまで待ってから、クライアントに制御を再開するために使用されるクラスのセット。
  • Client (public) – 全てを結びつけるクラスで、サービスAPIのパブリックなインターフェースです。クライアントは、ミドルウェアを使用してリクエストを作成し、レスポンスを返す役割を担っています。

コンポーネントの詳細については、smithy-rubyのwikiを参照してください。

Middleware

Middleware とはクライアントとサーバーの間に位置し、リクエストとレスポンスのサイクルを修正する方法を提供するクラスです。Middleware はリクエストを作成および送信し、レスポンスをパースするために使用されます。また Middleware はスタックで構成されており、別の Middleware を呼び出す役割を担っています。

middleware-stackMiddleware Stack

クライアントにおいて各APIの操作は、独自の Middleware Stack を作成し、リクエストとレスポンスのサイクルを処理する責任を負うメソッドを持つことになります。AWS SDK for Ruby の Middleware Stack は6つのデフォルトの Middleware で提供されます。各 Middleware は、リクエスト、レスポンス、コンテキストにアクセスすることができます。具体的には以下のようなコンポーネントがあります。

  • Validate – Validator クラスを使用して入力を検証することができます。(オプショナル – クライアント設定)
  • Build – Builder クラスと入力値を使用して、プロトコル固有の要求(例: JSON over HTTP)を構築します。
  • HostPrefix – 必要であればホストの接頭辞でエンドポイントを変更します。(オプショナル – Smithy trait)
  • Send– プロトコル固有のクライアント(HTTP クライアントなど)を使用して、リクエストを送信します。その際 Stubs クラスを使用してレスポンスを返すこともできます。
  • Parse – Parser クラスと生のレスポンスデータを使用して、プロトコル固有のレスポンス (例: XML over HTTP) をパースします。
  • Retry – ネットワークエラーや再試行可能またはスロットリングエラーのあるレスポンスに対してリクエストを再試行します。

プロトコルの実装では、独自のコードで生成された Middleware を挿入することもできます。Middleware は、Client クラスや Client インスタンス、個々の操作呼び出しに実行時に追加することもできます。

Rails JSON プロトコル

Smithyが構築したRuby SDK が完全に機能するためには、自動車にエンジンが必要なのと同じように、プロトコルの実装が必要です。今回の開発者プレビューの一環として、私たちは「Rails JSON」と呼ぶプロトコル実装を提供する予定です。Rails JSON プロトコル定義により、Smithy モデルを使用して、JSON 上で Rails API と直接通信する Ruby SDK をコード生成することができるようになります。

次のセクションではデモとして、Rails サービスをセットアップして、それと通信できる SDK を生成する方法を詳しく説明します。

Rails API サービスのセットアップ

SDK を作成する前に、SDK が通信するためのサービスが必要です。まず、rails new —api sample-service で新しい Rails API サービスを作成します。

次に rails のドキュメントを参考に、rails generate scaffold HighScore game:string score:integer で High Score モデルを作成し、rake db:migrate を実行しましょう。

models/high_score.rb で、ゲーム名に長さの検証を validates :game, length: { minimum: 2 } というように追加します。この検証は後で使用されます。

さて、いよいよ rails s で rails アプリを起動し、http://127.0.0.1:3000 のようなエンドポイントで動作していることを確認します。このエンドポイントは後ほど必要になります。

もし Rails アプリを生成できなくても、このサンプルの Rails サービスのコピーが smithy-ruby にあるので心配いりません。

Smithy モデルの追加

SDK を生成するには、先ほど定義した Rails サービスを記述した Smithy モデルが必要です。私はこれをすでに smithy-ruby の high-score-service.smithy で定義しています。このモデルは smithy-ruby に対して、High Score サービス用の構成とクライアント API を生成するコードと、Rails JSON プロトコルを使用するよう指示しています。

重要な部分をいくつか分解してみましょう。

最初のセクションでは、Smithy で Rails JSON プロトコルを持つ HighScoreService を作成し、そのリソースと操作を定義するように指示しています。リソースは識別子(Rails のデフォルトは id)を持っており、これはハイスコアを検索するのに使用されます。リソースには Rails の基本的な CRUD 操作(get、create、update、delete、list)がすべて含まれています。

@railsJson
@title("High Score Sample Rails Service")
service HighScoreService {
    version: "2021-02-15",
    resources: [HighScore],
}

/// Rails default scaffold operations
resource HighScore {
    identifiers: { id: String },
    read: GetHighScore,
    create: CreateHighScore,
    update: UpdateHighScore,
    delete: DeleteHighScore,
    list: ListHighScores
}

次のセクションではサービスの構成を定義します。HighScoreAttributes は、High Score のすべてのプロパティを返すシェイプです。HighScoreParams は、High Score が必要とするすべてのプロパティを含んでいます。2 文字以上という @length の検証は game プロパティに適用されます。

/// Modeled attributes for a High Score
structure HighScoreAttributes {
    /// The high score id
    id: String,
    /// The game for the high score
    game: String,
    /// The high score for the game
    score: Integer,
    // The time the high score was created at
    createdAt: Timestamp,
    // The time the high score was updated at
    updatedAt: Timestamp
}

/// Permitted params for a High Score
structure HighScoreParams {
    /// The game for the high score
    @length(min: 2)
    game: String,
    /// The high score for the game
    score: Integer
}

次にオペレーションの構成です。各オペレーションに @http trait を適用して Rails の期待通りのパスを指定しています。

/// Get a high score
@http(method: "GET", uri: "/high_scores/{id}")
@readonly
operation GetHighScore {
    input: GetHighScoreInput,
    output: GetHighScoreOutput
}
/// Input structure for GetHighScore
structure GetHighScoreInput {
    /// The high score id
    @required
    @httpLabel
    id: String
}
/// Output structure for GetHighScore
structure GetHighScoreOutput {
    /// The high score attributes
    @httpPayload
    highScore: HighScoreAttributes
}
/// Create a new high score
@http(method: "POST", uri: "/high_scores", code: 201)
operation CreateHighScore {
    input: CreateHighScoreInput,
    output: CreateHighScoreOutput,
    errors: [UnprocessableEntityError]
}
/// Input structure for CreateHighScore
structure CreateHighScoreInput {
    /// The high score params
    @required
    highScore: HighScoreParams
}
/// Output structure for CreateHighScore
structure CreateHighScoreOutput {
    /// The high score attributes
    @httpPayload
    highScore: HighScoreAttributes,
    /// The location of the high score
    @httpHeader("Location")
    location: String
}
/// Update a high score
@http(method: "PUT", uri: "/high_scores/{id}")
@idempotent
operation UpdateHighScore {
    input: UpdateHighScoreInput,
    output: UpdateHighScoreOutput,
    errors: [UnprocessableEntityError]
}
/// Input structure for UpdateHighScore
structure UpdateHighScoreInput {
    /// The high score id
    @required
    @httpLabel
    id: String,
    /// The high score params
    highScore: HighScoreParams
}
/// Output structure for UpdateHighScore
structure UpdateHighScoreOutput {
    /// The high score attributes
    @httpPayload
    highScore: HighScoreAttributes
}
/// Delete a high score
@http(method: "DELETE", uri: "/high_scores/{id}")
@idempotent
operation DeleteHighScore {
    input: DeleteHighScoreInput,
    output: DeleteHighScoreOutput
}
/// Input structure for DeleteHighScore
structure DeleteHighScoreInput {
    /// The high score id
    @required
    @httpLabel
    id: String
}
/// Output structure for DeleteHighScore
structure DeleteHighScoreOutput {}
/// List all high scores
@http(method: "GET", uri: "/high_scores")
@readonly
operation ListHighScores {
    output: ListHighScoresOutput
}
/// Output structure for ListHighScores
structure ListHighScoresOutput {
    /// A list of high scores
    @httpPayload
    highScores: HighScores
}
list HighScores {
    member: HighScoreAttributes
}

SDK の生成

モデルと Rails サービスが揃ったら次は SDK を生成します。Smithy のコード生成と統合は Java 環境でのみ利用可能です。今回のデモでは High Score Service SDK がすでに生成され、smithy-ruby レポジトリにコミットされています。必要に応じてこちらからダウンロードしてください。

もし自分で生成したり、Smithy モデルを生成したい場合は、Gradle プロジェクトで smithy-ruby を使用する方法を詳しく説明した README の指示に従えばよいでしょう。

SDK の利用

これで Rails のサービスと SDK ができました。irb -I high_score_service/libirb を起動して試してみましょう。

require 'high_score_service'# Create an instance of HighScoreService's Client.# This is similar to the AWS SDK Clients.# Here we use the endpoint of the Rails service.
client = HighScoreService::Client.new(endpoint: 'http://127.0.0.1:3000')# List all high scores
client.list_high_scores# => #<struct HighScoreService::Types::ListHighScoresOutput high_scores=[]># Try to create a high score# Should raise an UnprocessableEntityError, let's find out whybegin
  client.create_high_score(high_score: { score: 9001, game: 'X' })rescue => e
  puts e.data
  # => #<struct HighScoreService::Types::UnprocessableEntityError errors={"game"=>["is too short (minimum is 2 characters)"]}>end# Actually create a high score
client.create_high_score(high_score: { score: 9001, game: 'Frogger' })# => #<struct HighScoreService::Types::CreateHighScoreOutput# List high scores again
resp = client.get_high_score(id: '1')
resp.high_score# => #<struct HighScoreService::Types::HighScoreAttributes id=1, game="Frogger", score=9001 ... >

練習として、delete_high_scoreupdate_high_score の操作を試してみてください。

今後の予定

今後、AWS SDK For Ruby の新しいサービスクライアントバージョン(gemバージョン2、coreバージョン4)の生成には、smithy-ruby が使用される予定です。

私たちは Smithy Ruby と Rails API の使用例をもっと探したいと思っています。Smithy のモデルをパースして rails newrails generate コマンドのセットに変換できるかもしれません。さらに進んで、「サーバサイド SDK」は具体的な型とプロトコルの構築とパースを処理するプラガブルな Rails エンジンにできるかもしれません。

ご意見、ご要望

質問、コメント、懸念、アイデア、その他のフィードバックがある場合は、smithy-ruby リポジトリに issue または discussion を作成してください。私たちは SDK の設計に関するフィードバックや改善を歓迎し、特にコミュニティによる貢献を歓迎します。

お読みいただきありがとうございました。

Matt より

翻訳は Solutions Architect の光吉が担当しました。原文はこちらです。