Come abbiamo introdotto Haskell in Soisy

Come sapete, siamo una startup che opera nel settore FinTech con un prodotto per il pagamento rateale su e-commerce convenzionati.

Alcuni mesi fa è nata in azienda l’esigenza di sviluppare una nuova versione del nostro motore di rischio, quindi abbiamo iniziato a ragionare su come realizzarlo.

Alla fine abbiamo deciso di utilizzare Haskell. In questo post tenteremo di riassumere il percorso che ci ha portato a fare questa scelta, dalla situazione iniziali e le perplessità riguardo l’uso di una nuova tecnologia, all’effettivo rilascio in produzione; sul blog del nostro Marco trovi la versione in inglese di questo blog post.

Un po’ di contesto, cos’è il nostro motore di rischio?

Come abbiamo già detto, siamo un metodo di pagamento a rate. Come ogni servizio che opera nel mercato del Buy Now Pay Later, dobbiamo proteggerci dalle possibili insolvenze dei nostri clienti.

Lo strumento principale che abbiamo per farlo è il nostro motore di rischio, che in pratica processa i dati forniti dai nostri clienti per decidere se concedere oppure no un finanziamento.

In estrema sintesi, il nostro motore di rischio è un servizio stateless che riceve un po’ di dati, effettua vari calcoli apparentemente misteriosi e infine dice ‘Sì’ oppure ‘No’ a seconda che il finanziamento debba essere approvato o meno.

Il nostro stack tecnologico di default

Soisy, da un punto di vista tecnologico, è nata e si è evoluta fino ad adesso come un’applicazione monolitica in PHP. Usiamo un’architettura basata su Event Sourcing e CQRS e cerchiamo di mantenere una qualità del nostro codice il più alta possibile, grazie anche alla grande esperienza di tutto il nostro team tecnico.

Nonostante ciò, con il passare del tempo, un’applicazione monolitica in PHP comincia ad essere troppo stretta per le nostre necessità attuali e il nostro tasso di crescita.

Inoltre, con la crescita del nostro team, stiamo internalizzando esperienze e capacità che esulano dal nostro stack tecnologico principale, con competenze che vanno dalla programmazione funzionale a sistemi distribuiti a infrastructure-as-code.

La proposta

Tenendo conto del contesto descritto al paragrafo precedente, quando è giunto il momento di iniziare lo sviluppo del nuovo motore di rischio, uno dei nostri ingegneri ha fatto la seguente proposta nel nostro Slack aziendale:

dovremmo farlo in Haskell

Le principali motivazioni per la proposta erano:

  • Haskell ci consente di modellare il nostro dominio in maniera più chiara e più semplice. L’utilizzo di algebraic data types e funzioni invece di classi, oggetti e metodi permette di creare un modello di dominio più robusto;
  • Haskell ci consente di dimenticare all’atto pratico alcune problematiche quali la serializzazione e la deserializzazione grazie a generic programming;
  • il type system di Haskell consente un’estrema agilità e confidenza in fase di refactoring complicati. Cambiare il design dell’applicazione è possibile, e spesso facile, grazie all’aiuto del compilatore;
  • l’ecosistema Haskell, almeno per quelle che sono le nostre esigenze attuali, contiene librerie di alta qualità e la community è generalmente molto accogliente e pronta ad aiutare;
  • le performance e la velocità di esecuzione di un’applicazione Haskell sono di un altro livello rispetto ad un linguaggio interpretato come PHP.

Le perplessità

La proposta è stata ricevuta dal team in maniera varia, alcune persone ne erano entusiaste, mentre altre erano più perplesse.

Le domande e le osservazioni che sono state sollevate sono:

  • quanto è indipendente il motore di rischio dal resto dell’applicazione? Possiamo separarlo facilmente dal resto dell’applicazione?
  • se vogliamo provare Haskell, dobbiamo provarlo come un test, con la coscienza che come tale potrebbe fallire;
  • dobbiamo considerare l’impatto che una decisione del genere ha rispetto ai tempi di realizzazione;
  • ogni cosa nuova che va in produzione è una possibile causa di nuovi bug ed emergenze. Che strategie possiamo adottare per limitare tali evenienze in questo caso?
  • solo una piccola parte del team conosce il linguaggio e sarebbe capace di evolvere la nuova applicazione. Non rischiamo la creazione di un collo di bottiglia?

Il team che si è formato per affrontare la nuova implementazione del motore di rischio ha preso in considerazione tutte le perplessità che sono state avanzate e ha deciso di accettare i rischi che derivano dall’introduzione di una nuova tecnologia, giudicando che i benefici sarebbero stati maggiori rispetto agli svantaggi. Nello specifico, alcune considerazioni rilevanti sono state:

  • per quanto riguarda i tempi di rilascio, tutto il team ha deciso di dare la priorità all’affidabilità e alla qualità della soluzione rispetto alla velocità di rilascio;
  • per quanto riguarda i bug e le emergenze, è stato deciso di far girare la nuova versione in parallelo alla vecchia prima del rilascio effettivo, per prevenire brutte sorprese quando si fosse andati live;
  • è stato deciso di fare più pair programming possibile, in modo da diffondere la conoscenza sia riguardo Haskell, sia riguardo il dominio del motore di rischio;
  • inoltre, è stato deciso di dedicare tempo alla formazione del team tecnico su Haskell a rilascio effettuato.

