Autor: Rodrigo G. Tavares
Veja a Parte 1: O que é gRPC - Seus componentes RPC e HTTP2
Protocol Buffers ou para os íntimos Protobuf, é uma linguagem neutra criada para permitir a integração entre linguagens de programação, também usado como IDL pelo sistema gRPC.
Veremos nesse artigo o que são os Protocol Buffers, como eles funcionam e como eles se integram com o sistema gRPC.
Introdução
Quando falamos de integração entre linguagens, estamos trazendo o conceito de interoperabilidade, ou seja, a capacidade das aplicações e sistemas se comunicarem de maneira simples e fácil.
O que são os Protocol Buffers?
É uma linguagem neutra de plataforma neutra, usada para definição de tipos de dados e funções, muito parecido com o formato JSON, porém menor e mais rápido.
Como vimos no artigo anterior, ele é usado como IDL, linguagem para definição de interfaces, no sistema gRPC.
Por que eu preciso de uma linguagem neutra?
Uma linguagem neutra é uma linguagem simples, usada pra fazer a definição das interfaces e tipos usada nas integrações entre sistemas.
A partir dessa interface, cada linguagem que queira fazer uma integração deve interpretar o código escrito na IDL, gerando funções e tipos nos padrões da sua linguagem.
Essas interfaces geradas são chamadas de stubs e são usadas tanto pelo provedor de serviços, quanto pelo consumidor.
O lado provedor usará as interfaces para efetivamente implementar as regras daquele serviço, enquanto o consumidor, como o próprio nome diz, vai usá-las pra consumir, ou seja, acessar os serviços.
E é exatamente esse o papel dos protocol buffers.
Estrutura dos Protocol Buffers
Precisamos de atenção nos padrões e convenções de formatação do arquivo proto.
Mas por que isso é importante?
Usar o formato correto faz com que o processo de geração dos stubs siga as convenções de cada linguagem.
Em resumo, seguindo o padrão dos protocol buffers, fará com que o código gerado também esteja no padrão da linguagem de destino.
Configurações
Ao criar o arquivo protocol buffer, por convenção o nome do arquivo deve ser todo minúsculo com a extensão .proto.
Exemplo:
- pessoa.proto
- pessoafisica.proto
- pessoajuridica.proto
Sintaxe
Uma vez criado o arquivo, devemos atribuir a versão da sintaxe, que pode ser: proto2 ou proto3.
syntax = "proto3";
Desse ponto em diante focaremos na versão de sintaxe proto3
, que é a versão mais recente, para mais detalhes referente a versão proto2
, há um link para a documentação nas referências do artigo.
Pacote
Agora precisamos definir o pacote, essa é uma instrução opcional usada pra compor o nome da mensagem, essa configuração deixa o nome único e evitando conflitos com outros tipos de nome semelhante, ou seja, podemos ter tipos de nomes iguais, desde de que estes estejam em pacotes diferentes.
package expertostech;
A instrução package em Java, go e csharp é usada pra compor o código gerado, no caso do csharp o valor é atribuído ao namespace
, já no Go e no Java é atribuído em uma propriedade de mesmo nome.
Importação
Depois temos a área de importação, onde você pode fazer referência pra outros tipos, como é o caso da definição de data hora que precisa ser importado. Você também pode importar seus próprios tipos como referência.
import "google/protobuf/timestamp.proto";
import "endereco.proto";
Opções adicionais
Vamos agora para as configurações específicas de cada linguagem.
Para o Java, as configurações são:
-
java_multiple_files
, se verdadeiro, indica que as classes serão geradas em arquivos separados. -
java_outer_classname
, o nome da classe para geração.
option java_multiple_files = true;
option java_outer_classname = "PessoaProtos";
Se você quiser organizar seus arquivos proto em pacotes diferentes das suas classes, você pode usar as seguintes configurações:
-
java_package
, para o Java; -
go_package
, para o Go; -
csharp_namespace
, para o C#.
Essas três configurações sobrescrevem o valor do pacote, package ou namespace no c#, alterando os valores para geração das classes.
option java_package = "expertostech.tutorial.grpc";
option go_package = "expertostech/tutorial/grpc";
option csharp_namespace = "ExpertosTech.Tutorial.Grpc";
Definição de tipos
Uma curiosidade, é que os tipos nos protocol buffers são chamados de message
, justamente por que esses tipos são usados como "mensagem" de envio e respostas nas suas integrações entre sistemas.
Mensagem
Iniciamos a declaração cada tipo com a palavra chave message
, seguido do nome no padrão CamelCase.
message Pessoa {
}
É importante saber que podemos declarar várias mensagens em um mesmo arquivo proto.
message Pessoa {
}
message Usuario {
}
Atributos
Agora vamos declarar os atributos da mensagem.
Cada atributo começa com o tipo, seguido do nome e ao fim um código de identificação único.
message Pessoa {
string documento_pessoal = 1;
}
Esse código deve ser único na mensagem e não no arquivo proto, isso quer dizer que você pode ter vários identificadores de atributo com o número 1, por exemplo, desde que eles estejam em mensagens diferentes.
message Pessoa {
string documento_pessoal = 1;
}
message Usuario {
string login = 1;
}
Essa identificação deve ser feita a partir do número 1 e pode chegar até 536.870.911, ou (2^29)-1.
Eu tenho minhas dúvidas se você vai precisar chegar tão longe, mas é importante dizer que os números entre 19.000 à 19.999 são reservados para a identificação dos atributos do framework, isso quer dizer que se você usá-los, a geração dos stubs apresentará erro.
Nos atributos usamos como convenção de nome, letras minúsculas separando cada palavra com um underscore.
message Pessoa {
string documento_pessoal = 1;
string nome = 2;
}
Tipos de dados
Na tabela abaixo podemos ver os principais tipos para declaração de atributos e quais são os seus correspondentes nas principais linguagens. Caso queira ver a lista completa, há um link para a documentação nas referências do artigo.
.proto Type | C++ Type | Java/Kotlin Type | C# Type | Go Type |
---|---|---|---|---|
string | string | String | string | string |
int32 | int32 | int | int | int |
float | float | float | float | float64 |
double | double | double | double | float32 |
bool | bool | boolean | bool | bool |
Além dos tipos de dados da linguagem, você também pode usar como tipos nos seus atributos suas próprias mensagens, ou seja, você pode por exemplo criar uma mensagem Cidade
e usá-la como referência dentro da mensagem Endereco
.
message Endereco {
string logradouro = 1;
string numero = 2;
string bairro = 3;
Cidade cidade = 4;
}
message Cidade {
string nome = 1;
int32 ddd = 2;
}
É importante ressaltar que caso o tipo utilizado localizado em outro arquivo proto, é necessário importá-lo na seção import
.
// ...
import "endereco.proto";
// ...
message Pessoa {
string documento_pessoal = 1;
string nome = 2;
Endereco endereco = 3;
}
Listas de valores
Um recurso muito utilizado nas integrações são as listas de valores e temos dois tipos de listas nos protocol buffers:
Listas simples
Para declarar uma lista usamos a palavra chave repeated
, seguida pelo tipo do campo e o nome da lista.
Por convenção, uma vez que as listas possuem diversos itens, elas sempre são nomeadas no plural.
message Pessoa {
repeated Endereco enderecos = 3;
}
Listas chave e valor
Outra lista que temos disponível é a de chave e valor, chamada de map
, nesse tipo de lista podemos definir um tipo de dado para chave e outro tipo de dado para valor, lembrando que tanto na chave quanto no valor podemos usar tipos da linguagem ou nossos próprios tipos.
message Pessoa {
map<string, google.protobuf.Timestamp> atualizacoes = 4;
}
Enumerações
Fechando os principais tipos da linguagem, também temos um conjunto de valores fixos chamados de enum
.
Os enums têm a estrutura semelhante a de uma mensagem, porém, ao invés de atributos, há uma lista fixa de valores.
Pra declarar uma enumeração usamos a palavra chave enum
seguida do nome no padrão CamelCase.
Para a lista de valores, usamos as letras todas maiúsculas separando as palavras por um underscore.
Os itens do enum
também precisam ser numerados, só que diferente dos atributos da mensagem, a lista de valores do enum deve iniciar no número 0.
enum TipoPessoa {
NAO_DEFINIDA = 0;
FISICA = 1;
JURIDICA = 2;
}
Serviços
Fechamos a definição dos tipos, agora precisamos definir nossos serviços e funções.
Isso é muito simples, usamos a palavra chave service
, seguida do nome do serviço no padrão CamelCase.
service PessoaServico {
}
Para declararmos uma função dentro do seu serviço, iniciamos com a palavra chave rpc
, seguida pelo o nome da funcionalidade no padrão CamelCase, o parâmetro de entrada entre parênteses, seguido da palavra chave returns
com a mensagem de retorno da função também entre parênteses.
service PessoaServico {
rpc PessoaPorDocumento(Pessoa) returns (Pessoa) {}
}
Podemos declarar várias funções dentro de um mesmo serviço e obrigatoriamente todas elas precisam de uma mensagem como parâmetro de entrada e uma outra como parâmetro de saída.
Não podemos ter funções sem entrada ou saída, e os parâmetros devem obrigatoriamente ser mensagens e não tipos simples, como strings, inteiros e etc.
service PessoaServico {
rpc PessoaPorDocumento(Pessoa) returns (Pessoa) {}
rpc PessoaPorNome(Pessoa) returns (Pessoa) {}
}
Vejamos abaixo o código completo dos protocol buffers usados até aqui como exemplo.
Arquivo: endereco.proto
syntax = "proto3";
package expertostech;
option java_multiple_files = true;
option java_outer_classname = "EnderecoProtos";
option java_package = "expertostech.tutorial.grpc";
option go_package = "expertostech/tutorial/grpc";
option csharp_namespace = "ExpertosTech.Tutorial.Grpc";
message Endereco {
string logradouro = 1;
string numero = 2;
string bairro = 3;
Cidade cidade = 4;
}
message Cidade {
string nome = 1;
int32 ddd = 2;
}
Arquivo: pessoa.proto
syntax = "proto3";
package expertostech;
import "google/protobuf/timestamp.proto";
import "endereco.proto";
option java_multiple_files = true;
option java_outer_classname = "PessoaProtos";
option java_package = "expertostech.tutorial.grpc";
option go_package = "expertostech/tutorial/grpc";
option csharp_namespace = "ExpertosTech.Tutorial.Grpc";
service PessoaServico {
rpc PessoaPorDocumento(Pessoa) returns (Pessoa) {}
rpc PessoaPorNome(Pessoa) returns (Pessoa) {}
}
message Pessoa {
string documento_pessoal = 1;
string nome = 2;
repeated Endereco enderecos = 3;
map<string, google.protobuf.Timestamp> atualizacoes = 4;
TipoPessoa tipo_pessoa = 5;
}
enum TipoPessoa {
NAO_DEFINIDA = 0;
FISICA = 1;
JURIDICA = 2;
}
message Usuario {
string login = 1;
string senha = 2;
}
Gerando os stubs com Java
Para testar a geração veja o projeto completo no Github:
github/expertos-tech/protocol-buffer
Veja abaixo a estrutura do projeto e arquivo pom.xml
.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<span class="nt"><groupId></span>expertostech<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>stub-gen<span class="nt"></artifactId></span>
<span class="nt"><version></span>1.0-SNAPSHOT<span class="nt"></version></span>
<span class="nt"><properties></span>
<span class="nt"><maven.compiler.source></span>17<span class="nt"></maven.compiler.source></span>
<span class="nt"><maven.compiler.target></span>17<span class="nt"></maven.compiler.target></span>
<span class="nt"></properties></span>
<span class="nt"><dependencies></span>
<span class="nt"><dependency></span>
<span class="nt"><groupId></span>io.grpc<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>grpc-stub<span class="nt"></artifactId></span>
<span class="nt"><version></span>1.45.1<span class="nt"></version></span>
<span class="nt"></dependency></span>
<span class="nt"><dependency></span>
<span class="nt"><groupId></span>io.grpc<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>grpc-protobuf<span class="nt"></artifactId></span>
<span class="nt"><version></span>1.45.1<span class="nt"></version></span>
<span class="nt"></dependency></span>
<span class="nt"><dependency></span>
<span class="nt"><groupId></span>jakarta.annotation<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>jakarta.annotation-api<span class="nt"></artifactId></span>
<span class="nt"><version></span>1.3.5<span class="nt"></version></span>
<span class="nt"><optional></span>true<span class="nt"></optional></span>
<span class="nt"></dependency></span>
<span class="nt"></dependencies></span>
<span class="nt"><build></span>
<span class="nt"><extensions></span>
<span class="nt"><extension></span>
<span class="nt"><groupId></span>kr.motd.maven<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>os-maven-plugin<span class="nt"></artifactId></span>
<span class="nt"><version></span>1.4.1.Final<span class="nt"></version></span>
<span class="nt"></extension></span>
<span class="nt"></extensions></span>
<span class="nt"><plugins></span>
<span class="nt"><plugin></span>
<span class="nt"><groupId></span>org.xolstice.maven.plugins<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>protobuf-maven-plugin<span class="nt"></artifactId></span>
<span class="nt"><version></span>0.6.1<span class="nt"></version></span>
<span class="nt"><configuration></span>
<span class="nt"><protocArtifact></span>com.google.protobuf:protoc:3.3.0:exe:${os.detected.classifier}<span class="nt"></protocArtifact></span>
<span class="nt"><pluginId></span>grpc-java<span class="nt"></pluginId></span>
<span class="nt"><pluginArtifact></span>io.grpc:protoc-gen-grpc-java:1.4.0:exe:${os.detected.classifier}<span class="nt"></pluginArtifact></span>
<span class="nt"></configuration></span>
<span class="nt"><executions></span>
<span class="nt"><execution></span>
<span class="nt"><goals></span>
<span class="nt"><goal></span>compile<span class="nt"></goal></span>
<span class="nt"><goal></span>compile-custom<span class="nt"></goal></span>
<span class="nt"></goals></span>
<span class="nt"></execution></span>
<span class="nt"></executions></span>
<span class="nt"></plugin></span>
<span class="nt"></plugins></span>
<span class="nt"></build></span>
</project>
O que vem a seguir?
Fechamos a definição dos protocol buffers, que é a parte central do sistema gRPC.
No próximo artigo, última parte dessa série, entraremos na parte prática de tudo que vimos até aqui, a implementação passo a passo de um serviço gRPC com Java, usando os protocol buffers.
E se você chegou até aqui, deixe o seu gostei no artigo e já aproveita pra seguir o canal ExpertosTech em todas redes sociais:
https://linktr.ee/expertostech
Referencias:
Top comments (0)