Writing a Simple gRPC Application in Golang From Scratch
simple micro-service implementation
gRPC is a modern open-source, high-performance Remote Procedure Call (RPC) framework that can run in any environment. It is best suitable for internal communication between microservices.
Let’s build a simple gRPC application from scratch to understand gRPC better.
Building a Microservice in Golang
Photo by ASTERISK KWON on Unsplash
Let’s use an example of a book service to demonstrate how to use the gRPC framework. The complete code is on GitHub
This service will have five methods:
CreateBook
RetrieveBook
UpdateBook
DeleteBook
ListBook
Define A Protobuf File
gRPC uses the Protobuf .proto
file format to define the messages, services,and some aspects of the code generation.
Messages are underlying interchange format.
In our example, we define a message type Book
that contains a field name, author, price, etc.
syntax = "proto3";package api.v1;import "google/protobuf/timestamp.proto";
option go_package = "github.com/jerryan999/book-service/api/v1";message Book {
int64 bid = 1;
string title = 2;
string author = 3;
string description = 4;
string language = 8;
google.protobuf.Timestamp finish_time = 9;
}...
There are a couple of things to note in this short example.
We're using the latest version of the protobuf syntax
proto3
Protobufs has its’ own timestamp type, so we need to use it properly.
If we want to use message types with an RPC (Remote Procedure Call) system, we must define an gRPC service interface in a .proto
file.
Service defines rpc methods for remote calling
service BookService { rpc CreateBook(CreateBookRequest) returns (CreateBookResponse) {}; rpc RetrieveBook(RetrieveBookRequest)returns(RetrieveBookResp) {};
rpc UpdateBook(UpdateBookRequest) returns (UpdateBookResponse) {};
rpc DeleteBook(DeleteBookRequest) returns (DeleteBookResponse) {};
rpc ListBook(ListBookRequest) returns (ListBookResponse) {};
}message CreateBookRequest {
Book book = 1;
}message CreateBookResponse {
int64 bid = 1;
}message RetrieveBookRequest {
int64 bid = 1;
}message RetrieveBookResponse {
Book book = 1;
}
...
The Compiler Generates Codes
Let’s execute the following command to generate Golang code.
protoc ./api/v1/*.proto \
--go_out=. \
--go_opt=paths=source_relative \
--go-grpc_out=. \
--go-grpc_opt=paths=source_relative \
--proto_path=.
The above command will generate two files:
One for serializing the messages (book.pb.go) using protobuf.
The other file (book_grpc.pb.go) consists of some code for the gRPC client and server code.
Business Code
In this part, we define a Book
struct and aBookRepository
interface. They are used to describe the business logic of the book service.
package internalimport (
"context"
"time"
)type BookId int64type Book struct {
Bid BookId `json:"bid"`
Title string `json:"title"`
Author string `json:"author"`
Description string `json:"description"`
Language string `json:"language"`
FinishTime time.Time `json:"finishTime"`
}type BookRepository interface {
CreateBook(ctx context.Context, book *Book) (BookId, error)
RetrieveBook(ctx context.Context, bid BookId) (*Book, error)
UpdateBook(ctx context.Context, book *Book) error
DeleteBook(ctx context.Context, bid BookId) error
ListBook(ctx context.Context, offset int64, limit int64) ([]*Book, error)
}
Different BookRepository interface implementations can be used in different persistence layers (e.g., MongoDB, Postgres, etc.). This strategy is called Dependency Injection (DI).
Mongo Persistence layers
In this part, we will create a new type, MongoBookRepository
Which implements the BookRepository
interface.
...const (
bookCollection = "books"
)type MongoBookRepository struct {
counter BookId // increment book id
mu sync.Mutex
collection *mongo.Collection
}func NewMongoBookRepository(db *mongo.Database) *MongoBookRepository {
return &MongoBookRepository{
collection: db.Collection(bookCollection),
}
}func (r *MongoBookRepository) CreateBook(ctx context.Context, book *Book) (BookId, error) {...}
func (r *MongoBookRepository) RetrieveBook(ctx context.Context, bid BookId) (*Book, error) {...}
This new struct has three fields:
A
collection
field, which is a reference to a MongoDB collection.A
counter
field, which is used to generate unique IDs for the books.A
mu
field, Which is a mutex. It ensures that only one operation is performed when incrementing thecounter
variable.
gRPC Server Implementation
The gRPC server implements the BookServiceServer
interface from book_grpc.pb.go. We can use it to start a gRPC server in the next part.
...type grpcServer struct {
BookRepository BookRepository
api.UnimplementedBookServiceServer
}func NewRPCServer(repository BookRepository) *grpc.Server {
srv := grpcServer{
BookRepository: repository,
}
gsrv := grpc.NewServer()
api.RegisterBookServiceServer(gsrv, &srv)
return gsrv}func (s *grpcServer) CreateBook(ctx context.Context, req *api.CreateBookRequest) (*api.CreateBookResponse, error) {
book := &Book{
Bid: 0,
Title: req.Book.GetTitle(),
Author: req.Book.GetAuthor(),
Description: req.Book.GetDescription(),
Language: req.Book.GetLanguage(),
FinishTime: req.Book.GetFinishTime().AsTime(),
}
bid, error := s.BookRepository.CreateBook(ctx, book)
if error != nil {
return nil, status.Errorf(codes.InvalidArgument, error.Error())
}
return &api.CreateBookResponse{Bid: int64(bid)}, nil
}func (s *grpcServer) RetrieveBook(ctx context.Context, req *api.RetrieveBookRequest) (*api.RetrieveBookResponse, error) {...}...
Starting the Server
Starting a gRPC server is very simple. here is a code fragment from internal/server.go
...
port := ":8080"
listen, err := net.Listen("tcp", port)...
// mongo db
client, _ := mongo.Connect(ctx, options.Client().ApplyURI("mongodb://localhost:27017"))
db := client.Database("testing")// mongo repository
var repository internal.BookRepository = NewMongoBookRepository(db)// gRPC server start
srv := NewRPCServer(repository)
if err := srv.Serve(listen); err != nil {
log.Fatalf("Failed to serve: %v", err)
}
Call from client
we can find the gRPC Client code in cmd/client/main.go. Please refer to that file for more details.
Wrapping Up
I hope this example of implementing the gRPC application with Go helped you understand the gRPC better.