Considerazioni sulla codifica dei messaggi

Molte applicazioni cloud usano messaggi asincroni per scambiare informazioni tra i componenti del sistema. Un aspetto importante della messaggistica è il formato usato per codificare i dati del payload. Dopo aver scelto una tecnologia di messaggistica, il passaggio successivo consiste nel definire la modalità di codifica dei messaggi. Sono disponibili molte opzioni, ma la scelta corretta dipende dal caso d'uso.

Questo articolo descrive alcuni di questi aspetti.

Esigenze di scambio di messaggi

Uno scambio di messaggi tra un producer e un consumer richiede:

  • Una forma o struttura che definisce il payload del messaggio.
  • Un formato di codifica per rappresentare il payload.
  • Librerie di serializzazione per la lettura e la scrittura del payload codificato.

Il producer del messaggio definisce la forma del messaggio in base alla logica di business e alle informazioni da inviare ai consumer. Per strutturare la forma, dividere le informazioni in oggetti discreti o correlati (campi). Definire le caratteristiche dei valori per tali campi. Si consideri: qual è il tipo di dati più efficiente? Il payload avrà sempre determinati campi? Il payload conterrà un singolo record o un set ripetuto di valori?

Scegliere quindi un formato di codifica a seconda delle esigenze. Alcuni fattori includono la possibilità di creare dati altamente strutturati, se necessari, il tempo impiegato per codificare e trasferire il messaggio e la possibilità di analizzare il payload. A seconda del formato di codifica, scegliere una libreria di serializzazione ben supportata.

Un consumer del messaggio deve essere a conoscenza di tali decisioni per sapere come leggere i messaggi in ingresso.

Per trasferire i messaggi, il producer serializza il messaggio in un formato di codifica. All'estremità ricevente, il consumer deserializza il payload per usare i dati. In questo modo entrambe le entità condividono il modello e, purché la forma non cambi, la messaggistica continua senza problemi. Quando il contratto cambia, il formato di codifica deve essere in grado di gestire la modifica senza interrompere il consumer.

Alcuni formati di codifica, ad esempio JSON, sono descritti internamente, ovvero possono essere analizzati senza fare riferimento a uno schema. Tali formati, tuttavia, tendono a generare messaggi di dimensioni maggiori. Con altri formati, i dati potrebbero non essere analizzati tanto facilmente, ma i messaggi sono compatti. Questo articolo evidenzia alcuni fattori che consentono di scegliere un formato.

Considerazioni sul formato di codifica

Il formato di codifica definisce il modo in cui un set di dati strutturati viene rappresentato come byte. Il tipo di messaggio può influire sulla scelta del formato. I messaggi correlati alle transazioni aziendali conterranno probabilmente dati altamente strutturati. Deve inoltre essere possibile recuperarli successivamente per eventuali controlli. Per un flusso di eventi, potrebbe essere necessario leggere una sequenza di record il più rapidamente possibile e archiviarla per l'analisi statistica.

Ecco alcuni punti da valutare per la scelta di un formato di codifica.

Leggibilità da parte degli utenti

La codifica dei messaggi può essere suddivisa in formati basati su testo e binari.

Con la codifica basata su testo, il payload del messaggio è in testo normale e può quindi essere controllato da una persona senza usare librerie di codice. I formati leggibili dall'utente sono adatti per i dati di archiviazione. Inoltre, poiché il payload può essere letto da un utente, il debug e l'invio ai log dei formati basati su testo per la risoluzione degli errori risultano più facili.

Lo svantaggio è che il payload tende a essere più grande. Un formato comune basato su testo è JSON.

Crittografia

Se nei messaggi sono presenti dati sensibili, valutare se tali messaggi devono essere crittografati interamente, come descritto in questa guida sulla crittografia dei dati inattivi bus di servizio di Azure. In alternativa, se è necessario crittografare solo determinati campi e si preferisce ridurre i costi del cloud, è consigliabile usare una libreria come NServiceBus .

Dimensioni di codifica

