README
¶
gRPC no Golang 🦫
No Golang a configuração é mais burocrática que no JavaScript.
1. asdf
1.1. Protocol Buffer Compile
1.1.1. Instalar o plugin
1.1.2. Instalar a versão
1.2. Golang
1.2.1. Instalar o plugin
1.2.2. Instalar uma versão do Golang
2. Golang
2.1. Plugins Go
2.2. Atualizar PATH
2.3. Inicializar o projeto
2.4. Instalar a dependência grpc-go
2.5. Reshim
3. Criar os arquivos
3.1. Arquivo Protobuf
3.2. Compilar o arquivo proto
3.3. Server
3.4. Client
📌 Para uma fundamentação teórica e explicação do código, recomendamos a leitura da Wiki do projeto. Aqui vamos cuidar apenas dos comandos para fazer o gRPC funcionar Golang.
1. asdf
Podemos encontrar o plugin de qualquer linguagem procurando no google por: asdf plugin nomeDaLinguagem
. Entrar no github do plugin e seguir as instruções. Fizemos isso para o golang
e o compilador protoc
1.1. Protocol Buffer Compiler
Para compilar um arquivo protocol buffer
, nós precisamos instalar o compilador e depois os plugins específicos da linguagem golang. O protocol buffer compiler usaremos o asdf para instalá-lo.
1.1.1. Instalar o plugin
asdf plugin-add protoc https://github.com/paxosglobal/asdf-protoc.git
1.1.2. Instalar a versão
asdf install protoc 3.20.3
1.2. Golang
1.2.1. Instalar o plugin
asdf plugin-add golang https://github.com/kennyp/asdf-golang.git
1.2.2. Instalar uma versão do Golang
Podemos ver todas as versões disponíveis com:
asdf list all golang
Intalamos a versão 1.19:
asdf install golang 1.19
2. Golang
2.1. Plugins Go
Um dos grandes benefícios do gRPC é o fato dele gerar códigos que abstraem e cuidam de toda a comunicação pela rede. Para tanto, cada linguagem tem plugins específicos. No caso do Golang, precisamos instalar dois plugins para gerar códigos para nós a partir dos arquivos .proto.
go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28.1
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2
2.2. Atualizar PATH
Precisamos atualizar o PATH para incluir os pacotes baixados do Golang, para que o protoc
possa encontrar os plugins que acabamos de instalar.
export PATH="$PATH:$(go env GOPATH)/bin"
Este comando modifica temporariamente o PATH, apenas para o shell que estivermos usando. Se quisermos evitar ter de executarmos o comando anterior toda vez que formos compilar arquivos .proto em Go, então devemos acrescentar a linha de comando ao final do arquivo ~/.bashrc
.
Figura 1: GOPATH no ~/.bashrc
Nosso shell é bash, executar source ~/.bashrc
carrregará as novas configurações. Se usa outro shell, feche e abra o terminal novamente.
2.3. Inicializar o projeto
Estando dentro do diretório tcc_grpc/ execute os comandos para criar o diretório do go e entrar nele.
mkdir go_grpc; cd go_grpc;
O comando go mod init
cria um arquivo go.mod
e rastreia as dependências do projeto. Nós chamaremos nosso módulo como o subrepositório do tcc: go mod init github.com/earmarques/tcc_grpc/go_grpc
, mas recomendamos ao leitor usar o seu próprio git, ou um nome qualquer, como:
go mod init meu_modulo_grpc
2.4. Instalar o pacote grpc-go
Por fim, agora que o projeto está sendo monitorado, vamos instalar a dependência grpc-go
.
go get google.golang.org/grpc
2.5 Reshim
A documentação do plugin asdf do golang recomenda fazer um reshim toda vez que fizermos um go get
ou go install
, então, por prudência:
asdf reshim golang
3. Criar os arquivos
Vamos organizar os arquivos em pastas separadas, uma para arquivos .proto, outra para o servidor e outra para o cliente.
3.1. Arquivo Protobuf
Criamos a pasta protos e nela o arquivo gerador_id.proto
:
mkdir protos;
touch protos/gerador_id.proto;
Editamos o arquivo para ter o conteúdo:
// gerador_id.proto
syntax = "proto3";
import "google/protobuf/empty.proto";
option go_package = "github.com/earmarques/tcc_grpc/go_grpc";
package geradorid;
service GeradorID {
rpc GerarId(google.protobuf.Empty) returns (IdReply) {}
}
message IdReply {
int32 goId = 1;
}
Listagem 1: protos/gerador_id.proto
3.2. Compilar o arquivo proto
Estando no diretório protos/
, execute:
protoc --go_out=. --go_opt=paths=source_relative \
--go-grpc_out=. --go-grpc_opt=paths=source_relative \
google/protobuf/empty.proto gerador_id.proto
Chamamos o compilador protoc
que usará os plugins do Go para gerar o código. No arquivo gerador_id.proto
nós importamos a definição de tipo vazio (empty.proto
). Em definições de contrato, se uma chamada de procedimento remota rpc
não recebe nenhum parâmetro como argumento, ou retorna void
, ainda assim, devemos definir esse tipo message
. Como isso é uma message
muito recorrente, é bom que tenhamos uma definição comum ao invés de definí-la em cada arquivo .proto, e termos problemas de conflito de declaração. Sendo assim, nós importamos de google/protobuf/empty.proto
.
Veremos dois arquivos .go
criados na pasta protos, gerador_id_grpc.pb.go
e gerador_id.pb.go
. Também foi criada uma pasta google referente à importação, com outro código gerado pelos plugins, empty.pb.go
. A figura 2 mostra como deve ser a estrutura do módulo go_grpc.
Figura 2: Estrutura do projeto
3.3. Server
Criar a pasta do servidor e o seu código.
mkdir server;
touch server/main.go
// server/main.go
package main
import (
"context"
"log"
"net"
"google.golang.org/grpc"
pb "github.com/earmarques/tcc_grpc/go_grpc/protos"
emptypb "google.golang.org/protobuf/types/known/emptypb"
)
const (
port = ":50051"
)
var id int32 = 0
// Stub
type server struct {
pb.UnimplementedGeradorIDServer
}
// Implementação do método
func (s *server) GerarId(ctx context.Context, in *emptypb.Empty) (*pb.IdReply, error) {
id++
log.Printf("🦫 Id=%d", id)
return &pb.IdReply{GoId: id}, nil
}
func main() {
// Canal gRPC
lis, err := net.Listen("tcp", port)
if err != nil {
log.Fatalf("Falha ao escutar a conexão: %v", err)
}
// Instancia o servidor
s := grpc.NewServer()
pb.RegisterGeradorIDServer(s, &server{})
log.Printf("🦫 Servidor Go ouvindo na porta %s", port)
if err := s.Serve(lis); err != nil {
log.Fatalf("Falha ao prestar o serviço: %v", err)
}
}
Listagem 2: server/main.go
3.4 Client
Vamos criar um código de teste para consumir o serviço GeradorID
e checar se o servidor está respondendo.
Criar a pasta do cliente e o código.
mkdir client;
touch client/main.go
// client/main.go
package main
import (
"context"
"log"
"time"
"google.golang.org/grpc"
pb "github.com/earmarques/tcc_grpc/go_grpc/protos"
emptypb "google.golang.org/protobuf/types/known/emptypb"
)
const (
address = "localhost:50051"
)
func main() {
// Set up a connection to the server.
conn, err := grpc.Dial(address, grpc.WithInsecure(), grpc.WithBlock())
if err != nil {
log.Fatalf("Não foi possível estabelecer conexão com o servidor gRPC: %v", err)
}
defer conn.Close()
c := pb.NewGeradorIDClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
r, err := c.GerarId(ctx, &emptypb.Empty{})
if err != nil {
log.Fatalf("Não foi possível gEerar o id: %v", err)
}
var id = r.GetGoId()
log.Printf("🦫 ID gerado: %d", id)
//log.Printf("Não foi possível gerar o id: %v", r)
}
Listagem 3: client/main.go
4. Executar teste
Vamos precisar de dois terminais, em um deixaremos o servidor ouvindo na porta 50051, no outro executamos as chamadas remotas. O comportamento esperado é dado na figura 3.
Figura 3: Teste de comunicação cliente-servidor Golang
A nossa próxima API irá consumir este microserviço de gerador de id em Golang. É o serviço de banco de dados em 3. Dart.