Go and gRPC

What is gRPC?

First, let’s talk about gRPC.

gRPC, which stands for “gRPC Remote Procedure Call,” is a remote procedure call (RPC) framework that uses HTTP/2 for transport, Protocol Buffers (Protobuf) as its interface description language, and provides features like bidirectional streaming and flow control. The combination of these technologies makes gRPC a powerful choice for building efficient and interoperable APIs.

The Problems gRPC Solves

  • Efficient Communication: gRPC uses HTTP/2, providing multiplexing and parallelism for reduced latency and improved efficiency.

  • Language Agnostic: With support for multiple languages, gRPC allows services to be written in different languages, promoting interoperability.

  • Automatic Code Generation: Protobuf enables automatic generation of client and server code in various programming languages, reducing boilerplate code.

  • Bidirectional Streaming: gRPC supports bidirectional streaming, allowing multiple messages to be sent and received asynchronously.

Pros of gRPC

  • Performance: The binary Protobuf format and the use of HTTP/2 contribute to faster and more efficient communication.

  • IDL for Clear Contract: The Interface Definition Language (IDL) provided by Protobuf ensures a clear contract between services, making it easier to maintain and understand.

  • Streaming Support: gRPC’s bidirectional streaming is ideal for real-time applications, such as chat systems or live updates.

  • Code Generation: Automatic code generation simplifies the development process and reduces the chance of errors.

Cons of gRPC

  • Complexity: The learning curve for gRPC, especially for developers new to Protobuf and HTTP/2, can be steep.

  • Debugging: Debugging gRPC services can be challenging, and tools are not as mature as those for traditional REST APIs.

  • Human-Readability: Unlike JSON in REST APIs, Protobuf’s binary format is not human-readable, making it harder to debug and troubleshoot manually.

  • Not Suitable for All Use Cases: While gRPC excels in certain scenarios, it might not be the best choice for simpler applications or scenarios where human readability of the payload is crucial.

Golang example using gRPC

1st define the Service and Message type:

// user.proto
syntax = "proto3";

option go_package = "user";
message User {
  int64 id = 1;
  string name = 2;
}

Now create another file:

// user_service.proto
syntax = "proto3";

import "user.proto";
option go_package = "user";

service UserService {
  rpc GetUserById (UserRequest) returns (User);
}

message UserRequest {
  int64 user_id = 1;
}

Install the tool to build the code:

go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2
go get google.golang.org/grpc

To generate the code from the protobuf:

protoc --go_out=. user.proto user_service.proto

Implementing the service in Golang:

// user_service.go
package main

import (
	"context"
	"fmt"
	"net"

	"google.golang.org/grpc"

	pb "github.com/diogo-esteves/go_grpc/pb"
)

type userServiceServer struct{
    pb.UnimplementedUserServiceServer
}

func (s *userServiceServer) GetUserById(ctx context.Context, req *user.UserRequest) (*user.User, error) {
	// Logic to fetch user details from the database
	user := &user.User{
		Id:   req.UserId,
		Name: "Diogo Esteves", // Assuming a static name for simplicity
	}
	return user, nil
}

func main() {
	listen, err := net.Listen("tcp", ":5001")
	if err != nil {
		fmt.Printf("Error: %v", err)
		return
	}

	server := grpc.NewServer()
	user.RegisterUserServiceServer(server, &userServiceServer{})

	fmt.Println("gRPC server is running on port 5001")
	if err := server.Serve(listen); err != nil {
		fmt.Printf("Error: %v", err)
	}
}

Now implementing the client:

// client.go
package main

import (
	"context"
	"fmt"
	"log"

	"google.golang.org/grpc"
	pb "github.com/diogo-esteves/go_grpc/pb"
)

func main() {
	connection, err := grpc.Dial("localhost:5001", grpc.WithInsecure())
	if err != nil {
		log.Fatalf("Error: %v", err)
	}
	defer connection.Close()

	client := user.NewUserServiceClient(connection)

	userDetails, err := client.GetUserById(context.Background(), &user.UserRequest{UserId: 1})
	if err != nil {
		log.Fatalf("Error: %v", err)
	}

	fmt.Printf("User Details: %+v\n", userDetails)
}

Conclusion

gRPC is a powerful and efficient RPC framework that provides efficient communication, language-agnostic, automatic code generation, and bidirectional streaming.

References and code

0%