Introdução ao gRPC - Golang

thenicolau

Gustavo Nicolau

Posted on June 18, 2022

Introdução ao gRPC - Golang

Há algum tempo a arquitetura de API's REST vem dominando o mercado, seja em aplicativos web ou microserviços. Porém existe outras arquiteturas mais modernas e com muito mais vantagens que o REST, hoje nós vamos falar do gRPC

O que é gRPC?

gRPC é um sistema de código aberto criado pela Google em 2015 como uma melhoria para a arquitetura de comunicação chamada RPC ( Remote Procedure Call).

O gRPC usa em seu sistema de mensagens o formato Protobuf (Protocol buffers), que é um forma de definir uma estrutura de dados assim como o JSON e XML só que muito mais rápido e muito mais leve.

Outra grande vantagem de usar o gRPC é que normalmente API's REST são escritas em HTTP/1.1 ao contrário do gRPC que usa HTTP/2

O que é Protobuf?

O Protobuf ou Protocol Buffer como dito anteriormente, é que é um forma de definir uma estrutura de dados assim como o JSON e XML, ele funciona de uma forma um pouco diferente desses dois e tem muitos benefícios sobre eles.

Outra vantagem muito legal do Protobuf é que você pode gerar automaticamente código para várias linguagens, no momento que eu escrevo são elas C++, C#, Dart, Go, Java, Kotlin e Python. Mas para isso precisamos criar um arquivo com a extensão .proto para que isto dê certo,dito isso vamos colocar em prática esses conceitos.

E qual a vantagem de usar HTTP/2?

Além do fato de que o HTTP/2 ser muito mais recente que o HTTP/1.1 as principais vantagens são:

  • Conexão Única: O HTTP/1.1 é um protocolo sequencial, onde o navegador precisa abrir diversas conexões TCP em sequência para os arquivos e caso um arquivo seja muito pesado o processamento acaba sendo lento, já o HTTP/2 ele precisa de apenas uma conexão TCP onde ele fará todas as requisições necessárias para buscar os arquivos, e isso trás um custo de processamento e memória menor junto com uma latência mais baixa também, trazendo benefícios de hardware e software.

Foto comparando HTTP/1.1 e HTTP/2

  • Compactação dos cabeçalhos: Tanto o HTTP/1.1 quanto o HTTP/2 comprimem suas mensagens nas requisições, porém o HTTP/2 usa o HPACK, que é um algoritmo mais avançado que elimina algumas informações repetidas nos cabeçalhos tornando o carregamento de arquivos e pacotes muito mais rápído.

  • Server Push: Outra vantagem do HTTP/2 é que ele pode ser bidirecional ou seja, tanto o servidor quanto o cliente podem trocar informações entre si sem que seja solicitado, diferentemente do HTTP/1.1 que é necessário fazer uma requisição nova sempre que precisar de um novo conteúdo

Antes de tudo vamos usar a linguagem Go para o desenvolvimento do nosso servidor gRPC, não se apegue a linguagens porque isso não vai ser importante por agora.
Agora vamos iniciar nosso projeto com

go mod init grpc-go
Enter fullscreen mode Exit fullscreen mode

Logo depois vamos criar uma pasta chamada 'contracts' que será onde vamos criar nosso um arquivo chamado hello.proto.
Feito isso vamos criar nossa primeira estrutura de dados, que será um request e um response da nossa aplicação

syntax = "proto3";

message HelloRequest {
    string name = 1;
}

message HelloResponse {
    string msg = 1;
}
Enter fullscreen mode Exit fullscreen mode

Aqui nós criamos duas estruturas de dados, o número seguido do tipo e nome da variável é a ordem que ele vai ficar na estrutura.
Feito isso vamos adicionar também mais duas coisas nesse arquivo


option go_package = "./pb";

service HelloService {
    rpc Hello(HelloRequest) returns (HelloResponse) {};
}
Enter fullscreen mode Exit fullscreen mode

a primeira linha que nós adicionamos é para o gerador de código do protobuf sabe onde ele deve criar os arquivos, que no caso vai ser na pasta pb, já o service HelloService que nós criamos é os serviços que vamos usar no nosso servidor, dentro dele criamos a função Hello e nela recebemos a estrutura de dados de request e vamos retornar a estrutura de response.

Antes de prosseguirmos, vamos instalar o compilador do Protobuf que irá gerar nosso código.

  • Caso esteja usando Linux, você pode usar o apt ou o apt-get, por exemplo
apt install -y protobuf-compiler
protoc --version
Enter fullscreen mode Exit fullscreen mode
  • Caso esteja no Mac, você pode usar o Homebrew
brew install protobuf
protoc --version
Enter fullscreen mode Exit fullscreen mode
  • Caso esteja no Windows ou outro SO, você pode consultar o site oficial para fazer a instalação

Feito isso vamos rodar o comando

