Entendendo Protocol Buffers — Protobuf

Daniel Padua Ferreira
Daniel Padua Blog
10 min readNov 24, 2020

--

Introdução

Protocol Buffers (protobuf) é um método de serializar dados estruturados que é principalmente útil para comunicação entre serviços ou até mesmo para guardar dados. Ele foi desenhado pelo Google no começo de 2001 (mas apenas publicamente liberado em 2008) para ser menor e mais rápido que o XML. As mensagens protobuf são serializadas em um formato binary wire que é muito compacto, aumentando a performance.

Detalhes

Este protocolo envolve uma linguagem de descrição de interface específica para modelar a estrutura de dados definido em um arquivo .proto
Um programa de qualquer linguagem suportada consegue gerar através de um compilador código fonte nativo utilizando o contrato afim de criar ou interpretar streams de bytes que representam os dados estruturados.
Protocol buffers normalmente serve como base para remote procedure call (RPC) que é muito usada para comunicação de aplicações entre diferentes máquinas, o mais comumente utilizado é o gRPC. Protobuf é similar ao Apache Thrift usado pelo Facebook, Ion criado pela Amazon ou o Microsoft Bonds Protocol.

Exemplo

Neste exemplo nós iremos criar dois projetos:

  • Uma aplicação console Java que irá utilizar uma especificação .proto de um cliente (customer.proto) para gerar um arquivo com um cliente hard coded
  • Uma aplicação console C# que irá ler o arquivo do cliente hard coded gerado pela aplicação java e exibir os dados no console

Resumo

Caso você apenas queira ler o código e ir descobrindo e aprendendo por conta própria, disponibilizei repositório com estas duas aplicações. Primeiramente siga as instruções do arquivo README_pt.md do projeto Java no repositório:

Após gerar o arquivo serializado com protobuf, siga as instruções do arquivo README_pt.md do projeto C# no repositório:

Contrato Protobuf

Primeiro de tudo, vamos criar uma estrutura que irá representar um cliente. Os dados que precisamos representar são:

  • Identificação única (ID)
  • Foto
  • Nome
  • Data de nascimento
  • Data/Hora de criação
  • Data/Hora da última atualização

Portanto, será necessário criar um arquivo .proto como este:

Alguns pontos sobre o código acima:

  • O tipo Timestamp é um "Well Known Type" que foi introduzido no proto3, você pode utilizar estes tipos apenas importando-os nas primeiras linhas do arquivo proto
  • Os tipos Date e Money são "Google Common Type", diferentes do "Well Known Type" não é possível usá-los apenas realizando a importação. É necessário copiar o conteúdo destes arquivos do repositório do google e colar no seu projeto, qualquer que seja a linguagem que está utilizando.

Existem outros tipos, você pode ler a documentação aqui. Dos Well Known Types aqui e do Google Common Types aqui.

Este arquivo .proto será um recurso em comum entre os projetos C# e Java, mas para simplificar o exemplo, irei recriá-lo nos repositórios de ambos projetos. O ideal para projetos grandes e complexos seria ter um repositório separado como um ponto único para os projetos que irão utilizá-lo.

Aplicação Java Console

Com isto explicado, vamos começar a criar uma aplicação Java console. Para este exemplo irei utilizar a OpenJDK 15, IntelliJ IDEA CE e o Maven como build tool.

  1. Abra o IntelliJ IDEA CE and escolha criar um novo projeto
Uma imagem que mostra a tela inicial do IntelliJ IDEA Community, com a opção de novo projeto destacada
Criando um novo projeto

2. Escolha Maven no painel esquerdo, selecione sua Java JDK no canto superior direito. Como disse anteriormente, irei utilizar a OpenJDK 15 que havia instalado anteriormente

Uma imagem da tela que o IntelliJ exibe para escolher a build tool, com Maven selecionado e o arquétipo padrão
Detalhes do novo projeto

3. Preencha os próximos campos como desejar, por exemplo:

Uma imagem exibindo as configurações do maven a preencher, como: localização do projeto, groupid, artifactid e versão
Configurações maven do projeto

Projeto criado. Vamos começar adicionando o encoding do código fonte, informar ao compilador maven que estaremos utilizando a JDK 15 e adicionando a dependência protobuf-java feita pelo Google. Adicione as linhas a seguir no arquivo pom.xml dentro da tag "projeto", logo após a tag "version":

Agora, vamos incluir os arquivos .proto definidos na seção acima. Mas antes crie um diretório "proto" dentro de src/main:

Uma imagem da ação de abrir o menu de contexto do IntelliJ para criar o diretório dentro de src/main
Criando o diretório dos arquivos protobuf

