Goベースのマイクロサービスフレームワーク"goa"によるサービスAPIの定義,レビュー,実装

RightScaleのシニアシステムアーキテクトであるRaphael Simon氏が,GoベースのHTTPマイクロサービスフレームワーク“goa”を開発した。DSL(Domain-Specific Language)によるサービスAPIの定義と,対応するサーバとクライアントの“ボイラプレート”コード,およびドキュメントの自動生成が可能だ。

goaを紹介するGopher Academyのブログ記事には,RightScaleのエンジニアリングチームが実施中の,モノシリックRuby on RailsアプリケーションからGo(言語)ベースのマイクロサービスアプリケーションセットへの移行作業について述べられている。このマイグレーションで大きな課題となっているのが,設計の行き届いたサービスAPIの作成だ。そのためにマイクロサービスAPIの設計とレビュー,実装をサポートするツール群が開発された。この作業の成果のひとつとして,RightScaleのRubyベースのAPI開発フレームワークであるPraxisの影響を受けて開発されたのが,Goベースのフレームワークであるgoaだ。

InfoQは,中心的なコントリビュータRightScaleのシニアアーキテクトであるSimon氏と席を共にして,マイクロサービスフレームワークであるgoaの設計目標と使用状況,今後の計画について聞いた。

InfoQ: ようこそ,Raphaelさん! まずは‘goa’とは何かを説明して頂けますか? フレームワークを開発しようと思った動機についてもお願いします。

Simon: RightScaleはかなり前からマイクロサービスアーキテクチャへの移行に取り組んできましたが,最近になって,サービスの開発にGo言語の使用を始めました。全体的に言うと,Goは開発と運用のどちらの面からも,素晴らしい選択であることが分かりました。習得も容易ですし,作成したサービスのパフォーマンスも良好です。

しかしながら,Goを採用するということは,開発とデプロイの面で新たなスタックが追加されることでもあります – RightScaleでは,最近までもっぱらRubyを利用していましたから。開発チームはREST APIの開発経験が豊富でした(RightScaleは,RubyでREST APIを開発するPraxisフレームワークをオープンソースで提供しています)から,その経験を活用してGoベースのサービス開発スタックを構築するというのは合理的な選択でした。そのような目的にかなう既存のソリューションは,それまで存在しなかったのです。

その開発の成果がgoaです。goaは,APIのデザインをコードで記述することが可能なマイクロサービス開発用のフレームワークで,設計コードからHTTPサーバ用のラッパやスキャホールディング(scaffolding),ドキュメント,クライアント,さらにカスタム出力を含むさまざまな出力を生成するgoagenツールが提供されています。さらにパッケージには,サービスや生成されたコードのサービス実装に利用可能な,数多くのサポートモジュールも含まれています。

InfoQ: 単純に標準Goライブラリ(あるいはPeter Bourgon氏のgo-kit)を使用する場合と比較して,‘goa’を使うことでマイクロサービス開発はどのように容易になるのでしょう?

Simon: goaはAPIの意図(intent)を記述することができます – goaデザイン言語を使用して,APIが公開するリソースやアクション(つまりAPIエンドポイント)を定義することが可能なのです。アクションにはそれぞれ,期待されるリクエストの状態と可能なレスポンスを記述します。このアプローチには多くのメリットがあります – 生成されたSwagger仕様をSwagger UIを使って確認するなどの方法で,さまざまなチームのフィードバックを設計段階から得られることも,そのひとつです。UIの開発やユーザが目にする資料を書くチームにとって,このフィードバックループによるメリットには絶大なものがあります。何よりも素晴らしいのは,実際にコードを書く前にこれが実現するということです。

もうひとつの利点は,設計が完了すれば,後はgoaがリクエストの検証やリクエスト状態を記述するカスタムデータ構造など,定型的なコード(ボイラプレート)をすべて生成してくれることです。設計の結果は重要な実装情報として利用されます。実装のメンテナンスと設計の同期を維持するのはやっかいな作業ですから,この点は非常に重要です。goaで生成されたリクエストハンドラのコンテキストは,設計時に記述されたデータ構造を返すアクセサを公開するように,それぞれのアクションに合わせて調整されています。これによって“バインディング”や,あるいは受信したリクエストのフォーマット検証を手作業で行う必要性が軽減されるのです。

goagenでは,コマンドラインツールやGoパッケージ,Javaスクリプトによるクライアントなど,APIのクライアントも生成することができます。このプロセスを手作業で行なうのに要する時間削減という当然のメリットの他にも,さまざまなクライアント実装間での一貫性の維持や,APIに合わせたアップデートという面での有効性があります。サービスの数が増え続けているマイクロサービスの世界では,インテグレーションの構築プロセスを極めて容易なものにするという点で,一貫性という側面は非常に重要です。

InfoQ: ‘goa’はランタイム’エンジン’やロギングのサポート,‘ミドルウェア’のカスタマイズ,エラー処理など,多彩な機能を開発者に提供しているように思いますが,Goベースのマイクロサービスのための完結したソリューションを意図しているのでしょうか?

Simon: goaのランタイムエンジンの中心はリクエストコンテキストです。インターフェース間でコンテキストを転送する部分では,golangパッケージの開発者たちの成果を活用することによって,デッドラインの設定やgoroutine間での状態共有などの強力な機能を実現しています。外部からのリクエストを実行するソフトウェアモジュールなどに対して,幅広くコンテキストを提供できるので,デッドラインを含むすべてのリクエスト状態が,すべてのステージで参照可能になります。コンテキストは,生成されたコードによってアクションに固有のデータ構造にラップされた上で,デザインコードで定義されているフィールドを通じて,検証済みのリクエスト状態を公開します。

