gRPC in Golang: Building Fast and Scalable Services

Introduction

Imagine you’re building an app where multiple services need to talk to each other—like a shopping app where one service handles products, another handles payments, and another manages user accounts. For services to talk fast, especially when scaling up, gRPC is a great solution. Let's break down what gRPC is, why it’s useful, and how to set it up in Golang.

Section 1: What is gRPC?

gRPC (gRPC Remote Procedure Call) is a high-performance framework that lets programs communicate over the network. Created by Google, it uses HTTP/2 for faster communication and Protocol Buffers (protobufs) for defining the data structures, which are more efficient than JSON.

Key points to highlight:

  • Language agnostic – Works with multiple languages (not just Golang).

  • Fast and efficient – Uses HTTP/2 for multiplexing requests and Protocol Buffers for optimized data serialization.

  • Supports streaming – Allows real-time data streams between client and server.

Section 2: Setting Up gRPC in Golang

Let’s go through setting up gRPC in Golang. We’ll build a basic service that can get and set user information.

Step 1: Install gRPC and Protocol Buffers

In your project directory, start by installing the following dependencies:

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

Make sure protoc is also installed on your system. This will compile our .proto files into Go code.

Step 2: Define the Protocol Buffer File

In gRPC, you define your service interface and data structures in a .proto file. Create a file named user.proto:

syntax = "proto3";

package user;

service UserService {
  rpc GetUser (UserRequest) returns (UserResponse);
  rpc SetUser (UserRequest) returns (UserResponse);
}

message UserRequest {
  string name = 1;
}

message UserResponse {
  string message = 1;
}

Here’s what’s happening:

  • service UserService defines our service, which has two RPC methods: GetUser and SetUser.

  • UserRequest and UserResponse define the message structures used by our methods. UserRequest takes a name, and UserResponse returns a message.

Step 3: Generate Go Code from the Proto File

Run this command to generate the Go code:

protoc --go_out=. --go-grpc_out=. user.proto

This generates files with Go code for our protobuf and gRPC services.

Section 3: Implementing the Server

Now that we have our gRPC code, let’s implement the server. Create a file named server.go:

package main

import (
    "context"
    "fmt"
    "log"
    "net"

    "google.golang.org/grpc"
    pb "path/to/your/generated/proto/files"
)

type server struct {
    pb.UnimplementedUserServiceServer
}

func (s *server) GetUser(ctx context.Context, req *pb.UserRequest) (*pb.UserResponse, error) {
    message := fmt.Sprintf("Hello, %s!", req.Name)
    return &pb.UserResponse{Message: message}, nil
}

func (s *server) SetUser(ctx context.Context, req *pb.UserRequest) (*pb.UserResponse, error) {
    message := fmt.Sprintf("User %s has been saved!", req.Name)
    return &pb.UserResponse{Message: message}, nil
}

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

    grpcServer := grpc.NewServer()
    pb.RegisterUserServiceServer(grpcServer, &server{})

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

Explanation:

  1. Define the Server – The server struct implements the UserService interface.

  2. Implement MethodsGetUser and SetUser methods generate and return a UserResponse.

  3. Start the Server – In main, we set up a TCP listener on port 50051, create a new gRPC server, and register our UserService.

Section 4: Implementing the Client

To test this out, we’ll create a client in client.go.

package main

import (
    "context"
    "log"
    "time"

    "google.golang.org/grpc"
    pb "path/to/your/generated/proto/files"
)

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

    client := pb.NewUserServiceClient(conn)

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

    res, err := client.GetUser(ctx, &pb.UserRequest{Name: "John"})
    if err != nil {
        log.Fatalf("Could not get user: %v", err)
    }
    log.Printf("GetUser Response: %s", res.Message)

    res, err = client.SetUser(ctx, &pb.UserRequest{Name: "John"})
    if err != nil {
        log.Fatalf("Could not set user: %v", err)
    }
    log.Printf("SetUser Response: %s", res.Message)
}

Explanation:

  1. Create Connection – We connect to the gRPC server running on localhost:50051.

  2. Create Client – We create a new UserService client from our generated code.

  3. Make Calls – We call GetUser and SetUser with a context that has a timeout, printing the responses.

Conclusion

Now you have a basic gRPC service up and running in Golang! We covered:

  1. Defining a gRPC service using Protocol Buffers.

  2. Setting up and running a gRPC server.

  3. Creating a client to test our gRPC service.

0
Subscribe to my newsletter

Read articles from Oluwajuwon Falore directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Oluwajuwon Falore
Oluwajuwon Falore

I am a full-Stack (backend leaning) software developer. Experienced with all stages of the development cycle for dynamic web projects. Well-versed in programming languages including HTML5, CSS, JAVASCRIPT, NODEJS, GOLANG, REACTJS, PYTHON, ANGULAR and IONIC.