gRPC概要
GoaにおけるgRPCの主要概念とProtocol Buffersとの統合について学ぶ
GoaはgRPCサービスの設計と実装のためのファーストクラスサポートを提供しています。このガイドではGoaでgRPCを使用する際の主要な概念を紹介します。
gRPCとは?
gRPCは、以下の特徴を持つ高性能なRPC(Remote Procedure Call)フレームワークです:
- 効率的なシリアライゼーションのためにProtocol Buffersを使用
- トランスポート層にHTTP/2を活用
- 複数のプログラミング言語をサポート
- ストリーミング通信パターンに対応
GoaのgRPC統合
Goaのg RPCサポートは以下を提供します:
- 高レベルな設計: GoaのDSLを使用したサービス定義:
- Protocol Buffer定義(
.protoファイル) - サーバーとクライアントのコード
- 型安全なインターフェース
- Protocol Buffer定義(
- トランスポートサポート: HTTP/2とgRPCトランスポート層の完全な処理
- バリデーション: 組み込みのリクエストバリデーション
- エラー処理: ステータスコードを含む構造化されたエラー処理
基本的なサービス構造
GoaでgRPCサービスを定義する方法を見てみましょう。以下の例は、2つの数値を加算する簡単な計算機サービスを示しています:
var _ = Service("calculator", func() {
// サービスの説明はサービスの目的を文書化するのに役立ちます
Description("計算機サービスは算術演算を実行します")
// このサービスのgRPCトランスポートを有効化し設定します
GRPC(func() {
// このブロックにはタイムアウトやインターセプターなど、
// gRPC固有の設定を含めることができます
})
// gRPCエンドポイントとして公開される"add"メソッドを定義します
Method("add", func() {
// このメソッドの機能を文書化します
Description("2つの数値を加算します")
// 入力メッセージ構造を定義します(クライアントが送信するもの)
// 各Fieldは:位置番号、フィールド名、型を取ります
Payload(func() {
Field(1, "a", Int) // 加算する1つ目の数値
Field(2, "b", Int) // 加算する2つ目の数値
Required("a", "b") // 両方のフィールドは必須です
})
// 出力メッセージ構造を定義します(サーバーが返すもの)
Result(func() {
Field(1, "sum", Int) // a + bの結果
})
})
})
このコードは1つのメソッドを持つ完全なgRPCサービスを定義しています。Field(1, ...)の数値はメッセージシリアライゼーションに必要なProtocol Bufferのフィールド番号です。
Protocol Bufferとの統合
Goaでタイプを定義すると、自動的に対応するProtocol Bufferタイプにマッピングされます。以下がGoaタイプとProtocol Bufferタイプの対応関係です:
| Goaタイプ | Protocol Bufferタイプ |
|---|---|
| Int | int32 |
| Int32 | int32 |
| Int64 | int64 |
| UInt | uint32 |
| UInt32 | uint32 |
| UInt64 | uint64 |
| Float32 | float |
| Float64 | double |
| String | string |
| Boolean | bool |
| Bytes | bytes |
| ArrayOf | repeated |
| MapOf | map |
通信パターン
gRPCは4つの異なる通信パターンをサポートしています。それぞれの例を見てみましょう:
Unary RPC: 最もシンプルなパターン - クライアントが1つのリクエストを送信し、1つのレスポンスを受け取ります
Method("add", func() { Description("シンプルな加算メソッド - 2つの数値を受け取り、その合計を返します") Payload(func() { Field(1, "x", Int, "1つ目の数値") Field(2, "y", Int, "2つ目の数値") }) Result(func() { Field(1, "sum", Int, "xとyの合計") }) })サーバーストリーミング: クライアントが1つのリクエストを送信し、時間とともに複数のレスポンスを受け取ります
Method("stream", func() { Description("指定された開始数値からカウントダウンする数値をストリーミングします") Payload(func() { Field(1, "start", Int, "カウントダウンを開始する数値") }) // StreamingResultはサーバーが複数のレスポンスを送信することを示します StreamingResult(func() { Field(1, "count", Int, "カウントダウン中の現在の数値") }) })クライアントストリーミング: クライアントが時間とともに複数のリクエストを送信し、サーバーが1つのレスポンスを送信します
Method("collect", func() { Description("複数の数値を受け取り、その合計を返します") // StreamingPayloadはクライアントが複数のリクエストを送信することを示します StreamingPayload(func() { Field(1, "number", Int, "合計に加算する数値") }) Result(func() { Field(1, "total", Int, "受信したすべての数値の合計") }) })双方向ストリーミング: クライアントとサーバーの両方が時間とともに複数のメッセージを送信できます
Method("chat", func() { Description("両サイドがメッセージを送信できる双方向チャット") StreamingPayload(func() { Field(1, "message", String, "クライアントからのチャットメッセージ") }) StreamingResult(func() { Field(1, "response", String, "サーバーからのチャットメッセージ") }) })
各パターンは異なるシナリオに適しています:
- シンプルなリクエスト-レスポンスの相互作用にはUnary RPCを使用
- クライアントがデータのストリームを受信する必要がある場合(例:リアルタイム更新)にはサーバーストリーミングを使用
- サーバーに大量のデータを送信する必要がある場合(例:ファイルのアップロード)にはクライアントストリーミングを使用
- チャットアプリケーションやリアルタイムゲームなどの複雑な相互作用には双方向ストリーミングを使用
次のステップ
以下のセクションで詳細な情報を提供しています:
- サービス設計: サービス定義の詳細ガイド
- ストリーミングパターン: ストリーミング実装の詳細
- エラー処理: 包括的なエラー処理
- 実装: サーバーとクライアントの実装
- トランスポートと設定: 高度なトランスポートのトピック