Client Interceptors in Go Microservices

The latest release of Protocol Buffers can be found on the release page https://github.com/protocolbuffers/protobuf/releases/tag/v3.9.1

Make sure Git is installed on your machine and in your system’s PATH. Install the package to your $GOPATH with the go tool from shell:

$ go get google.golang.org/grpc




Create new folder named src. In src folder, create new folder named productservice. In productservice folder, create new folder named proto. In proto folder, create new file named product.proto as below:

syntax = "proto3";

package productservice;

service ProductService {

    rpc FindAll (FindAllRequest) returns (FindAllResponse);

    rpc Search(SearchRequest) returns (SearchResponse);

}

message SearchRequest {
    string keyword = 1;
}

message SearchResponse {
    repeated Product Products = 1;
}

message FindAllRequest {
}

message FindAllResponse {
    repeated Product Products = 1;
}

message Product {
    string id = 1;
    string name = 2;
    int32 quantity = 3;
    double price = 4;
    bool status = 5;
}

Select productservice folder and open it in Terminal window of Visual Studio Code. Use command line as below to generate product.pb.go file:

protoc -I ./ proto/product.proto --go_out=plugins=grpc:.




In productservice folder, create new folder named handlers. In this folder, create new file named handler.go as below:

package handlers

import (
	"context"
	productservice "productservice/proto"
	"strings"
)

type ProductServiceServer struct {
}

var products []*productservice.Product

func init() {
	products = []*productservice.Product{
		&productservice.Product{
			Id:       "p01",
			Name:     "tivi 1",
			Price:    4.5,
			Quantity: 4,
			Status:   true,
		},
		&productservice.Product{
			Id:       "p02",
			Name:     "tivi 2",
			Price:    22,
			Quantity: 3,
			Status:   false,
		},
		&productservice.Product{
			Id:       "p03",
			Name:     "laptop 3",
			Price:    27,
			Quantity: 3,
			Status:   true,
		},
	}
}

func (*ProductServiceServer) FindAll(ctx context.Context, in *productservice.FindAllRequest) (*productservice.FindAllResponse, error) {
	return &productservice.FindAllResponse{Products: products}, nil
}

func (*ProductServiceServer) Search(ctx context.Context, in *productservice.SearchRequest) (*productservice.SearchResponse, error) {
	var result []*productservice.Product
	for _, product := range products {
		if strings.Contains(product.Name, in.Keyword) {
			result = append(result, product)
		}
	}
	return &productservice.SearchResponse{Products: result}, nil
}




In productservice folder, create new folder named server. In this folder, create new file named main.go as below:

package main

import (
	"fmt"
	"net"
	"productservice/handlers"
	productservice "productservice/proto"

	"google.golang.org/grpc"
)

func main() {

	lis, err := net.Listen("tcp", ":1111")
	if err != nil {
		fmt.Println(err)
	}
	defer lis.Close()

	s := handlers.ProductServiceServer{}
	server := grpc.NewServer()

	productservice.RegisterProductServiceServer(server, &s)

	if err := server.Serve(lis); err != nil {
		fmt.Println(err)
	}

}

Select main.go file in server folder and open it in Terminal window of Visual Studio Code. Use command line as below to start server:

go run main.go




In src folder, create new folder named client. In src folder, create new folder named interceptors. In interceptors folder, create new client interceptors as below:

In interceptors folder, create new go file named datelogclientinterceptor.go as below:

package interceptors

import (
	"context"
	"fmt"
	"time"

	"google.golang.org/grpc"
)

func DateLogClientInterceptor(ctx context.Context, method string, req interface{}, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
	today := time.Now()
	fmt.Println("client call services date: " + today.Format("01/02/2006 15:04:05"))
	err := invoker(ctx, method, req, reply, cc, opts...)
	return err
}

In interceptors folder, create new go file named methodlogclientinterceptor.go as below:

package interceptors

import (
	"context"
	"fmt"

	"google.golang.org/grpc"
)

func MethodLogClientInterceptor(ctx context.Context, method string, req interface{}, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
	fmt.Println("client call " + method + " service")
	err := invoker(ctx, method, req, reply, cc, opts...)
	return err
}




In client folder, create new file named main.go as below:

package main

import (
	interceptors "client/interceptors"
	"context"
	"fmt"
	productservice "productservice/proto"

	grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"

	"google.golang.org/grpc"
)

func main() {

	conn, err := grpc.Dial(
		":1111",
		grpc.WithInsecure(),
		grpc.WithUnaryInterceptor(
			grpc_middleware.ChainUnaryClient(
				interceptors.DateLogClientInterceptor,
				interceptors.MethodLogClientInterceptor,
			),
		),
	)
	if err != nil {
		fmt.Println(err)
	}
	defer conn.Close()

	productServ := productservice.NewProductServiceClient(conn)

	response1, err1 := productServ.FindAll(context.Background(), &productservice.FindAllRequest{})
	if err1 != nil {
		fmt.Println(err1)
	} else {
		products := response1.Products
		fmt.Println("Product List")
		for _, product := range products {
			fmt.Println("id: ", product.Id)
			fmt.Println("name: ", product.Name)
			fmt.Println("price: ", product.Price)
			fmt.Println("quantity: ", product.Quantity)
			fmt.Println("status: ", product.Status)
			fmt.Println("========================")
		}
	}

	response2, err2 := productServ.Search(context.Background(), &productservice.SearchRequest{Keyword: "vi"})
	if err2 != nil {
		fmt.Println(err2)
	} else {
		products := response2.Products
		fmt.Println("Search Products")
		for _, product := range products {
			fmt.Println("id: ", product.Id)
			fmt.Println("name: ", product.Name)
			fmt.Println("price: ", product.Price)
			fmt.Println("quantity: ", product.Quantity)
			fmt.Println("status: ", product.Status)
			fmt.Println("========================")
		}
	}

}

Select main.go file in client folder and open it in Terminal window of Visual Studio Code. Use command line as below to start client:

go run main.go




client call services date: 08/19/2019 16:26:32
client call /productservice.ProductService/FindAll service
Product List
id:  p01
name:  tivi 1
price:  4.5
quantity:  4
status:  true
========================
id:  p02
name:  tivi 2
price:  22
quantity:  3
status:  false
========================
id:  p03
name:  laptop 3
price:  27
quantity:  3
status:  true
========================

client call services date: 08/19/2019 16:26:32
client call /productservice.ProductService/Search service
Search Products
id:  p01
name:  tivi 1
price:  4.5
quantity:  4
status:  true
========================
id:  p02
name:  tivi 2
price:  22
quantity:  3
status:  false
========================