Considerações sobre codificação de mensagens
Muitos aplicativos de nuvem usam mensagens assíncronas para trocar informações entre componentes do sistema. Um aspecto importante do sistema de mensagens é o formato usado para codificar os dados de conteúdo. Depois que você escolher uma tecnologia de sistema de mensagens, a próxima etapa será definir como as mensagens serão codificadas. Há muitas opções disponíveis, mas a escolha certa depende do seu caso de uso.
Este artigo descreve algumas das considerações.
Necessidades de troca de mensagens
Uma troca de mensagens entre um produtor e um consumidor precisa:
- Uma forma ou estrutura que define o conteúdo da mensagem.
- Um formato de codificação para representar a carga útil.
- Bibliotecas de serialização para ler e gravar o conteúdo codificado.
O produtor da mensagem define a forma da mensagem com base na lógica de negócios e nas informações que deseja enviar aos consumidores. Para estruturar a forma, divida as informações em assuntos discretos ou relacionados (campos). Decida as características dos valores desses campos. Considere: Qual é o tipo de dados mais eficiente? O conteúdo sempre terá determinados campos? O conteúdo terá um único registro ou um conjunto repetido de valores?
Em seguida, escolha um formato de codificação dependendo de sua necessidade. Alguns fatores incluem a capacidade de criar dados altamente estruturados se você precisar, o tempo necessário para codificar e transferir a mensagem e a capacidade de analisar a carga. Dependendo do formato de codificação, escolha uma biblioteca de serialização com suporte.
Um consumidor da mensagem deve estar ciente dessas decisões para que saiba como ler mensagens de entrada.
Para transferir mensagens, o produtor serializa a mensagem para um formato de codificação. No final do recebimento, o consumidor desserializa o conteúdo para usar os dados. Dessa forma, ambas as entidades compartilham o modelo e, desde que a forma não mude, o sistema de mensagens continua sem problemas. Quando o contrato é alterado, o formato de codificação deve ser capaz de lidar com a alteração sem interromper o consumidor.
Alguns formatos de codificação, como JSON, são autodescrevendo, o que significa que podem ser analisados sem fazer referência a um esquema. No entanto, esses formatos tendem a produzir mensagens maiores. Com outros formatos, os dados podem não ser analisados tão facilmente, mas as mensagens são compactas. Este artigo destaca alguns fatores que podem ajudá-lo a escolher um formato.
Considerações sobre o formato de codificação
O formato de codificação define como um conjunto de dados estruturados é representado como bytes. O tipo de mensagem pode influenciar a opção de formato. As mensagens relacionadas a transações comerciais provavelmente conterão dados altamente estruturados. Além disso, talvez você queira recuperá-lo mais tarde para fins de auditoria. Para um fluxo de eventos, talvez você queira ler uma sequência de registros o mais rápido possível e armazená-la para análise estatística.
Aqui estão alguns pontos a serem considerados ao escolher um formato de codificação.
Capacidade de leitura humana
A codificação de mensagens pode ser amplamente dividida em formatos binários e baseados em texto.
Com a codificação baseada em texto, o conteúdo da mensagem está em texto sem formatação e, portanto, pode ser inspecionado por uma pessoa sem usar bibliotecas de código. Formatos legíveis por humanos são adequados para dados de arquivamento. Além disso, como um humano pode ler a carga, os formatos baseados em texto são mais fáceis de depurar e enviar para logs para solucionar problemas de erros.
A desvantagem é que a carga tende a ser maior. Um formato comum baseado em texto é JSON.
Encriptação
Se houver dados confidenciais nas mensagens, considere se essas mensagens devem ser totalmente criptografadas, conforme descrito nesta orientação sobre como criptografar dados do Barramento de Serviço do Azure em repouso. Como alternativa, se apenas determinados campos precisarem ser criptografados e você preferir reduzir os custos de nuvem, considere usar uma biblioteca como NServiceBus para isso.
Tamanho da codificação
O tamanho da mensagem afeta o desempenho de E/S da rede em toda a transmissão. Formatos binários são mais compactos do que formatos baseados em texto. Formatos binários exigem bibliotecas de serialização/desserialização. O conteúdo não pode ser lido a menos que seja decodificado.
Use um formato binário se quiser reduzir o volume de fios e transferir mensagens mais rapidamente. Essa categoria de formato é recomendada em cenários em que a largura de banda de rede ou armazenamento é uma preocupação. As opções de formatos binários incluem o Apache Avro, os Buffers de Protocolo do Google (protobuf), o MessagePack e o CBOR (Representação Concisa de Objetos Binários). Os prós e os contras desses formatos são descritos nesta seção.
A desvantagem é que a carga não é legível por humanos. A maioria dos formatos binários usa sistemas complexos que podem ser caros de manter. Além disso, eles precisam de bibliotecas especializadas para decodificar, o que pode não ter suporte se você quiser recuperar dados de arquivamento.
Compreendendo a carga útil
Um conteúdo de mensagem chega como uma sequência de bytes. Para analisar essa sequência, o consumidor deve ter acesso aos metadados que descrevem os campos de dados no conteúdo. Há duas abordagens principais para armazenar e distribuir metadados:
Metadados marcados. Em algumas codificações, notadamente JSON, os campos são marcados com o tipo de dados e o identificador, dentro do corpo da mensagem. Esses formatos são autodescritivos porque podem ser analisados em um dicionário de valores sem a necessidade de se referir a um esquema. Uma maneira de o consumidor entender os campos é consultar valores esperados. Por exemplo, o produtor envia carga em JSON. O consumidor analisa o JSON em um dicionário e verifica a existência de campos para entender o conteúdo. Outra maneira é o consumidor aplicar um modelo de dados compartilhado pelo produtor. Por exemplo, se você estiver usando uma linguagem tipada estaticamente, muitas bibliotecas de serialização JSON poderão analisar uma cadeia de caracteres JSON em uma classe tipada.
Esquema. Um esquema define formalmente a estrutura e os campos de dados de uma mensagem. Nesse modelo, o produtor e o consumidor têm um contrato por meio de um esquema bem definido. O esquema pode definir os tipos de dados, os campos obrigatórios/opcionais, as informações de versão e a estrutura do conteúdo. O produtor envia o conteúdo de acordo com o esquema do autor. O consumidor recebe o conteúdo aplicando um esquema de leitor. A mensagem é serializada/deserializada utilizando bibliotecas específicas para codificação. Há duas maneiras de distribuir esquemas:
Armazene o esquema como um preâmbulo ou cabeçalho na mensagem, mas separado do conteúdo.
Armazene o esquema externamente.
Alguns formatos de codificação definem o esquema e usam ferramentas que geram classes do esquema. O produtor e o consumidor usam essas classes e bibliotecas para serializar e desserializar o conteúdo. As bibliotecas também fornecem verificações de compatibilidade entre o esquema de escritor e o esquema de leitura. Tanto o protobuf quanto o Apache Avro seguem essa abordagem. A principal diferença é que o protobuf tem uma definição de esquema independente de linguagem, mas o Avro usa JSON compacto. Outra diferença é a maneira como ambos os formatos fornecem verificações de compatibilidade entre esquemas de leitor e gravador.
Outra maneira de armazenar o esquema externamente em um registro de esquema. A mensagem contém uma referência ao esquema e à carga útil. O produtor envia o identificador de esquema na mensagem e o consumidor recupera o esquema especificando esse identificador de um repositório externo. Ambas as partes usam uma biblioteca específica de formato para ler e gravar mensagens. Além de armazenar o esquema, um registro pode fornecer verificações de compatibilidade para garantir que o contrato entre o produtor e o consumidor não seja interrompido à medida que o esquema evolui.
Antes de escolher uma abordagem, decida o que é mais importante: o tamanho dos dados de transferência ou a capacidade de analisar os dados arquivados posteriormente.
Armazenar o esquema junto com o conteúdo gera um tamanho de codificação maior e é preferencial para mensagens intermitentes. Escolha essa abordagem se a transferência de partes menores de bytes for crucial ou se você esperar uma sequência de registros. O custo de manutenção de um repositório de esquema externo pode ser alto.
No entanto, se a decodificação sob demanda do conteúdo for mais importante do que o tamanho, incluir o esquema com o conteúdo ou a abordagem de metadados marcados garantirá a decodificação posteriormente. Pode haver um aumento significativo no tamanho da mensagem e pode afetar o custo do armazenamento.
Controle de versão do esquema
À medida que os requisitos de negócios mudam, espera-se que a forma mude e o esquema evolua. O controle de versão permite que o produtor indique atualizações de esquema que podem incluir novos recursos. Há dois aspectos no versionamento:
O consumidor deve estar ciente das alterações.
Uma forma é que o consumidor verifique todos os campos para determinar se o esquema foi alterado. Outra maneira é que o produtor publique um número de versão do esquema com a mensagem. Quando o esquema evolui, o produtor incrementa a versão.
As alterações não devem afetar ou quebrar a lógica de negócios dos consumidores.
Suponha que um campo seja adicionado a um esquema existente. Se os consumidores que usam a nova versão receberem uma carga de acordo com a versão antiga, sua lógica pode falhar se não conseguirem ignorar a falta do novo campo. Considerando o caso inverso, suponha que um campo seja removido no novo esquema. Os consumidores que usam o esquema antigo podem não conseguir ler os dados.
Formatos de codificação, como o Avro, oferecem a capacidade de definir valores padrão. No exemplo anterior, se o campo for adicionado com um valor padrão, o campo ausente será preenchido com o valor padrão. Outros formatos, como o protobuf, fornecem funcionalidade semelhante por meio de campos obrigatórios e opcionais.
Estrutura do conteúdo
Considere a maneira como os dados são organizados na carga útil. É uma sequência de registros ou uma carga única e discreta? A estrutura de carga útil pode ser categorizada em um dos seguintes modelos:
Matriz/dicionário/valor: define entradas que contêm valores em uma ou matrizes multidimensionais. As entradas têm pares chave-valor exclusivos. Ele pode ser estendido para representar as estruturas complexas. Alguns exemplos incluem JSON, Apache Avro e MessagePack.
Esse layout será adequado se as mensagens forem codificadas individualmente com esquemas diferentes. Se você tiver vários registros, o conteúdo poderá ficar muito redundante, fazendo com que o conteúdo fique sobrecarregado.
Dados tabulares: as informações são divididas em linhas e colunas. Cada coluna indica um campo ou o assunto das informações e cada linha contém valores para esses campos. Esse layout é eficiente para um conjunto repetido de informações, como dados de série temporal.
CSV é um dos formatos mais simples baseados em texto. Ele apresenta dados como uma sequência de registros com um cabeçalho comum. Para codificação binária, o Apache Avro tem um preâmbulo semelhante a um cabeçalho CSV, mas gera um tamanho de codificação compacto.
Suporte à biblioteca
Considere usar formatos bem conhecidos em vez de um modelo proprietário.
Formatos conhecidos têm suporte por meio de bibliotecas que têm suporte universal da comunidade. Com formatos especializados, você precisa de bibliotecas específicas. Sua lógica de negócios pode ter que contornar algumas das opções de design de API fornecidas pelas bibliotecas.
Para o formato baseado em esquema, escolha uma biblioteca de codificação que faça verificações de compatibilidade entre o leitor e o esquema de gravador. Determinadas bibliotecas de codificação, como o Apache Avro, requerem que o consumidor especifique o esquema de gravação e de leitura antes de desserializar a mensagem. Essa verificação garante que o consumidor esteja ciente das versões do esquema.
Interoperabilidade
Sua escolha de formatos pode depender da carga de trabalho específica ou do ecossistema de tecnologia.
Por exemplo:
O Azure Stream Analytics tem suporte nativo para JSON, CSV e Avro. Ao usar o Stream Analytics, faz sentido escolher um desses formatos, se possível. Caso contrário, você pode fornecer um desserializador personalizado, mas isso adiciona alguma complexidade adicional à sua solução.
JSON é um formato de intercâmbio padrão para APIs REST HTTP. Se seu aplicativo receber conteúdo JSON de clientes e, em seguida, os colocar em uma fila de mensagens para processamento assíncrono, talvez faça sentido usar JSON para o sistema de mensagens, em vez de reencodificar em um formato diferente.
Estes são apenas dois exemplos de considerações de interoperabilidade. Em geral, os formatos padronizados serão mais interoperáveis do que os formatos personalizados. Nas opções baseadas em texto, o JSON é um dos mais interoperáveis.
Opções para formatos de codificação
Aqui estão alguns formatos de codificação populares. Considere as considerações antes de escolher um formato.
JSON
JSON é um padrão aberto (RFC8259IETF). É um formato baseado em texto que segue o modelo de matriz/dicionário/valor.
O JSON pode ser usado para marcar metadados e você pode analisar o conteúdo sem um esquema. O JSON dá suporte à opção de especificar campos opcionais, o que ajuda na compatibilidade para frente e para trás.
A maior vantagem é que está universalmente disponível. É mais interoperável e o formato de codificação padrão para muitos serviços de mensagens.
Sendo um formato baseado em texto, ele não é eficiente na transmissão e não é uma opção ideal em casos em que o armazenamento é uma preocupação. Se você estiver retornando itens armazenados em cache diretamente para um cliente por meio de HTTP, o armazenamento de JSON poderá economizar o custo de desserialização de outro formato e, em seguida, serializar para JSON.
Use JSON para mensagens de registro único ou para uma sequência de mensagens em que cada mensagem tenha um esquema diferente. Evite usar JSON para uma sequência de registros, como para dados de série temporal.
Há outras variações de JSON, como BSON (JSON binário), que é uma codificação binária alinhada para trabalhar com o MongoDB.
CSV (valores separados por vírgula)
CSV é um formato tabular baseado em texto. O cabeçalho da tabela indica os campos. É uma opção preferencial em que a mensagem contém um conjunto de registros.
A desvantagem é a falta de padronização. Há muitas maneiras de expressar separadores, cabeçalhos e campos vazios.
Buffers de protocolo (protobuf)
Protocol Buffers (ou protobuf) é um formato de serialização que usa arquivos de definições fortemente tipadas para definir esquemas em pares de chave/valor. Esses arquivos de definição são compilados em classes específicas de linguagem que são usadas para serializar e desserializar mensagens.
A mensagem contém uma pequena carga binária compactada, que resulta em uma transferência mais rápida. A desvantagem é que o conteúdo não é legível por humanos. Além disso, como o esquema é externo, não é recomendável para casos em que você precisa recuperar dados arquivados.
Apache Avro
apache Avro é um formato de serialização binária que usa um arquivo de definição semelhante ao protobuf, mas não há uma etapa de compilação. Em vez disso, os dados serializados sempre incluem um preâmbulo de esquema.
O preâmbulo pode conter o cabeçalho ou um identificador de esquema. Devido ao tamanho de codificação menor, o Avro é recomendado para transmitir dados. Além disso, como ele tem um cabeçalho que se aplica a um conjunto de registros, é uma boa opção para dados tabulares.
MessagePack
MessagePack é um formato de serialização binária projetado para ser compacto para transmissão por fio. Não há nenhum esquema de mensagem ou verificação de tipo de mensagem. Esse formato não é recomendado para armazenamento em massa.
CBOR
CBOR (Representação de Objeto Binário Conciso) (Especificação) é um formato binário que oferece tamanho de codificação pequeno. A vantagem do CBOR em relação ao MessagePack é que ele está em conformidade com o IETF no RFC7049.