Présentation de gRPC

Présentation de gRPC


Avant toute chose je vous invite à lire la première partie si ce n'est pas déjà fait. L'article présente protobuf qui va être utilisé avec gRPC. Aujourd'hui nos projets sont quasiment tous en architecture microservices et communiquent via HTTP.

Et si maintenant on utilisait HTTP/2 pour faire communiquer nos services ?

Parce qu'un client HTTP/1 c'est bien mais un client HTTP/2 c'est mieux.

HTTP/2 c'est quoi ?

Je vous invite à lire cet article de Vincent pour y voir plus clair.

gRPC c'est quoi ?

gRPC a été développé initialement par Google puis rendu open source. Il permet de réaliser des clients et serveurs rpc via HTTP/2 et donc de profiter de ses nouveautés. Les données sont sérialisées et désérialisées grâce à Protocol Buffers. Le framework gRPC permet aussi d'avoir un client et un serveur dans différents langages. En effet il est disponible pour la plupart des langages. Chaque service rpc est déclaré dans un fichier protobuf. La RFC est disponible ici si ça vous intéresse.

gRPC permet quatre modes de communication.

Le one-to-one classique :

Client Request -> Server
Client <- Response Server

service CustomService {
    rpc Endpoint(Request) returns (Response) {}
}

La streaming côté client :

Streaming Request -> Server
Client <- Response Server

service CustomService {
    rpc Endpoint(stream Request) returns (Response) {}
}

Le streaming côté serveur :

Request -> Server
Client <- Streaming Response

service CustomService {
    rpc Endpoint(Request) returns (stream Response) {}
}

Le streaming bidirectionnel :

Streaming Request -> Server
Client -> Streaming Response

service CustomService {
    rpc Endpoint(stream Request) returns (stream Response) {}
}

Nous allons voir pour cet article uniquement le troisième mode streaming côté serveur mais les quatre sont très semblables.

Vous pouvez retrouver l'ensemble du code de l'exemple sur mon github.

Installation

Assurez-vous avant de commencer l'installation d'avoir bien installé Go en version supérieure à 1.5 et Protocol Buffers en version 3.

Récupérez gRPC pour Go :

go get google.golang.org/grpc

Protobuf

Nous allons commencer par récupérer le fichier proto du précédent article.

Nous allons ajouter un service pour récupérer en streaming la liste des Posts.

Un service rpc est composé de la structure suivante :

rpc function(request) returns (response)

Ici on a une request vide et un streaming de Post.

service PostService { rpc ListPosts(google.protobuf.Empty) returns (stream Post) {} }

Ce qui nous donne :

syntax = "proto3"; import "google/protobuf/empty.proto"; package main; message Post { int32 id = 1; string title = 2; string author = 3; } service PostService { rpc ListPosts(google.protobuf.Empty) returns (stream Post) {} }

Serveur

On va commencer par générer le code source depuis le fichier protobuf.

protoc --proto_path=. --go_out=plugins=grpc:. post.proto

On commence par déclarer une pseudo base de données :

var posts = []Post{ { Id: 1, Title: "My awesome article 1", Author: "Quentin Neyrat", }, { Id: 2, Title: "My awesome article 2", Author: "Quentin Neyrat", }, { Id: 3, Title: "My awesome article 3", Author: "Quentin Neyrat", }, }

Puis on crée un serveur TCP sur le port 4000 pour notre serveur gRPC et on attache notre service déclaré dans le protobuf :

lis, _ := net.Listen("tcp", "localhost:4000") g := grpc.NewServer() RegisterPostServiceServer(g, NewServer()) g.Serve(lis) }

On a plus qu'à créer notre endpoint qui va parcourir notre pseudo base de données et envoyer les posts un par un :

func (s *Server) ListPosts(empty *google_protobuf.Empty, stream PostService_ListPostsServer) error { for _, post := range posts { fmt.Printf("Send post #%d \n", post.GetId()) if err := stream.Send(&post); err != nil { return err } } return nil }

Le code final :