Le dimensioni dei messaggi influiscono sulle prestazioni di I/O di rete. I formati binari sono più compatti rispetto ai formati basati su testo. I formati binari richiedono librerie di serializzazione/deserializzazione. Il payload non può essere letto a meno che non venga decodificato.

Usare un formato binario se si vuole ridurre il footprint sulla rete e trasferire i messaggi più velocemente. Questa categoria di formato è consigliata negli scenari in cui la larghezza di banda di archiviazione o di rete presenta un problema. Le opzioni per i formati binari includono Apache Avro, Google Protocol Buffers (protobuf), MessagePack e Concise Binary Object Representation (CBOR). I vantaggi e i svantaggi di questi formati sono descritti in Questa sezione.

Lo svantaggio è che il payload non è leggibile dall'utente. La maggior parte dei formati binari usa sistemi complessi spesso costosi da gestire. Inoltre, sono necessarie librerie specializzate per la decodifica, che potrebbero non essere supportate se si vogliono recuperare i dati di archiviazione.

Informazioni sul payload

Un payload del messaggio arriva come sequenza di byte. Per analizzare questa sequenza, il consumer deve avere accesso ai metadati che descrivono i campi dati nel payload. Esistono due approcci principali per l'archiviazione e la distribuzione dei metadati:

Metadati con tag. In alcuni linguaggi di codifica, in particolare JSON, i campi vengono contrassegnati con il tipo di dati e l'identificatore, all'interno del corpo del messaggio. Questi formati sono descritti internamente perché possono essere analizzati in un dizionario di valori senza fare riferimento a uno schema. Un modo in cui il consumer può comprendere i campi è eseguire una query per i valori previsti. Ad esempio, il producer invia un payload in JSON. Il consumer analizza il codice JSON in un dizionario e verifica l'esistenza dei campi per comprendere il payload. Un altro modo prevede che il consumer applichi un modello di dati condiviso dal producer. Ad esempio, se si usa un linguaggio tipizzato in modo statico, molte librerie di serializzazione JSON possono analizzare una stringa JSON in una classe tipizzata.

Schema. Uno schema definisce formalmente la struttura e i campi dati di un messaggio. In questo modello il producer e il consumer hanno un contratto tramite uno schema ben definito. Lo schema può definire i tipi di dati, i campi obbligatori/facoltativi, le informazioni sulla versione e la struttura del payload. Il producer invia il payload in base allo schema writer. Il consumer riceve il payload applicando uno schema reader. Il messaggio viene serializzato/deserializzato usando le librerie specifiche della codifica. Esistono due modi per distribuire gli schemi:

  • Archiviare lo schema come preambolo o intestazione nel messaggio, ma separato dal payload.

  • Archiviare lo schema esternamente.

Alcuni formati di codifica definiscono lo schema e usano strumenti che generano classi dallo schema. Il producer e il consumer usano tali classi e librerie per serializzare e deserializzare il payload. Le librerie forniscono anche controlli di compatibilità tra lo schema writer e reader. Sia protobuf che Apache Avro seguono questo approccio. La differenza principale sta nel fatto che protobuf prevede una definizione di schema indipendente dal linguaggio e Avro usa JSON compatto. Un'altra differenza riguarda il modo in cui entrambi i formati forniscono controlli di compatibilità tra gli schemi reader e writer.

È anche possibile archiviare lo schema esternamente in un registro schemi. Il messaggio contiene un riferimento allo schema e al payload. Il producer invia l'identificatore dello schema nel messaggio e il consumer recupera lo schema specificando tale identificatore da un archivio esterno. Entrambe le parti usano una libreria specifica del formato per la lettura e la scrittura di messaggi. Oltre ad archiviare lo schema, un Registro di sistema può fornire controlli di compatibilità per assicurarsi che il contratto tra producer e consumer non sia interrotto con l'evolversi dello schema.

Prima di scegliere un approccio, decidere cosa è più importante: le dimensioni dei dati di trasferimento o la possibilità di analizzare i dati archiviati in un secondo momento.

