DEV Community

Cover image for O que é gRPC - Como usar os Protocol Buffers | Parte 2
Expertos Tech
Expertos Tech

Posted on • Updated on

O que é gRPC - Como usar os Protocol Buffers | Parte 2

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.

Image description

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";
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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";
Enter fullscreen mode Exit fullscreen mode

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";
Enter fullscreen mode Exit fullscreen mode

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";
Enter fullscreen mode Exit fullscreen mode

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 {
}
Enter fullscreen mode Exit fullscreen mode

É importante saber que podemos declarar várias mensagens em um mesmo arquivo proto.

message Pessoa {
}

message Usuario {
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

É 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;
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode
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;
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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 {
}
Enter fullscreen mode Exit fullscreen mode

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) {}
}
Enter fullscreen mode Exit fullscreen mode

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) {}
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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.

Estrutura do projeto

<?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>

    <groupId>expertostech</groupId>
    <artifactId>stub-gen</artifactId>
    <version>1.0-SNAPSHOT</version>


    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-stub</artifactId>
            <version>1.45.1</version>
        </dependency>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-protobuf</artifactId>
            <version>1.45.1</version>
        </dependency>
        <dependency>
            <groupId>jakarta.annotation</groupId>
            <artifactId>jakarta.annotation-api</artifactId>
            <version>1.3.5</version>
            <optional>true</optional>
        </dependency>
    </dependencies>

    <build>
        <extensions>
            <extension>
                <groupId>kr.motd.maven</groupId>
                <artifactId>os-maven-plugin</artifactId>
                <version>1.4.1.Final</version>
            </extension>
        </extensions>
        <plugins>
            <plugin>
                <groupId>org.xolstice.maven.plugins</groupId>
                <artifactId>protobuf-maven-plugin</artifactId>
                <version>0.6.1</version>
                <configuration>
                    <protocArtifact>com.google.protobuf:protoc:3.3.0:exe:${os.detected.classifier}</protocArtifact>
                    <pluginId>grpc-java</pluginId>
                    <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.4.0:exe:${os.detected.classifier}</pluginArtifact>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                            <goal>compile-custom</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>
Enter fullscreen mode Exit fullscreen mode

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)