Implementando uma API com protobuf e gRPC

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á!


Se inscreva na nossa newsletter

* indicates required

Um comentário sobre “Implementando uma API com protobuf e gRPC

Deixe uma resposta