Depois adicione um novo arquivo chamado: customer.proto e coloque o código mencionado na seção acima:

Uma imagem mostrando o arquivo protobuf customer com um erro de compilação
Erro ao importar outros arquivos protobuf

Os imports do money.proto e date.proto irão mostrar erro porque ainda não os criamos. Você pode criá-los repetindo o procedimento acima adicionando o código do money.proto e date.proto diretamente do repositório do Google.

Uma imagem exibindo o arquivo customer.proto sem erros de compilação
customer.proto sem erros

Ok, contratos protobuf criados. Agora, para gerarmos o código fonte nativo (classes java) a partir do contrato, nós precisamos usar o executável protoc para compilar os arquivos .proto apontando a linguagem desejada de saída. Existem duas maneiras principais de fazer isto:

  • Manualmente, baixando o protoc na sua máquina e executando-o. Caso você deseje ir por este caminho, leia o guia de instalação do protoc aqui
  • Automaticamente, adicionando geração de código via protoc no build do seu projeto maven. Existem alguns plugins maven para isto, mas irei utilizar o protoc-jar-maven-plugin, que envolve o executável do protoc como um jar assim ele pode ser executado em qualquer SO e então compilar seus arquivos .proto.

Você pode começar a utilizá-lo apenas adicionando as linhas abaixo no seu pom.xml abaixo da tag "project", logo após a tag "dependencies":

Sempre verifique se existem novas versões estáveis antes de adicionar dependências ou plugins em seu pom.xml

Nós estamos adicionando a estrutura de diretório onde estamos armazenando os arquivo .proto, assim o plugin saberá onde eles estão para compilá-los.

Neste ponto é provável que você queira executar o compilador protoc para gerar as classes java que representam os contratos protobuf, então, clique na tab maven e depois no botão "run maven goal" e escreva: mvn clean install

Uma imagem mostrando o maven integrado ao IntelliJ pronto para executar o goal clean install
Executando maven clean install

Caso a compilação tenha sucesso, a mensagem a seguir irá ser exibida:

Uma imagem mostrando que o projeto foi compilado corretamente
Um build de sucesso

Próximo passo é criar um pacote "dev.danielpadua.protobufexamplejava" e dentro do mesmo uma classe principal para nossa aplicação console:

Uma imagem mostrando a classe Program com o método main vazio
Nosso método main

Caso você digite “Customer”, o autocomplete do IntelliJ irá aparecer e te sugerir importar a classe gerada pelo protoc via plugin:

Uma imagem mostrando o autocomplete do IntelliJ reconhecendo com sucesso as classes auto-geradas
Sucesso ao auto-completar classes geradas

Então, deu tudo certo. Agora nós podemos escrever o código para gerar o cliente hard coded e gravá-lo no diretório que você desejar (dentro do método main):

Note que o código acima faz uso de uma classe Utils para converter os tipos: Java LocalDate para Google Date, Java BigDecimal para Google Money e Java LocalDateTime para Google Timestamp. Você pode incluir minha classe Utils no seu projeto copiando o código a seguir:

Caso você tenha o problema a seguir no IntelliJ, você pode simplesmente utilizar a sugestão sugerida: “Set language level to 8 — Lambdas, type annotations etc”:

Uma image mostrando o erro de language level no IntelliJ IDEA Community
Language level no IntelliJ IDEA

Agora podemos executar a aplicação. Clique no botão “Add Configuration” localizado no canto superior direito, ao lado do botão de build. Clique no botão com o sinal de soma e selecione “Application”:

Uma imagem mostrando o popup para criação de uma run configuration para rodar a aplicação no IntelliJ IDEA
Criando uma run configuration

Então preencha o nome da configuração e selecione a classe principal a ser executada:

Uma imagem mostrando os últimos campos a serem preenchidos para criar uma configuração de execução
Últimos campos para preencher antes de executar

Depois, clique no botão executar ou debugar caso você queira:

Uma imagem exibindo a configuração criada, e os botões de executar e debugar
Vamos rodar!

Um resultado de sucesso deve exibir a mensagem a seguir:

Uma imagem mostrando o resultado depois de executar a aplicação com sucesso gerando o arquivo protobuf
Arquivo protobuf criado com sucesso

Agora verifique o diretório que você definiu como saída para o arquivo:

Uma imagem mostrando o finder do macOs com o arquivo protobuf-customer gerado pela aplicação java
Sucesso!

Nós implementamos com sucesso uma simples aplicação console Java que cria uma mensagem protobuf utilizando uma estrutura definida em um arquivo .proto, através de um processo de compilação automatizado e integrado ao build maven. Agora para provar que é útil para comunicação entre projetos de diferentes linguagens, iremos criar uma aplicação console C# para ler este arquivo e mostrar os dados no console.