protoc --go_out=plugins=grpc:./ contracts/*.proto
Enter fullscreen mode Exit fullscreen mode

Isso vai criar a pasta pb e gerar um novo arquivo .go chamado hello.pb.go e nesse arquivo vamos ter uma série de métodos criados pelo gerador de código automático do protobuf. Vamos também instalar a dependência do gRPC do go, para isso damos esse comando

go get -u google.golang.org/grpc
Enter fullscreen mode Exit fullscreen mode

Agora depois de finalizado a instalação, vamos criar nosso servidor. Primeiro vamos criar uma pasta chamada server e dentro dela um arquivo chamado main.go. Dentro desse arquivo vamos começar com o seguinte

package main

import (
    "context"
    "grpc-go/pb"
    "log"
    "net"

    "google.golang.org/grpc"
)

type Server struct {}

func (s *Server) Hello(ctx context.Context, request *pb.HelloRequest) (*pb.HelloResponse, error) {

}


func main(){

}
Enter fullscreen mode Exit fullscreen mode

Nós criamos uma struct chamada Server e depois criamos um método para essa struct chamado Hello que vai ser nossa primeira função do servidor, e essa função recebe dois parâmetros, o primeiro sendo um context que não nos interessa por agora, e o segundo sendo o HelloRequest que nós tinhamos criado no arquivo hello.proto. Isso tudo é possível graças ao gerador de código do protobuf que criou o módulo pb que contém todas essas informações.
Após isso vamos criar nosso retorno da função fazendo o seguinte

func (s *Server) Hello(ctx context.Context, request *pb.HelloRequest) (*pb.HelloResponse, error) {
    return &pb.HelloResponse{Msg: "Hello " + request.GetName()}, nil
}
Enter fullscreen mode Exit fullscreen mode

Como podem ver nós estamos retornando a estrutura de dados de response, porém temos um request.GetName(), uma função que nós não definimos antes, e isso acontece por que o protobuf ao gerar o código para gente, faz os getters e setters automaticamente, isso ajuda muito no desenvolvimento.
Agora vamos criar iniciar nosso servidor na função main do nosso arquivo

func main(){
    listen, err := net.Listen("tcp", "0.0.0.0:9000")

    if err != nil {
        log.Fatalf("Failed to listen: %v", err)
    }

    grpcServer := grpc.NewServer()
    pb.RegisterHelloServiceServer(grpcServer, &Server{})

    if err := grpcServer.Serve(listen); err != nil {
        log.Fatalf("Failed to serve: %v", err)
    }
}
Enter fullscreen mode Exit fullscreen mode

Na nossa primeira linha da função nós iniciamos nosso servidor na porta 9000 e logo após nós validamos para ver se não deu nada de errado. Após isso iniciamos nosso servidor gRPC e depois nós registramos nosso serviço que foi criado no arquivo hello.proto, nós passamos como parâmetro o servidor gRPC que foi iniciado e depois nossa struct do servidor.
Logo após isso iniciamos o nosso servidor gRPC e verificamos se não vai dar nenhum erro.
Agora vamos rodar nosso servidor com um

go run server/main.go
Enter fullscreen mode Exit fullscreen mode

Bom a parte de servidor está pronta, porém nós precisamos testar para ver tudo funcionando, para isso vamos criar uma nova pasta chamada de client, e dentro dela vamos criar um arquivo main.go. Dentro desse arquivo vamos colocar isso

package main

import (
    "context"
    "grpc-go/pb"
    "log"

    "google.golang.org/grpc"
)

func main(){
    connection, err := grpc.Dial("localhost:9000", grpc.WithInsecure())

    if err != nil {
        log.Fatalf("Failed to connect: %v", err)
    }

    defer connection.Close()

    client := pb.NewHelloServiceClient(connection)

    request := &pb.HelloRequest{Name: "Seu nome"}

    response, err := client.Hello(context.Background(), request)

    if err != nil { 
        log.Fatalf("Failed to call: %v", err)
    }

    log.Printf("Response: %v", response.Msg)
}
Enter fullscreen mode Exit fullscreen mode

Nós chamamos a função main e nela começamos criando uma conexão com o gRPC e nessa função de conexão grpc.Dial nós passamos dois parâmetros, o endereço do servidor e também uma função que nada mais é que o tipo de conexão que estamos fazendo, como nós estamos localhost, vamos usar o WithInsecure.
Depois nós verificamos se não existe nenhum erro, logo após isso nós damos um defer connection.Close(), que basicamente vai fechar a nossa conexão antes da função ser encerrada.
Criamos também nossa variável de cliente, onde ele conecta diretamente com o módulo gerado pelo protobuf, e também montamos nosso request puxando também o módulo gerado automaticamente.
Depois chamamos nossa função Hello que está atrelada a nossa variável client e nela nós passamos nossos dois parâmetros necessários, o context.Background() que não nos interessa e também o nosso request que nós criamos acima.
Após isso nós verificamos se não deu nenhum erro durante a chamada da nossa função e então nós mostramos o resultado que o nosso servidor nos enviou. Vamos testar isso dando um

go run client/main.go
Enter fullscreen mode Exit fullscreen mode

Caso o console exiba a mensagem Response: Hello Seu nome foi porque nosso servidor e nosso cliente funcionou perfeitamente.

Conclusão

Mesmo que ainda hoje boa parte da WEB se concentra em arquiteturas REST, usar gRPC para microserviços ou até mesmo aplicações maiores pode ser uma vantagem para você e para seu time, as vantagens que o gRPC trás valem muito a pena para serem usadas no dia a dia principalmente quando o foco é reduzir latências.

💖 💪 🙅 🚩
thenicolau
Gustavo Nicolau

Posted on June 18, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related