API with gRPC and Golang

https://medium.com/@arkadybalaba/api-with-grpc-and-golang-d6aba44345a0

Arkady Balaba

What is gRPC?

gRPC is now popular RPC framework first introduced by Google in 2015 as an attempt to improve Stubby — general-purpose RPC infrastructure developed and used by Google for more than a decade. RPC here stands for Remote Procedure Call and is a type of communication protocol between clients and servers. RPC operates on top of the transport layer and abstracts the way system entities communicate with each other. Thanks to RPC your distributed application pieces do not need to know of any network details to “talk” to each other.

Why gRPC?

With all the benefits of open source projects, there are several other reasons you would want to look on gRPC as your next RPC framework.

  • Language agnostic
    gRPC uses Protocol Buffers (or Protobuf) as their mechanism to define service interfaces and serialize structured data. This is fully language and platform-neutral.
  • Fast
    Thanks to HTTP/2 that gRPC uses as its transport protocol you get:
    • headers compression — smaller request payload
    • binary serialization — better compression
    • single TCP connection — you only need one connection between client and server to handle multiple requests
  • Forms strong contracts between the client and the server
    With generated stubs, you get to use strong contracts for free. Application entities here communicate with schema defined data structures (protos), which guarantees content and data types on both ends.

Protobuf Schema

Protocol buffers schema (proto3) consists of two key types — messages and services. Services define an interface and when implemented with the desired language, service methods are used to send and receive data between the client and the server. Messages on the other side define serializable data structures used by the service methods to communicate.

In this article we going to define and implement a simple service, that takes a timestamp as an input to schedule a reminder. Let’s call it reminder.proto.

  1. syntax = "proto3";
  2. package demo.reminder.v1;
  3. import "google/protobuf/timestamp.proto";
  4. option go_package = "reminder";
  5. service ReminderService {
  6. rpc ScheduleReminder(ScheduleReminderRequest) returns (ScheduleReminderResponse) {}
  7. }message ScheduleReminderRequest { google.protobuf.Timestamp when = 1;
  8. }message ScheduleReminderResponse {
  9. string id = 1;
  10. }

The code fragment above defines several things — a service (an interface) and two messages. It also includes a syntax statement — protobuf language version used in the snippet (proto2 or proto3), package name and an import statement to include timestamp type used in the ScheduleReminderRequest message.

Reminder service definition declares one RPC method — ScheduleReminder, that takes only one argument — timestamp. ‘When’ value here specify the desired date/time of when a user should get reminder notification. In response, the method would send new scheduled reminder id.

Tooling

Protocol buffers come with a C++ built compiler — a tool called ‘protoc’. This one is used to generate code stubs for defined schemas.

Mac users can utilize ‘brew’ to setup protocol buffers

  1. brew install protobuf

For other platforms, please refer to the installation instructions section at protobuf repo.

To generate code stubs, you will need Go language protobuf runtime installed. This can be done with ‘go get’ command

  1. go get -u github.com/golang/protobuf/protoc-gen-go

Instructions for other languages can be found here.

Server/Client Generation

When all set, we can continue to code generation. In fact, it’s very simple and all you need is to run this command:

  1. protoc --go_out=plugins=grpc:. reminder.proto

There are few arguments here:
1. go_out states protoc should be generating Go stubs using protoc-gen-go plugin
2. our protobuf schema defines RPC service and therefore we should set protoc-gen-go to use grpc plugin, to generate gRPC compatible code

Once run, you would expect reminder.pb.go file to be generated. It contains client and server code stubs, types and helper functions.

Service Implementation

Since protoc has handled most of the work for us. All we need to do is to implement the generated server interface.

  1. // ReminderServiceServer is the server API for the Reminder service.
  2. type ReminderServiceServer interface {
  3. ScheduleReminder(context.Context, *ScheduleReminderRequest) (*Reminder, error)
  4. }

Server and ScheduleReminder function would look like:

  1. type MyServer struct {
  2. }
  3. func (s *MyServer) ScheduleReminder(ctx context.Context, req *reminder.ScheduleReminderRequest) (*reminder.ScheduleReminderResponse, error) { // validate your input and schedule the reminder return &reminder.ScheduleReminderResponse{
  4. Id: id,
  5. }, nil
  6. }

Next init and start gRPC server:

  1. // server/main.go
  2. func main(){ grpcServer := grpc.NewServer()
  3. reminder.RegisterReminderServiceServer(grpcServer, new(MyServer))
  4. go func() {
  5. lis, err := net.Listen("tcp", "localhost:8080")
  6. if err != nil {
  7. log.Fatalf("failed to listen: %v", err)
  8. }
  9. if err := grpcServer.Serve(lis); err != nil {
  10. log.Fatal("failed to start server", err)
  11. }
  12. }() // let us wait for an input here (ctrl+c) to stop the client
  13. c := make(chan os.Signal)
  14. signal.Notify(c, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
  15. signal := <-c
  16. log.Fatalf("process killed with signal: %v", signal.String())}

MyServer would get started on port 8080 and expose one defined endpoint — ScheduleReminder.

Now for the client to connect to the server you would have to setup a connection and pass it to a newly created client.

  1. // client/main.go
  2. func main() {
  3. ctx, _ := context.WithTimeout(
  4. context.Background(),
  5. 5*time.Second,
  6. )
  7. reminderConn, err := grpc.DialContext(ctx, "localhost:8080",
  8. grpc.WithInsecure(),
  9. )
  10. if err != nil {
  11. log.Fatalln("Failed to dial server: ", err)
  12. } reminderClient := reminders.NewReminderServiceClient(
  13. reminderConn) oneMinute, _ := ptypes.TimestampProto(time.Now().Add(time.Minute))
  14. resp, err := reminderClient.ScheduleReminder(ctx,
  15. &reminders.ScheduleReminderRequest{
  16. When: oneMinute,
  17. }) if err!=nil{
  18. log.Fatalln("Failed to schedule a reminder: ", err)
  19. } log.Infof("Reminder have been successfully scheduled. New reminder id is %s", resp.GetId())
  20. }

All done. We have defined a proto schema that describes the Reminder service with the only one endpoint — ScheduleReminder. Implemented gRPC server and the client, established connection between the two and scheduled one reminder.

You can find the full example in this GitHub repository.

ft_authoradmin  ft_create_time2020-01-24 02:00
 ft_update_time2020-01-24 02:03