L’azienda ha avuto fiducia nelle considerazioni del team e così l’avventura ha avuto inizio 🚀

Alcune decisioni tecniche

Con davanti il compito di realizzare un progetto greenfield in Haskell, abbiamo avuto la possibilità di valutare tutto lo stack tecnico per il nostro progetto. Per la maggior parte, abbiamo scelto le cose che conoscevamo meglio, in modo da poter trarre vantaggio dalle nostre esperienze precedenti.

Qui sotto andiamo più nel dettaglio su alcune scelte rilevanti che abbiamo fatto.

(Almost) boring Haskell

Un membro del team era alla sua prima esperienza con Haskell, quindi abbiamo deciso di non seppellirlo da subito sotto troppi concetti astratti.

La maggior parte delle volte abbiamo preferito evitare astrazioni che non aiutavano la leggibilità del codice. Abbiamo spesso preferito utilizzare tipi concreti invece di usare type variables e type classes, approccio che avrebbe reso il codice più generico.

Tutto il servizio è stateless e puro per sua natura, quindi abbiamo potuto evitare di usare monadi, monad transformers o un qualunque altro sistema per la gestione esplicita degli effetti, a parte un minimo utilizzo di IO a bordi dell’applicazione.

L’unica concessione che abbiamo deciso di fare a concetti un po’ più avanzati è stato l’uso delle lenti, che rende la gestione di strutture dati annidate più semplice. Abbiamo comunque preferito evitare gli operatori infix e abbiamo utilizzato invece la forma esplicita delle funzioni come `view` e `set`.

Stack

Abbiamo scelto Stack per la gestione della fase di build e delle dipendenze. La scelta è stata dovuta principalmente alla nostra esperienza pregressa, e siamo molto felici della scelta in quanto non abbiamo per il momento mai avuto nessun problema legato alle dipendenze o alla fase di build.

Servant

Data la nostra esigenza di creare un servizio web con un numero limitato di endpoint ragionevolmente semplici, da far girare su una rete privata, nella quale l’autenticazione non è, almeno per il momento, un problema, abbiamo deciso di utilizzare Servant. Questo per trarre vantaggio dalla sua grande sicurezza a livello di tipi e per l’ottima espressività. Ci piace in particolare l’abilità di generare una documentazione degli endpoint delle nostre API sempre aggiornata, cosa che torna molto utile in un team dove non tutti leggo fluentemente il codice Haskell.

Dhall

Il motore di rischio è composto da un certo numero di regole che sono configurate da svariati parametri. Essere capaci di manovrarli direttamente in un file di configurazione rende tutta l’applicazione più mantenibile ed evolvibile.

L’uso di Dhall come linguaggio di configurazione ci ha permesso di aumentare la nostra abilità di rimodellare la struttura della configurazione, mantenendola facilmente allineata con quello che il codice si aspetta.

QuickCheck

Anche quando usiamo PHP ci sforziamo di scrivere i nostri test utilizzando property based testing. Farlo in Haskell è molto più piacevole in quanto il linguaggio si presta molto meglio ad un tale approccio.

Abbiamo testato la nostra applicazione a fondo, sia da un punto di vista unitario sia funzionale. Una delle belle cose di Haskell è che i test girano molto più veloci rispetto a PHP. Gli stessi test che per la versione precedente giravano per circa 10 minuti, con Haskell stanno in genere meno di 2 secondi.

Il risultato

Adesso sono ormai tre settimane che abbiamo rilasciato il nuovo motore in produzione e, almeno finora, tutto è andato liscio.

La realizzazione del progetto è durata più tempo di quanto ci aspettavamo all’inizio, ma la maggior parte del tempo aggiuntivo è stata dedicata all’integrazione del nuovo sistema con il monolite PHP e non a causa di problemi legati ad Haskell.

Per il momento, anche se il progetto sta girando in produzione, la nostra adozione di Haskell rimane comunque un esperimento. Con il passare del tempo, valutando sia come l’applicazione si sta comportando sia come il costo di manutenzione influenza tutto il team, chiuderemo l’esperimento e decideremo se includere Haskell definitivamente nel nostro stack oppure se ritornare al nostro beneamato PHP.

Articolo precedente

Articolo successivo

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *

Condividi

Contattaci

supporto@soisy.it

Disclaimer

Con nessuno dei nostri articoli offriamo consulenza finanziaria: i dati e le analisi contenuti negli articoli del blog sono a scopo informativo e non costituiscono la consulenza di un esperto.

Voglio saperne di più