gRPC概要

GoaにおけるgRPCの主要概念とProtocol Buffersとの統合について学ぶ

GoaはgRPCサービスの設計と実装のためのファーストクラスサポートを提供しています。このガイドではGoaでgRPCを使用する際の主要な概念を紹介します。

gRPCとは?

gRPCは、以下の特徴を持つ高性能なRPC(Remote Procedure Call)フレームワークです:

  • 効率的なシリアライゼーションのためにProtocol Buffersを使用
  • トランスポート層にHTTP/2を活用
  • 複数のプログラミング言語をサポート
  • ストリーミング通信パターンに対応

GoaのgRPC統合

Goaのg RPCサポートは以下を提供します:

  1. 高レベルな設計: GoaのDSLを使用したサービス定義:
    • Protocol Buffer定義(.protoファイル)
    • サーバーとクライアントのコード
    • 型安全なインターフェース
  2. トランスポートサポート: HTTP/2とgRPCトランスポート層の完全な処理
  3. バリデーション: 組み込みのリクエストバリデーション
  4. エラー処理: ステータスコードを含む構造化されたエラー処理

基本的なサービス構造

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タイプ
Intint32
Int32int32
Int64int64
UIntuint32
UInt32uint32
UInt64uint64
Float32float
Float64double
Stringstring
Booleanbool
Bytesbytes
ArrayOfrepeated
MapOfmap

通信パターン

gRPCは4つの異なる通信パターンをサポートしています。それぞれの例を見てみましょう:

  1. 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の合計")
        })
    })
    
  2. サーバーストリーミング: クライアントが1つのリクエストを送信し、時間とともに複数のレスポンスを受け取ります

    Method("stream", func() {
        Description("指定された開始数値からカウントダウンする数値をストリーミングします")
        Payload(func() {
            Field(1, "start", Int, "カウントダウンを開始する数値")
        })
        // StreamingResultはサーバーが複数のレスポンスを送信することを示します
        StreamingResult(func() {
            Field(1, "count", Int, "カウントダウン中の現在の数値")
        })
    })
    
  3. クライアントストリーミング: クライアントが時間とともに複数のリクエストを送信し、サーバーが1つのレスポンスを送信します

    Method("collect", func() {
        Description("複数の数値を受け取り、その合計を返します")
        // StreamingPayloadはクライアントが複数のリクエストを送信することを示します
        StreamingPayload(func() {
            Field(1, "number", Int, "合計に加算する数値")
        })
        Result(func() {
            Field(1, "total", Int, "受信したすべての数値の合計")
        })
    })
    
  4. 双方向ストリーミング: クライアントとサーバーの両方が時間とともに複数のメッセージを送信できます

    Method("chat", func() {
        Description("両サイドがメッセージを送信できる双方向チャット")
        StreamingPayload(func() {
            Field(1, "message", String, "クライアントからのチャットメッセージ")
        })
        StreamingResult(func() {
            Field(1, "response", String, "サーバーからのチャットメッセージ")
        })
    })
    

各パターンは異なるシナリオに適しています:

  • シンプルなリクエスト-レスポンスの相互作用にはUnary RPCを使用
  • クライアントがデータのストリームを受信する必要がある場合(例:リアルタイム更新)にはサーバーストリーミングを使用
  • サーバーに大量のデータを送信する必要がある場合(例:ファイルのアップロード)にはクライアントストリーミングを使用
  • チャットアプリケーションやリアルタイムゲームなどの複雑な相互作用には双方向ストリーミングを使用

次のステップ

以下のセクションで詳細な情報を提供しています: