You have probably heard of gRPC if you haven't used it. It is particularly popular for inter-service communication due to its efficiency, language agnostic nature and support out of the box for things like MTLS. Here's a really simple example of how you can get started with gRPC and hopefully highlights some of the benefits.

gRPC & Go


You have probably heard of gRPC even if you haven’t used it. It is particularly popular for inter-service communication due to its efficiency, language-agnostic nature, and support out of the box for things like MTLS. Here’s a really simple example of how you can get started with gRPC and hopefully highlights some of the benefits.

For the examples here, I am using Buf which is currently my favourite protobuf tool. Firstly, define a service definition:

syntax = "proto3";

package example;

service ExampleService {
rpc ExampleMethod (ExampleRequest) returns (ExampleResponse);
}

message ExampleRequest {
string name = 1;
}

message ExampleResponse {
string message = 1;
}

Create a buf.gen.yaml (you can also use Buf to generate this automatically). The one below basically says generate me both Go proto code and gRPC bindings:

version: v1
plugins:
- name: go
  out: gen/go
- name: go-grpc
  out: gen/go

Now you can run buf generate. It will generate you a bunch of code in your gen/go folder. Next, we will declare a server and implement the interface of the gRPC server, and start it:

package main

import (
	"context"
	"log"
	"net"

	"google.golang.org/grpc"
	pb "path/to/gen/go/example"
)

type server struct {
	pb.UnimplementedExampleServiceServer
}

func (s *server) ExampleMethod(ctx context.Context, req *pb.ExampleRequest) (*pb.ExampleResponse, error) {
	return &pb.ExampleResponse{Message: "Hello " + req.Name}, nil
}

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

	s := grpc.NewServer()
	pb.RegisterExampleServiceServer(s, &server{})

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

A client can now connect as follows:

package main

import (
	"context"
	"log"
	"time"

	"google.golang.org/grpc"
	pb "path/to/gen/go/example"
)

func main() {
	conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure())
	if err != nil {
		log.Fatalf("did not connect: %v", err)
	}
	defer conn.Close()
	c := pb.NewExampleServiceClient(conn)

	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()

	r, err := c.ExampleMethod(ctx, &pb.ExampleRequest{Name: "world"})
	if err != nil {
		log.Fatalf("could not greet: %v", err)
	}

	log.Printf("Greeting: %s", r.Message)
}

That’s it!

Some next steps for you to consider

  • Try and update the protobuf to add another service method, generate it, and implement it. This will be FAST!
  • Add breaking change detection to your protobuf.
  • Try and generate a client in another language (Java/Node/Python works pretty well).
  • Try and build a protobuf “monorepo” that generates code in CI for multiple languages and does breaking change detection.
  • Try and connect to your service without using grpc.WithInsecure().

After these next steps, you will hopefully see some of the benefits of gRPC and will be ready to present it to your colleagues. Good luck!