Dando continuidade ao post o que é e como utilizar protobufs, nesse post, vamos colocar o conhecimento teórico em prática e fazer uma API utilizando protobuf e gRPC. Embora seja uma API simples, ela te dará uma ótima base para construir aplicações mais complexas.
Dependências
Mas antes de meter a mão na massa, precisamos instalar o protoc
e os pluginsprotoc-gen-go
e protoc-gen-go-grpc
. Para instalar o protoc
, independente do OS que você esteja utilizando, recomendo fazer download do binário direto da página de releases do projeto. Após o download, descompacte e mova o programa protoc
, que está dentro da pasta bin, em alguma pasta que esteja referenciada ao seu $PATH, por exemplo, a pasta go/bin
ou ~/.local/bin
. Com o protoc
instalado, agora precisamos instalar os dois plugins para gerar código Go. Partindo do princípio que você tem o Go instalado na sua máquina, execute os dois comandos abaixo para instalar os plugins.
$ go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28 $ go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2
Protobuf
Agora que estamos com todos os programas instalados, vamos começar nossa API criando uma pasta com o nome de proto, e o arquivo category.proto dentro dela.
syntax = "proto3"; package categories; option go_package = "v1/proto"; message Category { int32 id = 1; string name = 2; string description = 3; bool is_active = 4; } message CategoryResponse { Category category = 1; } message CategoryFilterRequest { string field = 1; string value = 2; } message CategoryListResponse { repeated Category categories = 1; } service CategoryService { rpc Save(Category) returns (CategoryResponse); rpc Find(CategoryFilterRequest) returns (CategoryListResponse); }
Se você não tem familiaridade com arquivos .proto, recomendo a leitura do post anterior que fiz sobre protobuf. Com o arquivo .proto definido, vamos executar o protoc
com os plugins para gerar os arquivos com código Go.
protoc --go_out=. --go_opt=paths=source_relative \\ --go-grpc_out=. --go-grpc_opt=paths=source_relative \\ proto/category.proto
Esse comando irá gerar os arquivos proto/category.pb.go
e proto/category_grpc.pb.go
. Neles, vamos encontrar código para popular, serializar e devolver os tipos de message que definimos no arquivo .proto, além de código para cliente e servidor. De qualquer forma, você não precisa se preocupar com o conteúdo desses arquivos, já que eles não devem ser editados manualmente.
gRPC
Agora, precisamos implementar a interface CategoryService, que está definida no arquivo proto/category_grpc.pb.go
. Para isso vamos criar uma nova pasta na raiz do projeto com o nome de category. Dentro dela, um arquivo category.go com a seguinte struct.
import ( "database/sql" pb "github.com/aprendagolang/classificados/proto" ) type categoryServiceServer struct { pb.UnimplementedCategoryServiceServer db *sql.DB }
Como vamos utilizar o conceito de Dependency Injection em nosso service, precisamos implementar uma função para receber a conexão com o banco de dados, e retornar um ponteiro de categoryServiceServer.
func NewService(db *sql.DB) *categoryServiceServer { return &categoryServiceServer{db: db} }
Feito isso, precisamos implementar os métodos Save
e Find
que definimos no CategoryService do arquivo category.proto. Levando em consideração que, o mais importante aqui é a compreensão do funcionamento de um serviço gRPC e não a implementação em si dos métodos, farei uma implementação “dummy” deles.
func (svc *categoryServiceServer) Save(ctx context.Context, c *pb.Category) (*pb.CategoryResponse, error) { return &pb.CategoryResponse{ Category: c, }, nil } func (svc *categoryServiceServer) Find(ctx context.Context, c *pb.CategoryFilterRequest) (*pb.CategoryListResponse, error) { categories := []*pb.Category{ {Id: 1, Name: "Carros", Description: "", IsActive: true}, {Id: 2, Name: "Barcos", Description: "", IsActive: true}, {Id: 3, Name: "Motos", Description: "", IsActive: false}, } return &pb.CategoryListResponse{Categories: categories}, nil }
Note que, ambos os métodos, além dos parâmetros e retornos que definimos, eles recebem um context.Context
e retornam um error
. Por fim, na raiz do projeto, criamos um arquivo main.go
para implementar o servidor.
package main import ( "database/sql" "log" "net" "github.com/aprendagolang/classificados/category" pb "github.com/aprendagolang/classificados/proto" _ "github.com/mattn/go-sqlite3" "google.golang.org/grpc" ) func main() { db, err := sql.Open("sqlite3", "./classificados.db") if err != nil { log.Fatal(err) } defer db.Close() // Fica ouvindo conexões na porta 9000 l, err := net.Listen("tcp", "localhost:9000") if err != nil { log.Fatalf("failed to listen: %v", err) } // inicia servidor gRPC var opts []grpc.ServerOption server := grpc.NewServer(opts...) // registra o service pb.RegisterCategoryServiceServer(server, category.NewService(db)) // serve as conexões server.Serve(l) }
Ignore a parte do banco de dados. Repare que os outros passos, embora em uma ordem diferente, são muito parecidos com um servidor HTTP comum. Ouvimos conexões em uma determinada porta, registramos os serviços (assim como registramos rotas), e por fim, servimos as conexões.
Conclusão
Implementar um serviço gRPC não é nada complexo. Afinal, esse framework é só uma “casca” ao redor da lógica da aplicação, assim como Fiber, Echo e outros frameworks web são. Para finalizar essa série, no próximo post, vamos implementar o lado cliente da comunicação gRPC. Até lá!
[…] você ainda não leu o post anterior, onde implementamos a API em gRPC, peço que leia, pois os passos para definição do arquivo proto e geração do código utilizando […]