コンテキスト以外はすべて交換が可能です – goaは“バッテリ内蔵”モデルに従っています。例えば,エラー処理の方法に特別な要求がないのであれば,実用的な処理(この場合はステータスコード500を返します)がデフォルトで提供されていますが,これをオーバーライドしてカスタムロジックを実装することも簡単です。goa自体でも,応答内にコールスタックを表示しない代替エラーハンドラを提供しています。もうひとつの例はミドルウェアのサポートです。goaは独自のミドルウェアを定義しているので,goaのリッチなコンテキストを活用することも,あるいはHTTPミドルウェアを直接利用することも可能です。

まだ簡単には交換できない部分もありますが(ログ処理のように),これはコード生成で当初から重視していた成果のひとつです。goaは,モジュール化された完全なマイクロサービス開発フレームワークの提供を目指しています。例えば現在,go-kitその他のGoパッケージの機能を利用しているのであれば,今後もそれを使い続けることができると同時に,goaがプロモートするデザインベースAPI開発アプローチのメリットも享受できるのです。

InfoQ: goaの仕様を読んで特に興味深かったのは,サービスAPIを利用するクライアントのコードが生成可能なことです。一般的にコード生成に関する議論では,生成されるコードの複雑さや,生成プロセスが不明確でカスタマイズできないなど,否定的な意見が少なくないのですが,このような実装を選択した理由について説明して頂けますか?

Simon: 生成するコードが単純で定型的な機能を実装したものだということが,ここではポイントです。生成されるコードは,あなたが“手書き”する場合に使うであろうものと同じ構造に従います。goagenは‘驚き最小の原則(principle of least surprise)’に可能な限り従っているのです。最終的なコードに関しては,例えば,同じことをするために必要な数千行のコーディングを回避できるというメリットを考えれば,結果的にはより“標準的”(あるいは効率的)だという考え方もできると思います。クライアントのGoパッケージを見ても,それが自動生成したものだと推測するのは難しいでしょう – ローカル変数の命名がちょっと変なので,それと分かる程度だと思います :)

goaのコード生成でもう一つ重要なのは,生成されたコードが必ず,手作業で修正する必要のない別パッケージにまとめられている点です。つまり,生成されたコードとユーザのコードが明確に区別されているのです。goagenは常にパッケージ全体を再生成するので,両方のタイプのコードを混在することはできません。唯一の例外はスキャホールディングコードです。これはgoagenで,ブートストラップ時に一度だけ生成することができます。

最後に,goagenは必要なものだけを生成します – 生成されたコードでは,ロギングやエラー処理といった共通機能部分はすべてgoaパッケージを使用しています。このため生成されたコードを修正しなくても,その動作を簡単にカスタマイズすることができます – 例えば,独自のサービスエラーハンドラを実装することで,生成されたすべてのコードがそのカスタムロジックを使うようになります。生成されたコードはgoaパッケージと一緒に自分自身を”プラグイン”されて,その動作を変更するためにのノブを外部に公開しているものと考えればよいでしょう。

goagenはプラグインをサポートしてますが,これはパブリックなGenerate関数を持った標準Goパッケージ以外の何ものでもありません。プロジェクトへの最初のコントリビューションのひとつは,Brian Ketelsen氏の書いたgormaプラグインです。これは API設計に記述されたタイプからgormモデルを生成します。コード(それ以外のアーティファクトも)の生成がオープンになっている点は,このプロジェクトの驚くべき成果のひとつです。この開発は,まさにGoならではのものだと確信しています。

InfoQ: プロジェクトに関心を持ったInfoQ読者が‘goa’プロジェクトにコントリビュートするには,どうすれば最もよいでしょうか?

Simon: GitHubリポジトリのWiKiにあるロードマップ資料に,たくさんの提案があげられています – ただし,あくまでも提案ですが。プロジェクトをやっていて最もやりがいを感じるのは,それがコミュニティに速やかに受け入れられて,思いもよらなかった面でさまざまなコントリビューションが提供されることです。goaはまだプロジェクトとして初期段階ですから,いろいろな方向に成長の余地を残していますし,gophers.slack.comには活発な#goa slackチャネルもあります。ですからまずはリポジトリを見て,slackチャネルに参加して,第一歩を踏み出してください!

特に今,プロジェクトが支援を求めている分野がひとつあります – ロゴを募集中です!それからWebサイトのリニューアルもできるといいのですか ;)

InfoQ: 今日はお時間を頂いて,ありがとうございました。他に何か,InfoQ読者に伝えておきたいことがありますか?

Simon: お招き頂いてありがとうございます! API開発の必要な読者のみなさんには,ぜひgoaを試して頂きたいと思います。効率的なサービス開発が可能になると信じています。マイクロサービスが世界中で急速にデファクトスタンダードになりつつあり,優れたAPIを備えることがこれまで以上に重要です。優秀なツールを使うことで,大きな違いを生み出すことができるのです。利用が増えることでgoaがさらに進化するのを見るのが楽しみです。他の人たちが何かをしてくれるのか,待ち遠しく思っています。

goaマイクロサービスフレームワークに関する詳細は,Gopher Academyのブログ記事 “goa: Untangling Microservices”や,goa Githubリポジトリで見ることができる。