はじめのガイド
このガイドでは Goa による完全なサービスの書き方についてひと通り説明します。 この単純なサービスは、Github リポジトリにある基本的な例を実装しています。 説明では Go モジュールの使用を前提としていて、Go のバージョン1.11以降が必要です。
前もって必要なこと
以下の説明はあなたのホームディレクトリ配下に新しいプロジェクトを作成します。
$HOME
は他の場所に置き換えてもかまいません。
このガイドは Go モジュールの使用を想定しています。 (Go 1.13 以降のデフォルト)
cd $HOME
mkdir -p calc/design
cd calc
go mod init calc
次に Goa をインストールします:
go install goa.design/goa/v3/cmd/goa@v3
これから作成するサービスは gRPC を利用するので、 protoc
とprotoc-gen-go
の両方が必要になります。
- releases から
protoc
バイナリをダウンロードしてください。 protoc
がパスに含まれるか確認してください。- Go の protoc プラグインをインストールします:
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
デザイン
次のステップではサービス API を設計していきます。
このステップが明確であることが、Goa フレームワークの主な差別化要因のひとつです:
Goa を使用すると、実装上の問題とは無関係に自分の API について検討し、実装を作成する前にすべての利害関係者とその設計を確認できます。
これは、さまざまなチームがサービスを実装して使用する必要があるような大規模な組織では大きなメリットです。
ファイル $HOME/calc/design/design.go
を開き、以下のコードを記述します:
package design
import (
. "goa.design/goa/v3/dsl"
)
var _ = API("calc", func() {
Title("Calculator Service")
Description("Service for multiplying numbers, a Goa teaser")
Server("calc", func() {
Host("localhost", func() {
URI("http://localhost:8000")
URI("grpc://localhost:8080")
})
})
})
var _ = Service("calc", func() {
Description("The calc service performs operations on numbers.")
Method("multiply", func() {
Payload(func() {
Field(1, "a", Int, "Left operand")
Field(2, "b", Int, "Right operand")
Required("a", "b")
})
Result(Int)
HTTP(func() {
GET("/multiply/{a}/{b}")
})
GRPC(func() {
})
})
Files("/openapi.json", "./gen/http/openapi.json")
})
このデザインは calc
という名前のサービスを記述していて、ひとつのメソッド multiply
が定義されています。
multiply
は2つの整数からなるペイロードを入力として受け取り、ひとつの整数を返します。
このメソッドは、HTTP と gRPC のどちらのトランスポートの対応についても記述しています。
HTTP トランスポートは URL パラメータを使用して入力整数を伝送しますが、gRPC トランスポートはメッセージを使用します(これはデフォルトの動作であるため、デザインに明示されていません)。
HTTP トランスポートと gRPC トランスポートのいずれも、レスポンスにステータスコード OK
を使用します(これもデフォルトです)。
最後に、デザインは HTTP ファイルサーバーのエンドポイントを公開します。これは生成された OpenAPI 仕様を提供します 。
上記の例は、Goa ができることのほんの一部です。 もっと多くの例を examples リポジトリで見つけることが出来るでしょう。 Goa DSL パッケージの GoDoc では、すべての DSL キーワードをそれぞれの説明と使用例付きで一覧できます。
コード生成
goa gen
コマンド
さて、サービスのための設計ができたので、goa gen
コマンドを実行して定型コードを生成できます。
このコマンドは、デザインパッケージのインポートパスを引数として取ります。
また、出力ディレクトリのパスもオプションのフラグとして指定できます。
ここまでデザインは calc
モジュール配下に作成してきたので、コマンドは次のようになります:
goa gen calc/design
このコマンドは、生成したファイルの名前を出力します。 ターゲットディレクトリが指定されていない場合、現在の作業ディレクトリにファイルを生成します。
生成されたファイルは次のようになります:
gen
├── calc
│ ├── client.go
│ ├── endpoints.go
│ └── service.go
├── grpc
│ ├── calc
│ │ ├── client
│ │ │ ├── cli.go
│ │ │ ├── client.go
│ │ │ ├── encode_decode.go
│ │ │ └── types.go
│ │ ├── pb
│ │ │ ├── calc.pb.go
│ │ │ └── calc.proto
│ │ └── server
│ │ ├── encode_decode.go
│ │ ├── server.go
│ │ └── types.go
│ └── cli
│ └── calc
│ └── cli.go
└── http
├── calc
│ ├── client
│ │ ├── cli.go
│ │ ├── client.go
│ │ ├── encode_decode.go
│ │ ├── paths.go
│ │ └── types.go
│ └── server
│ ├── encode_decode.go
│ ├── paths.go
│ ├── server.go
│ └── types.go
├── cli
│ └── calc
│ └── cli.go
├── openapi.json
└── openapi.yaml
gen
ディレクトリはトランスポートに依存しないサービスコードを格納する calc
サブディレクトリを含みます。
endpoints.go
ファイルはトランスポートに依存しないサービスコードをトランスポート層に公開する Goa エンドポイント を作成します。
grpc
ディレクトリには、 protoc
ツールの出力(pb/calc.pb.go) はもちろん、 calc
gRPC サービスを記述するプロトコルバッファファイル (pb/calc.proto) も含まれています。
このディレクトリには、リクエストとレスポンスをエンコード・デコードするためのロジックとともに、protoc で生成された gRPC サーバーとクライアントのコードを接続するサーバーとクライアントのコードも含まれています。
最後に cli
サブディレクトリはコマンドラインから gRPC リクエストをビルドするための CLI コードを含みます。
http
サブディレクトリは HTTP トランスポートを記述しており、リクエストとレスポンスをエンコード・デコードするロジックを持つサーバとクライアントのコード、コマンドラインから HTTP リクエストを構築する CLI コードを定義しています。
また、Open API 2.0 仕様のファイルも json と yaml 両方の形式で含まれています。
goa example
コマンド
さてここで、goa example
コマンドを実行して、サービスの基本的な実装を生成することができます。
この実装には、goroutine を起動して HTTP と gRPC サーバーを起動するビルド可能なサーバーファイルと、
サーバーにリクエストを送ることができるクライアントファイルを伴います。
注:
goa gen
によって生成されたコードは編集できません。 このディレクトリは、コマンドが実行されるたびに(たとえば、デザインが変更された後に)完全に最初から再生成されます。 これは、生成されたコードと生成されていないコードとの間のインターフェースをきれいに保ち、標準の Go 構成(つまり関数呼び出し)を使用するための仕様です。 しかしgoa example
によって生成されたコードは あなたの コードです。 修正したり、テストを追加したりする必要があります。 このコマンドは、自力の開発を助けるサービスの開始点を生成します - 特に、デザインが変更されたときに再実行することを意図して いません 。 代わりに単にファイルを編集してください。
goa example calc/design
goa example
コマンドは以下のファイルを生成します:
├── calc.go
├── cmd
│ ├── calc
│ │ ├── grpc.go
│ │ ├── http.go
│ │ └── main.go
│ └── calc-cli
│ ├── grpc.go
│ ├── http.go
│ └── main.go
calc.go
はデザインに記述された multiply
メソッドのダミー実装を含みます。
すべきこととして残されているのは、実際の実装を与え、ビルドし、サーバやクライアントを実行することだけです。
calc.go
ファイルを開き、Multiply
メソッドを実装します:
func (s *calcsrvc) Multiply(ctx context.Context, p *calc.MultiplyPayload) (res int, err error) {
return p.A * p.B, nil
}
goa example
コマンドは、デザインに記述されている任意の Server DSL を使用して、ビルド可能なサーバーファイルとクライアントファイルを生成します。
デザインで指定されたそれぞれの Server
DSL に対して cmd
に一つのディレクトリを構築します。
ここではポート 8000 で HTTP リクエストを待ち受けるサーバー calc
をひとつ定義しました。
サービスをビルドし実行する
生成されたサーバーとクライアントのコードは次のようにビルドされ実行されます:
go build ./cmd/calc && go build ./cmd/calc-cli
# サーバーの実行
./calc
[calcapi] 21:35:36 HTTP "Multiply" mounted on GET /multiply/{a}/{b}
[calcapi] 21:35:36 HTTP "./gen/http/openapi.json" mounted on GET /openapi.json
[calcapi] 21:35:36 serving gRPC method calc.Calc/Multiply
[calcapi] 21:35:36 HTTP server listening on "localhost:8000"
[calcapi] 21:35:36 gRPC server listening on "localhost:8080"
# クライアントの実行
# HTTP サーバーと交信
$ ./calc-cli --url="http://localhost:8000" calc multiply --a 1 --b 2
2
# gRPC サーバーと交信
$ ./calc-cli --url="grpc://localhost:8080" calc multiply --message '{"a": 1, "b": 2}'
2
まとめと次のステップ
Goa は、サーバとクライアントのコードおよびドキュメントを自動的に生成できる信頼できる唯一の情報源を記述することが可能で、そのことによって、サービス開発を加速することが出来ます。 API デザインに焦点を当てることができるため、実装を開始する前にチームが API を確認して合意することができ、堅牢でスケーラブルな開発プロセスが可能になります。 デザインが完成したら、生成されたコードは入力バリデーションを含むデータの Marshal、Unmarshal に関わるすべての面倒な作業を引き受けます(たとえば、整数以外の値を使用して calc サービスを呼び出してみてください)。
この例は Goa の基本にのみ触れています。デザインのデザイン概要 では他の多くの側面もカバーしています。 ぜひ他の例も見てみてください。 最後に、DSL パッケージ GoDoc には多くのコードスニペットが含まれており、デザインを作成する際の参考資料として役立ちます。