L'archiviazione dello schema insieme al payload genera dimensioni di codifica maggiori ed è preferibile per i messaggi intermittenti. Scegliere questo approccio se il trasferimento di blocchi di byte più piccoli è fondamentale o si prevede una sequenza di record. Il costo della gestione di un archivio schemi esterno può essere elevato.

Tuttavia, se la decodifica su richiesta del payload è più importante delle dimensioni, includere lo schema con il payload o l'approccio relativo ai metadati con tag garantisce la decodifica in seguito. Potrebbe esserci un aumento significativo delle dimensioni dei messaggi e potrebbe influire sul costo dell'archiviazione.

Controllo delle versioni dello schema

Quando cambiano i requisiti aziendali, è previsto che la forma cambi e lo schema si evolva. Il controllo delle versioni consente al producer di indicare gli aggiornamenti dello schema che potrebbero includere nuove funzionalità. Esistono due aspetti per il controllo delle versioni:

  • Il consumer deve essere a conoscenza delle modifiche.

    A tale scopo, il consumer può controllare tutti i campi per determinare se lo schema è stato modificato. In alternativa, il producer può pubblica un numero di versione dello schema con il messaggio. Quando lo schema si evolve, il producer incrementa la versione.

  • Le modifiche non devono interessare o interrompere la logica di business dei consumer.

    Si supponga di aggiungere un campo a uno schema esistente. Se i consumer che usano la nuova versione ottengono un payload in base alla versione precedente, la logica potrebbe interrompersi se non riescono a superare la mancanza del nuovo campo. Considerando il caso inverso, si supponga che un campo venga rimosso nel nuovo schema. I consumer che usano lo schema precedente potrebbero non essere in grado di leggere i dati.

    I formati di codifica come Avro offrono la possibilità di definire valori predefiniti. Nell'esempio precedente, se il campo viene aggiunto con un valore predefinito, il campo mancante verrà popolato con il valore predefinito. Altri formati come protobuf offrono funzionalità simili tramite i campi obbligatori e facoltativi.

Struttura del payload

Valutare il modo in cui i dati sono organizzati nel payload. Si tratta di una sequenza di record o di un singolo payload discreto? La struttura del payload può essere classificata in uno di questi modelli:

  • Matrice/dizionario/valore: definisce le voci che contengono valori in una matrice o in matrici multidimensionali. Le voci contengono coppie chiave-valore univoche. Può essere estesa per rappresentare le strutture complesse. Alcuni esempi includono JSON, Apache Avro e MessagePack.

    Questo layout è adatto se i messaggi vengono codificati singolarmente con schemi diversi. Se sono presenti più record, il payload può diventare eccessivamente ridondante fino a causare il software bloat del payload.

  • Dati tabulari: le informazioni sono suddivise in righe e colonne. Ogni colonna indica un campo o l'oggetto delle informazioni e ogni riga contiene valori per tali campi. Questo layout è efficiente per un set ripetuto di informazioni, ad esempio i dati delle serie temporali.

    CSV è uno dei formati basati su testo più semplici. Presenta i dati come una sequenza di record con un'intestazione comune. Per la codifica binaria, Apache Avro ha un preambolo simile a un'intestazione CSV, ma genera dimensioni di codifica compatte.

Supporto per le librerie

Valutare l'uso di formati noti rispetto a un modello proprietario.

I formati noti sono supportati tramite librerie universalmente supportate dalla community. Con i formati specializzati sono necessarie librerie specifiche. La logica di business potrebbe dover aggirare alcune delle scelte di progettazione delle API fornite dalle librerie.

Per il formato basato su schema, scegliere una libreria di codifica che esegue controlli di compatibilità tra lo schema reader e writer. Alcune librerie di codifica, ad esempio Apache Avro, prevedono che il consumer specifichi sia lo schema writer che lo schema reader prima di deserializzare il messaggio. Questo controllo garantisce che il consumer sia a conoscenza delle versioni dello schema.

Interoperabilità

La scelta dei formati può dipendere dal carico di lavoro o dall'ecosistema tecnologico specifico.

