Desenvolvimento

19 dez, 2018

Protobuf – Uma alternativa à serialização em JSON e XML

Publicidade

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 artigo, a ideia é 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 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 si é bem simples de criar e compreender – bem parecida 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.

Depois de criar o .proto, o próximo passo é compilar 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 criação deste artigo, 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

Exemplo:

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, somente 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, e 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@email.com",
  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` seria 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, 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 artigo.
  • São de 3 a 10 vezes menores em tamanho
  • São de 20 a 100 vezes mais rápidas na serialização/desserialização
  • Geram classes de fácil utilização pela aplicação

Ao invés de trafegarmos strings em formato JSON, XML e etc, os quais terão um processo de Parse que pode ser custoso a nível de processamento e memória, o protoc, por sua vez, otimiza 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 no 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.

Espero que o artigo tenha sido útil. Se você já utilizou alguma implementação de protobuf ou ficou com alguma dúvida, comente logo abaixo.

Até mais!

***

Este artigo foi produzido em parceria com a Lambda3. Leia outros conteúdos no blog da empresa: blog.lambda3.com.br