Goa provides first-class support for designing and implementing gRPC services. This guide introduces the core concepts of using gRPC with Goa.
gRPC is a high-performance RPC (Remote Procedure Call) framework that:
Goa’s gRPC support provides:
.proto
files)Let’s look at how to define a basic gRPC service in Goa. The following example demonstrates a simple calculator service that adds two numbers:
var _ = Service("calculator", func() {
// Service description helps document the purpose of your service
Description("The Calculator service performs arithmetic operations")
// Enable and configure gRPC transport for this service
GRPC(func() {
// This block can contain gRPC-specific settings like timeouts,
// interceptors, etc.
})
// Define a method named "add" that will be exposed as a gRPC endpoint
Method("add", func() {
// Document what this method does
Description("Add two numbers")
// Define the input message structure (what the client sends)
// Each Field takes: position number, field name, and type
Payload(func() {
Field(1, "a", Int) // First number to add
Field(2, "b", Int) // Second number to add
Required("a", "b") // Both fields are mandatory
})
// Define the output message structure (what the server returns)
Result(func() {
Field(1, "sum", Int) // The result of adding a + b
})
})
})
This code defines a complete gRPC service with one method. The numbers in Field(1, ...)
are Protocol Buffer field numbers, which are required for message serialization.
When you define types in Goa, they are automatically mapped to corresponding Protocol Buffer types. Here’s how Goa types correspond to Protocol Buffer types:
Goa Type | Protocol Buffer Type |
---|---|
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 supports four different communication patterns. Let’s look at each with examples:
Unary RPC: The simplest pattern - client sends one request and gets one response
Method("add", func() {
Description("Simple addition method - takes two numbers and returns their sum")
Payload(func() {
Field(1, "x", Int, "First number")
Field(2, "y", Int, "Second number")
})
Result(func() {
Field(1, "sum", Int, "The sum of x and y")
})
})
Server Streaming: Client sends one request, but receives multiple responses over time
Method("stream", func() {
Description("Streams countdown numbers from the given start number")
Payload(func() {
Field(1, "start", Int, "Number to start counting down from")
})
// StreamingResult indicates server will send multiple responses
StreamingResult(func() {
Field(1, "count", Int, "Current number in the countdown")
})
})
Client Streaming: Client sends multiple requests over time, server sends one response
Method("collect", func() {
Description("Accepts multiple numbers and returns their sum")
// StreamingPayload indicates client will send multiple requests
StreamingPayload(func() {
Field(1, "number", Int, "Number to add to the sum")
})
Result(func() {
Field(1, "total", Int, "Sum of all numbers received")
})
})
Bidirectional Streaming: Both client and server can send multiple messages over time
Method("chat", func() {
Description("Bidirectional chat where both sides can send messages")
StreamingPayload(func() {
Field(1, "message", String, "Chat message from client")
})
StreamingResult(func() {
Field(1, "response", String, "Chat message from server")
})
})
Each pattern is useful for different scenarios:
The following sections provide detailed information about: