Bitrock logo

Polymorphic Messages in Kafka Streams

Di solito le cose iniziano in modo semplice…

Si sta progettando un’applicazione Kafka Streams che deve leggere i comandi e produrre l’evento aziendale corrispondente.

I modelli Avro che ci si aspetta di leggere hanno questo aspetto:

Mentre i messaggi di output che si devono produrre hanno l’aspetto seguente:

Si sa che si può sfruttare il plugin sbt-avrohugger per generare la classe Scala corrispondente per ogni schema Avro, in modo da potersi concentrare solo sulla progettazione della logica aziendale.

Poiché i messaggi in sé sono piuttosto semplici, si decide di creare una funzione monomorfa per mappare le proprietà tra ogni comando e l’evento corrispondente.

La topologia risultante ha l’aspetto seguente:

…Ma poi il dominio si amplia

Oggi sono emersi nuovi requisiti funzionali: la vostra applicazione deve ora gestire più tipi di risorse, ognuna con le sue proprietà uniche.

State pensando a come implementare questo requisito e rendere la vostra applicazione più resistente a ulteriori cambiamenti di comportamento?

Flussi multipli

Potreste suddividere sia i comandi che gli eventi in più argomenti, uno per tipo di asset, in modo da mantenere lo schema Avro corrispondente e garantirne la compatibilità.

Questa soluzione, tuttavia, comporta la replica di quasi tutta la stessa topologia più volte, quindi non è consigliata a meno che la logica aziendale non debba essere personalizzata per ogni tipo di risorsa.

Messaggi “Tutti o nessuno”

Avro non supporta l’ereditarietà tra i record, quindi qualsiasi strategia OOP per far sì che gli asset ereditino le proprietà da un antenato comune non è purtroppo praticabile.

Si potrebbe però creare un oggetto “Frankenstein” con tutte le proprietà di ogni singolo asset e compilare solo quelle necessarie per ogni tipo di asset.

Questa è sicuramente la soluzione peggiore dal punto di vista evolutivo e della manutenibilità.

Tipi di unione

Fortunatamente, Avro offre un’interessante funzionalità denominata union types: è possibile esprimere la diversità delle proprietà di ogni asset tramite un’unione di più payload, sempre basandosi su un unico messaggio come wrapper.

Inserire flussi polimorfi

Oggetti senza forma

Per far fronte a questo polimorfismo avanzato, si sfrutta la libreria shapeless, che introduce il tipo Coproduct, compagno perfetto del tipo Avro union.

Innanzitutto, si aggiorna la mappatura dei tipi personalizzati di sbt-avrohugger, in modo che generi un tratto sigillato aggiuntivo per ogni protocollo Avro contenente più record:

La classe di comando generata ha un aspetto simile a questo:

Aggiornamento della logica di business

Grazie al tratto Poly1 di Shapeless, è possibile scrivere la logica aziendale aggiornata in un’unica classe:

Le modifiche alla topologia sono minime, come ci si aspetterebbe:

Un tipo speciale di Serde

Ora il pezzo finale del puzzle, i Serde. Vi presentiamo la libreria avro4s, che porta Avro GenericRecords oltre i limiti.

Si crea una classe di tipo per estendere un vecchio Serde, fornendo un metodo nuovo di zecca:

Ora ogni classe generata ha il suo Serde, personalizzato sullo schema Avro corrispondente.

Mettere tutto insieme

Infine, il programma principale in cui si combinano tutti gli ingredienti:

Conclusioni

Quando più casi d’uso condividono (quasi) la stessa logica di business, è possibile creare un’applicazione di elaborazione dei flussi con un polimorfismo ad hoc e ridurre al minimo la duplicazione del codice, rendendo l’applicazione ancora più a prova di futuro.

Skip to content