Ad esempio:

  • Analisi di flusso di Azure include il supporto nativo per JSON, CSV e Avro. Quando si usa Analisi di flusso, è opportuno scegliere uno di questi formati, se possibile. In caso contrario, è possibile fornire un deserializzatore personalizzato, ma questa alternativa aggiunge ulteriore complessità alla soluzione.

  • JSON è un formato di interscambio standard per le API REST HTTP. Se l'applicazione riceve payload JSON dai client e le inserisce in una coda di messaggi per l'elaborazione asincrona, potrebbe essere opportuno usare JSON per la messaggistica, anziché ricodicerla in un formato diverso.

Questi sono solo due esempi di considerazioni sull'interoperabilità. In generale, i formati standardizzati saranno più interoperabili rispetto ai formati personalizzati. Nelle opzioni basate su testo, JSON è una di quelle che garantisce la maggiore interoperabilità.

Scelte per i formati di codifica

Ecco alcuni formati di codifica comuni. Valutare le considerazioni prima di scegliere un formato.

JSON

JSON è uno standard aperto (IETF RFC8259). Si tratta di un formato basato su testo che segue il modello matrice/dizionario/valore.

È possibile usare il formato JSON per contrassegnare i metadati e analizzare il payload senza uno schema. JSON supporta l'opzione per specificare campi facoltativi, facilitando così la compatibilità con le versioni precedenti e successive.

Il vantaggio principale è che è disponibile a livello universale. Si tratta del formato di codifica predefinito e più interoperabile per molti servizi di messaggistica.

Essendo un formato basato su testo, non è efficiente sulla rete e non è una scelta ideale in caso di problemi relativi allo spazio di archiviazione. Se, tuttavia, vengono restituiti elementi memorizzati nella cache direttamente in un client tramite HTTP, l'archiviazione del codice JSON può risparmiare il costo della deserializzazione da un altro formato e la successiva serializzazione in JSON.

Usare JSON per i messaggi con un record singolo o per una sequenza di messaggi in cui ogni messaggio ha uno schema diverso. Evitare di usare JSON per una sequenza di record, ad esempio per i dati delle serie temporali.

Esistono altre varianti di JSON, ad esempio JSON binario (BSON), ovvero una codifica binaria allineata per l'uso con MongoDB.

Valori delimitati da virgole (CSV)

CSV è un formato tabulare basato su testo. L'intestazione della tabella indica i campi. Si tratta della scelta preferita quando il messaggio contiene un set di record.

Lo svantaggio sta nella mancanza di standardizzazione. Esistono molti modi per esprimere separatori, intestazioni e campi vuoti.

Protocol Buffers (protobuf)

Protocol Buffers (o protobuf) è un formato di serializzazione che usa file di definizione fortemente tipizzato per definire schemi in coppie chiave/valore. Questi file di definizione vengono quindi compilati in classi specifiche del linguaggio usate per la serializzazione e deserializzazione dei messaggi.

Il messaggio contiene un payload binario compresso di piccole dimensioni che garantisce un trasferimento più rapido. Lo svantaggio è che il payload non è leggibile dall'utente. Inoltre, poiché lo schema è esterno, non è consigliato nei casi in cui è necessario recuperare i dati archiviati.

Apache Avro

Apache Avro è un formato di serializzazione binario che usa un file di definizione simile a protobuf, ma non prevede un passaggio di compilazione. I dati serializzati includono sempre un preambolo dello schema.

Il preambolo può contenere l'intestazione o un identificatore dello schema. Grazie a dimensioni di codifica inferiori, Avro è consigliato per lo streaming dei dati. Inoltre, poiché ha un'intestazione che si applica a un set di record, è una scelta ottimale per i dati tabulari.

MessagePack

MessagePack è un formato di serializzazione binaria progettato per essere compatto nella trasmissione in rete. Non sono presenti schemi di messaggio o controlli dei tipi di messaggio. Questo formato non è consigliato per l'archiviazione in blocco.

CBOR

La specifica Concise Binary Object Representation (CBOR) è un formato binario che offre dimensioni di codifica ridotte. Il vantaggio di CBOR rispetto a MessagePack è che è conforme allo standard IETF RFC7049.

Passaggi successivi