Aplicação Console C#

Para o examplo do C# irei utilizar: .NET 5, Visual Studio Code e o pacote Grpc.AspNetCore que contém um compilador de protobuf é embutido ao dotnet build.

Abra o Visual Studio Code, abra o terminal integrado, navegue até o diretório onde você deseja guardar o projeto e execute os comandos a seguir, linha a linha:

Feito, projeto criado, agora vamos abri-lo no Visual Studio Code:

Uma imagem mostrando o Visual Studio Code com o menu Open e o terminal abertos
Selecione a pasta raíz (a mesma que você está no terminal)

A estrutura do projeto deve ser como esta:

Uma iamgem mostrando o questionamento do Visual Studio Code sobre auto gerar os assets de build debug para o projeto
Adicione os assets para fazer build e debug

Clique no “Yes” na mensagem no canto inferior direito, para que o Omnisharp crie a pasta .vscode com os assets para executar/debugar o projeto. Selecione “DanielPadua.ProtobufExampleCsharp”:

Uma imagem mostrando o Visual Studio Code questionando sobre qual projeto ele deve criar os assets de build debug
Escolha o projeto para gerar os assets

Abra o terminal novamente no nível src/DanielPadua.ProtobufExampleCsharp e execute:

Agora vamos incluir os contratos .proto. Crie um diretório abaixo da raíz do projeto principal e nomeie-o como: “Protos” e crie os arquivos .proto listados na seção acima:

Uma imagem mostrando a estrutura do projeto no Visual Studio Code e os arquivos protobuf criados corretamente
Arquivos protobuf criados

Adicione os arquivos .proto no .csproj para que o protoc compile quando o dotnet build rodar:

Uma imagem mostrando o código do csproj adicionando o caminho dos arquivos protobuf recém criados
Adicionando o caminho dos arquivos protobuf para o plugin compilar

Agora vamos compilar o projeto para gerar as classes C# a partir dos contratos .proto. No terminal, execute:

A mensagem de um build de sucesso deve ser algo como isto:

Uma imagem mostrando o terminal integrado ao Visual Studio Code com a saída indicando que o build obteve sucesso
Dotnet build ok

Próximo passo é substituir o “Hello World” no método Main pelas linhas a seguir:

Agora você deve tentar importar o namespace "Contracts", pressione ctrl+. (windows, linux) or cmd+. (macOs) para abrir o autocomplete, e então a opção de importar irá aparecer:

Uma imagem mostrando o intellisense do Visual Studio Code importando os contratos protobuf com sucesso
Intellisense e a geração de código protobuf funcionando

Caso a opção de importar não aparece, tente reiniciar o Omnisharp usando: ctrl+shift+p (windows, linux) or cmd+shift+p (macOs), digite: restart omnisharpe aperte o enter:

Uma imagem mostrando o command palette do Visual Studio Code com a opção de reiniciar o omnisharp selecionada
Espere por um momento antes de tentar importar novamente

Infelizmente a integração do Omnisharp com o compilador de protobuf não é perfeita, mas funciona.

Garanta que você está lendo o mesmo diretório e arquivo que o projeto Java gerou, e então rode sua aplicação console C# apertando o botão de executar do Visual Studio Code (caso você tenha configurado os assets de execução/debug corretamente) ou simplesmente rodando a linha de comando: dotnet run estando no diretório raíz do projeto principal:

Uma imagem mostrando o terminal integrado do Visual Studio Code com o comando dotnet run indicando sucesso
Executando do terminal
Uma imagem mostrando a aba de executar ou debugar do Visual Studio Code, indicando sucesso
Executando do Visual Studio Code

E, nós conseguimos. Recebemos e interpretamos uma mensagem serializada em protobuf gerada por uma aplicação Java, dentro de uma aplicação C#.

Conclusão

Protobuf foi feito para ser mais rápido, mais leve e consequentemente mais performático do que outros protocolos. Então, te convido a fazer uma breve pesquisa como: "protobuf vs json performance" ou alguma outra, para ver benchmarks e outros cases de sucesso mundo afora.

Neste artigo espero ter dado um bom mergulho para aqueles que, como eu há um tempo atrás, não haviam nem ouvido falar sobre protobuf e sempre utilizaram JSON ou XML para serialização.

Até a próxima!

Publicado originalmente em https://blog.danielpadua.dev 24 de Novembro de 2020.

--

--

Daniel Padua Ferreira
Daniel Padua Blog

Microsoft Certified Professional (MCP), Certified Tester Foundation Level (CTFL), Software Engineer, Technology and Cryptocurrencies enthusiast