Em um projeto recente na Lambda3, tivemos o desafio de desenvolver um sincronizador de dados, serializando e deserializando estruturas de diferentes fontes de dados, o qual também eram lidos por outras aplicações.

 
Um dos desafios era tornar o processo de serialização/deserialização mais otimizado, aumentando a capacidade e velocidade de processamento dos mesmos dentro deste sincronizador e de outros sistemas, como uma ASP.NET API que utilizava amplamente estas estruturas. Neste post, a idéia é compartilhar como utilizamos Protocol Buffers ou simplesmente protobuf para tornar esse processo mais eficiente.
 

Protobuf

 
É um protocolo criado pelo Google para trafegar estruturas de dados de forma extensível e otimizada entre diferentes aplicações, sendo uma alternativa ao uso de serialização de dados em JSON, XML e similares.
 

Como funciona?

 

  • Cria-se um arquivo .proto com a definição da estrutura de dados a ser utilizada.
  • Compila-se este arquivo para gerar uma classe que representa essa estrutura na sua aplicação, no caso, em C#.
  • Instancia-se a classe na aplicação e atribuí-se os dados
  • Realiza-se a encodificação para o formato _binário_, utilizando métodos presentes na classe C# gerada.
  • O array de bytes é então trafegado 🙂
Abaixo eu detalho com maior profundidade os passos acima.

Primeiramente, cria-se um arquivo com extensão .proto o qual irá conter toda a estrutura, veja um exemplo abaixo:
 
syntax = "proto3";

message Person {
string Name = 1;
int32 Id = 2;
string Email = 3;

enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}

message PhoneNumber {
string Number = 1;
PhoneType Type = 2;
}

PhoneNumber Phone = 4;
}
 
A estrutura em sí é bem simples de criar e compreender, bem parecido com uma classe C#, ela contém:

  • tipos de dados
  • nome das propriedades
  • posição destes dados no momento de realizar a encodificação/decodificação
  • etc.
Você pode consultar a documentação completa do .Proto Language Guide para maiores detalhes.

Após criado o .proto o próximo passo é compilarmos essa estrutura para gerar um arquivo a ser utilizado pela sua aplicação, neste caso, iremos gerar uma classe C# com o protoc, o compilador que vai ler a definição e gerar nossa classe C#.
 

Onde baixar o compilador?

 

Até o presente momento da escrita deste post a versão mais atual é a 3.6.1.

Você pode baixar o compilador de várias fontes, as principais são:
 
dotnet global tool –  protoc

dotnet tool install --global protoc --version 3.6.1

Chocolatey – protoc
choco install protoc --version 3.6.1

E, se preferir, é possível baixar o .exe diretamente do repositório oficial na área de releases, você deve baixar o arquivo protoc-3.6.1-win32.zip e descompactar o arquivo bin/protoc.exe, não se esqueça de adicionar o caminho para o protoc.exe em suas variáveis de ambiente para que ele também fique acessível pelo terminal, assim como nos instaladores mencionados acima.
 

Gerando a classe C# a partir do arquivo .proto

 

Instalado o compilador do protoc, agora basta abrir o terminal e rodar o comando de compilação abaixo:

protoc --proto_path=<pasta_protos> --csharp_out=<pasta_de_saída> nome_do_arquivo.proto

ex:

cd c:/repos/projeto
protoc --proto_path=protos/ --csharp_out=Sincronizador/Modelos/ Produto.proto

O resultado final é algo semelhante a este exemplo disponibilizado no gist.

Este arquivo C# não deve ser alterado manualmente, só mesmo via protoc 😉
 

Como utilizar a classe .cs gerada

 

Adicione a classe C# em sua aplicação, ela possui uma dependência com o pacote Google.Protobuf, portando, inclua essa dependência em seu projeto para que a aplicação compile normalmente.

Install-Package Google.Protobuf -Version 3.6.1

A serialização do objeto pode ser feita diretamente para um array e bytes, a serialização com protobuf não gera uma string comum como encodificação final, assim como ocorre com a serialização em JSON e XML, o protobuf possui o seu próprio formato de encodificação.

//instanciando um objeto PhoneNumber e Person
var phoneNumber = new PhoneNumber
{
  Number = "(11) 12345-6789",
  Type = PhoneType.Mobile
};
var person = new Person()
{
  Name = "Lazaro",
  Id = 123,
  Email = "[email protected]",
  Phone = phoneNumber
};

//serializando o objeto
File.WriteAllBytes("person123_file", person.ToByteArray());

//deserializando o objeto
var person_fromfile = new Person();
person_fromfile.MergeFrom(File.ReadAllBytes("person123_file"));

Deixo abaixo um exemplo de como ficaria a serialização de uma classe com a seguinte definição.
 
message Test1 {
optional int32 a = 1;
}
```

```
var message = new Test1 {
a = 150
}

File.WriteAllBytes("test1", message.ToByteArray());

O resultado final do arquivo `test1` seriam apenas 3 bytes:
 
08 96 01

Por que não usar apenas XML ou JSON?

Tanto o XML quanto o JSON são excelentes formatos, são independentes de plataforma e, em geral, são de fácil compreensão. Contudo, listo alguns pontos abaixo, colhidos também da documentação oficial, para que você avalie se, para o SEU cenário, faz sentido a utilização do protoc.

  • estruturas `.proto` são simples, principalmente para estruturas com poucas propriedades, conforme visto no post.
  • são de 3 a 10 vezes menores em tamanho
  • são de 20 a 100 vezes mais rápidas na serialização/deserialização
  • geram classes de fácil utilização pela aplicação
Ao invés de trafegarmos strings em formato JSON, XML e etc., o qual terão um processo de Parse que pode ser custoso a nível de processamento e memória, o protoc, por sua vez, otimizada a encodificalção para conter apenas o conteúdo das propriedades, já com suas posições pré-definidas pelo .proto, bem como outras otimizações como 128 Varints, Signed Integers e etc.
 

Comparação com Serialização em JSON

Criei um projeto simples para testar o cenário de serialização entre protoc e JSON aqui neste github. Você pode baixar o projeto e rodar localmente, aproveite para testar outros objetos e outras estratégias.

O resultado mostrou que a performance do protoc é exponencialmente maior que a da serialização em JSON, mesmo que os dois formatos estejam serializando para um array de bytes. Vejam o gráfico com o compilado dos resultados.

 

protoc vs json

Espero que o post tenha sido útil, se você já utilizou alguma implementação de protobuf ou ficou com alguma dúvida comente no blog.

Até mais.