package main import ( "fmt" "log" "net" google_protobuf "github.com/golang/protobuf/ptypes/empty" grpc "google.golang.org/grpc" ) type Server struct{} func NewServer() *Server { return &Server{} } var posts = []Post{ { Id: 1, Title: "My awesome article 1", Author: "Quentin Neyrat", }, { Id: 2, Title: "My awesome article 2", Author: "Quentin Neyrat", }, { Id: 3, Title: "My awesome article 3", Author: "Quentin Neyrat", }, } func (s *Server) ListPosts(empty *google_protobuf.Empty, stream PostService_ListPostsServer) error { for _, post := range posts { fmt.Printf("Send post #%d \n", post.GetId()) if err := stream.Send(&post); err != nil { return err } } return nil } func main() { lis, err := net.Listen("tcp", "localhost:4000") if err != nil { log.Fatalf("failed to listen: %v", err) } g := grpc.NewServer() RegisterPostServiceServer(g, NewServer()) g.Serve(lis) }

Astuce : Vous pouvez exécuter go run * pour compiler entièrement le projet.

Client

Dans un nouveau projet, on déclare un client gRPC pour notre service.

conn, _ := grpc.Dial("localhost:4000", grpc.WithInsecure()) client := NewPostServiceClient(conn)

Puis une méthode pour récupérer les Posts :

func printPosts(client PostServiceClient) { stream, err := client.ListPosts(context.Background(), &google_protobuf.Empty{}) if err != nil { log.Fatalf("%v.ListPosts(_) = _, %v", client, err) } for { post, err := stream.Recv() if err == io.EOF { break } if err != nil { log.Fatalf("%v.ListPosts(_) = _, %v", client, err) } fmt.Printf("Id: %d \n", post.GetId()) fmt.Printf("Title: %s \n", post.GetTitle()) fmt.Printf("Author: %s \n", post.GetAuthor()) } }

Le code final donne :

package main import ( "fmt" "io" "log" google_protobuf "github.com/golang/protobuf/ptypes/empty" context "golang.org/x/net/context" grpc "google.golang.org/grpc" ) func printPosts(client PostServiceClient) { stream, err := client.ListPosts(context.Background(), &google_protobuf.Empty{}) if err != nil { log.Fatalf("%v.ListPosts(_) = _, %v", client, err) } for { post, err := stream.Recv() if err == io.EOF { break } if err != nil { log.Fatalf("%v.ListPosts(_) = _, %v", client, err) } fmt.Printf("Id: %d \n", post.GetId()) fmt.Printf("Title: %s \n", post.GetTitle()) fmt.Printf("Author: %s \n", post.GetAuthor()) } } func main() { conn, err := grpc.Dial("localhost:4000", grpc.WithInsecure()) if err != nil { log.Fatalf("Failed to start gRPC connection: %v", err) } defer conn.Close() client := NewPostServiceClient(conn) printPosts(client) }

Conclusion


gRPC permet de profiter de toutes les nouveautés de HTTP/2 et la puissance de Protocol Buffers. Indispensable pour la communication entre micro-services.

Auteur(s)

Quentin Neyrat

Quentin Neyrat

Back-end developer @ Eleven Labs

Voir le profil

Vous souhaitez en savoir plus sur le sujet ?
Organisons un échange !

Notre équipe d'experts répond à toutes vos questions.

Nous contacter

Découvrez nos autres contenus dans le même thème

Monitorer ses containers Docker

Monitorer ses containers Docker

Les containers sont aujourd'hui largement utilisés du développement jusqu'en production. Cependant un `docker stats` en ssh ne permet pas de gérer correctement son environnement de production. Nous allons donc voir comment répondre à ce besoin de monitoring pour des containers en production.

Soigner ses emails transactionnels

Soigner ses emails transactionnels

L'année dernière un client nous a demandé que les e-mails envoyés par l'api soient plus esthétiques. N'ayant aucun intégrateur dans l'équipe, personne n'était motivé pour s'occuper de ce genre de ticket. Nous avons donc tiré au sort pour savoir qui s'en chargerait. Bien sûr, c'est tombé sur moi. Le but de la fonctionnalité était d'avoir des emails beaucoup plus soignés et responsives.

Présentation de Protocol Buffers

Présentation de Protocol Buffers

Une problématique assez récurrente dans nos applications est la manière de rendre notre donnée, qu'elle soit transmise ou stockée. Le format qu'on utilise souvent aujourd'hui est le `JSON`. Cependant certains langages le gèrent très mal tel que Java et Go. Google a donc développé un système pour palier à ce problème : `Protocol Buffers`.