Introduction to gRPC in Go

When building distributed systems, efficient and robust communication is crucial. gRPC makes this a breeze! gRPC is an open-source RPC (Remote Procedure Call) framework, developed by Google. It runs over HTTP/2 (which enables some of it's features), using Protocol Buffers (or Protobuf for short) for data serialization, it's designed to be language-agnostic. This means that although it is often using with Go, it works great with other languages too!

gRPC

gRPC is primarily designed for distributed systems as it has, built-in support for things like load balancing, tracing, health checking, and authentication. It is also modular, so it's easy to configure just the bits you need.

Workflow of gRPC

The workflow for building gRPC services typically involves the following steps:

Workflow
  • Define a Protobuf Contract: Define the RPC methods and the request/response structures using Protobuf.
  • Compile Protobuf Files: Use the Protobuf compiler (protoc) to generate client and server interfaces. This can be run with various plugins for the desired programming languages.
  • Implement Server Logic: Implement the server logic by providing concrete implementations of the RPC methods.
  • Initialize Client Stubs: Use the generated client stubs to make RPC calls to the server.

Why Choose gRPC?

Strong Message Structure

gRPC is entirely contract-based, meaning all requests and responses are defined upfront. This makes it easy for developers to see exactly what data is expected, helping minimize the risk of breaking changes when updates are made.

This makes it particularly useful for microservice architectures where lightweight services need to communicate frequently. With contracts defined using protobuf, communication is type-safe, reducing the possibility of mismatches and errors.

Efficiency

Built on HTTP/2, gRPC is faster and more efficient than traditional HTTP/1-based protocols. This results in faster, more efficient communication and reduces network delay through the use of multiplexing.

Multiplexing allows a client to fire off multiple requests at once on the same connection and receive the responses back in any order. This provides a framework for long-lived connections, which increases network efficiency and allows for real-time communication.

This enables some quite powerful functionality like bi-directional streaming between client and server. Bi-directional streaming allows both the client and server to independently send streams of messages to each other.

This makes gRPC highly scalable and particularly well-suited for use cases where a large volume of messages is exchanged, such as gaming servers or financial tickers.

Broad Language Support

gRPC also offers native support for multiple programming languages, covering nearly all major languages. That’s with native code generation instead of using third-party tools.

This is really useful in architectures where you might have a lot of different microservices that are written in a number of different languages, for example.

For instance, at very large companies, you might have five, six, seven different languages being used by different teams. Protobuf makes it very easy to create SDKs and communicate between them.

Faster Message Transmission

Protobuf messaging in gRPC is marshaled into a compact binary format before being sent over the network. As a result, Protobuf messages are much smaller than text-based formats like JSON. Reduced message sizes allow data to be transferred more quickly, which not only decreases network load but can also provide a better experience for our users!.

gRPC also supports built-in message compression, which can further reduce the amount of data sent over the network. This makes it a good fit for bandwidth-constrained or low-power networks (for example IoT).

Comparing gRPC, REST and GraphQL

You might be wondering how gRPC compares to other API technologies you have heard of or used like REST and GraphQL. These all provide similar functionality with regards to enabling communication between services, but there are some important differences. While gRPC is optimized for high performance and strongly typed contracts, REST is more widely adopted with simpler browser support, whereas GraphQL offers flexible, efficient data querying. Here's a handy comparison table.

gRPCRESTGraphQL
ProtocolHTTP/2HTTP/1.1HTTP/1.1
Data FormatProtocol Buffers (binary)JSON, XML (text)JSON (text)
PerformanceHigher performanceLower performanceVaries, optimized for reducing over-fetching or under-fetching
StreamingBi-directionalN/A (request-response model)Partial (subscriptions allow for real-time updates via WebSockets)
TypingStrongly typedFlexibleStrongly typed (via schema)
Browser SupportLimited, requires gRPC-WebBroadly supportedBroadly supported
Error HandlingExplicit status codesHTTP status codesCustom error handling in the response format
Use CasesMicroservices, high-performance/low-latency applicationsWeb APIs, better for diverse clients including browsersEfficient querying over broad datasets, reduces over-fetching/under-fetching, complex queries

Types of gRPC APIs

Types of API

gRPC supports four types of APIs:

  1. Unary: A single request followed by a single response.
    • Example Use Case: Fetching a user profile.
  2. Server-side Streaming: A single initial request followed by a stream of responses.
    • Example Use Case: Streaming log entries or a large dataset.
  3. Client-side Streaming: A stream of requests followed by a single final response.
    • Example Use Case: Uploading a file in chunks.
  4. Bi-directional Streaming: Both client and server send a stream of messages to each other independently.
    • Example Use Case: Real-time chat application.

Selecting the right type of API depends on the requirements of your service. Consider factors like the nature of the data, the interaction pattern needed, and the network environment.

Building a Simple gRPC Service in Go

Ok so we now know all the theory, let’s walk through how to build a simple gRPC service in Go that implements a "Hello World" example.

Step 1: Define the Protobuf Contract

To start, create a subdirectory for Protobuf files and a .proto file.

$ mkdir proto
$ touch proto/hello.proto

The hello.proto file will contain the gRPC service contract:

syntax = "proto3";

package hello;

option go_package = "github.com/yourusername/grpc-go-example/proto";

service HelloService {
  rpc SayHello (SayHelloRequest) returns (SayHelloResponse);
}

message SayHelloRequest {
  string name = 1;
}

message SayHelloResponse {
  string message = 1;
}

This includes the service definition, along with the structure of both the requests and responses. It also includes a go_package, this allows us to define the correct Go package based on our Go module.

Step 2: Compile the Protobuf File

Compile the protocol buffers code and generate our Go client and server stubs.

To install the Protobuf compiler and the Go plugins:

$ brew install protobuf
$ go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
$ go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest

Compile the Protobuf file:

$ protoc --go_out=. --go_opt=paths=source_relative \
  --go-grpc_out=. --go-grpc_opt=paths=source_relative \
  proto/hello.proto

This will generate the Go code for both the message definitions and the gRPC client/server stubs, in the same directory as the hello.proto file.

Step 3: Implement the Server

Implement the RPC

Create an internal package to contain your RPC implementation:

$ mkdir -p internal/hello
$ touch internal/hello/hello.go

Your new struct should implement the generated HelloServiceServer interface.

package hello

import (
  "context"
  "fmt"

  "google.golang.org/grpc/codes"
  "google.golang.org/grpc/status"

  "github.com/yourusername/grpc-course/proto"
)

type Server struct {
   proto.UnimplementedHelloServiceServer
}

func (s *Server) SayHello(ctx context.Context, req *proto.SayHelloRequest) (*proto.SayHelloResponse, error) {
  if req.GetName() == "" {
    return nil, status.Error(codes.InvalidArgument, "name cannot be empty")
  }

   return &proto.SayHelloResponse{
      Message: fmt.Sprintf("Hello %s", req.GetName()),
   }, nil
}

In this example, we are returning a message with the provided name in the response. If the name sent in the request is empty, we will return an INVALID_ARGUMENT status code, which is the equivalent of a 400 Bad Request.

Startup the Server

Create a directory for the server and a main.go file:

$ mkdir -p cmd/server
$ touch cmd/server/main.go

Implement the server in main.go:

package main

import (
   "log"
   "net"

   "google.golang.org/grpc"

   "github.com/yourusername/grpc-course/internal/hello"
   "github.com/yourusername/grpc-course/proto"
)

func main() {
   lis, err := net.Listen("tcp", ":50051")
   if err != nil {
      log.Fatalf("Failed to listen: %v", err)
   }
   defer lis.Close()

   grpcServer := grpc.NewServer()

   // register our RPC implementation on the gRPC server.
   proto.RegisterHelloServiceServer(grpcServer, &hello.Server{})

   log.Println("Server is running on port 50051")

   if err := grpcServer.Serve(lis); err != nil {
      log.Fatalf("Failed to serve: %v", err)
   }
}

This will spin up a server on port 50051, and register and expose our RPCs.

Step 4: Implement the Client

Create a separate directory for the client application and a main.go file:

$ mkdir -p cmd/client
$ touch cmd/client/main.go

Implement the client in main.go:

package main

import (
   "context"
   "log"

   "google.golang.org/grpc"

   "github.com/yourusername/grpc-go-example/proto"
)

func main() {
   ctx := context.Background()

   // use `WithInsecure` to make calls in plaintext (without TLS).
   conn, err := grpc.NewClient("localhost:50051", grpc.WithInsecure())
   if err != nil {
    log.Fatalf("Failed to connect: %v", err)
   }
   defer conn.Close()

   c := proto.NewHelloServiceClient(conn)

   r, err := c.SayHello(ctx, &proto.SayHelloRequest{Name: "Chris"})
   if err != nil {
    log.Fatalf("Failed to greet: %v", err)
   }

   log.Printf("Successful response received: %s", r.GetMessage())
}

Step 5: Run the Server and Client

Open two terminal windows. In the first terminal, run the server:

$ go run cmd/server/main.go

In the second terminal, run the client:

$ go run cmd/client/main.go

You should see the server log that it is running and the client log the greeting it received from the server.

Wrapping Up

gRPC is a powerful framework for building high-performance, language-agnostic microservices (with exceptionally good support for Go). Its use of HTTP/2 and Protobuf provides improvements in efficiency and performance compared to REST.

If you’re interested in learning more and diving deeper into gRPC, check out this course on Building Production-Ready Services with gRPC and Go, which covers everything from the basics of gRPC to advanced topics, such as authentication, testing and deployment.

No prior knowledge of gRPC is needed and it will teach you everything you need to know.