Risolvo problemi software e ottimizzo architetture : Architetture
Mia foto

Iniziamo a programmare con Node.js

Un professionista che si rispetti deve avere il coraggio di "lasciare la strada vecchia per la nuova", quest'anno ho deciso di studiare tutto quello che gravita attorno allo stack MEAN e al mondo dei BigData, provando a spiegare le mie scelta ai lettori di baccan.it

Iniziamo a programmare con Node.js
Premessa
Ho passato gli ultimi 28 anni della mia vita a programmare, quasi giornalmente, tutto quello che mi capitava sotto mano.
Ho scritto codice in molti linguaggi, partendo da ASM ed arrivando a Scratch e ho avuto la fortuna di lavorare sia su progetti piccoli, dove è importante avere sotto controllo tutto il processo, sia su lavori molto grandi, dove quello che fai è solo una piccola frazione di qualcosa di molto articolato.

Tralasciando tutto il codice scritto prima del 2010, I linguaggi ai quali ho dedicato la maggior parte del tempo sono sicuramente PHP e Java. Giusto per farvi capire cosa intendo, provo a fare un veloce elenco delle prime cose che mi vengono in mente:

* Motori di ricerca
* Linguaggi di programmazione
* Crawler di vario genere
* Server di servizi, sia REST che con protocolli proprietari
* Applicazioni Web su vari application server Java
* Utility da riga comando
* Vari demoni: POP3, SMTP, NNTP, DNS, etc.
* Il CMS che utilizzo per baccan.it ed per altri 30 siti: clienti, amici e onlus (chiaramente gratis)

Dopo tanto codice, ho iniziato a notare un principio di pigrizia nel mio lavoro e soprattutto notavo che il mercato Java iniziava a diventare poco stimolante. In molti progetti si passava più tempo a lavorare sull'architettura che a risolvere i problemi dei clienti.
Credo che un prodotto debba, prima di tutto: risolvere problemi e poi aderire all'architettura perfetta. Ai clienti interessa che tutto funzioni bene e sempre, non che si usi l'ultima versione di un certo framework o l'ultimo database uscito.

Vedevo che, attorno a me, molti programmatori usavano nuovi stack architetturali per la realizzazione di nuovi progetti. Stack che sulla carta sembravano essere molto efficienti, soprattutto rispetto a certe architetture Java, ormai troppo complesse da programmare per il reale valore aggiunto che fornivano.

Per svecchiare le mie conoscenze, dato che ormai mi ero assestato sullo stack LAMP/WAMP/Java, ed aprirmi a nuovi mercati, ho deciso di dedicare parte del mio studio all'approfondimento di qualcosa di nuovo.

Quello che vorrei descrivere in questo articolo, sono i primi giorni di studio ed i meccanismi che mi hanno spinto a fare, o non fare, certe scelte, sperando di poter aiutare altri programmatori che si trovano nella mia stessa situazione e cercano degli stimoli per "lasciare la strada vecchia per la nuova".

Cosa scegliere?
E' proprio vero che più studi e meno sai. Più provavo ad informarmi e più capivo che gli argomenti che potevo approfondire erano infiniti. Per riuscire ad uscire da questa situazione ho provato a fare un elenco degli ambiti nei quali non mi sentivo molto "forte":

* Sviluppo di App: al momento non ho mai realizzato un'App. Un po' mi vergogno di questa cosa, ma nella mia testa un'App è solo il punto finale di un progetto e non il punto iniziale.
Va bene se è chiara la business logic e se l'App è l'anello finale di una serie di processi, altrimenti c'è il rischio di mettere tutto nell'App, per poi perdere il controllo del proprio prodotto, rendendone difficile un porting o un'espansione.
Oltre a questo iniziano ad aumentare gli strumenti in grado di realizzare App in modo trasversale: IOS, Android, Windows Phone, aspetto che aumenta la confusione che ho in testa e la mia fissazione nel trovare qualcosa che faccia "quasi" tutto e non mi costringa a passare in troppi ambienti diversi, rendendomi poco produttivo.
Infine, anche se mi venisse voglia di sviluppare un'App, negli ultimi 12 mesi ho avuto una sola richiesta da parte dei miei clienti, e per giunta con un budget molto ridotto, ammazzando definitivamente la mia voglia di studiare questi argomenti.

* BigData: il mondo dei database mi ha sempre affascinato. Le mie prime applicazioni nascevano nel mondo del gestionale, dove l'uso dei database era qualcosa di necessario, così come la gestione di milioni di record e la loro consistenza.
Anche in questo caso però, i miei clienti sono ancora molto legati ad architetture più che consolidate e a database con i quali parlano da decenni. Si tratta principalmente di Oracle, Microsoft SQL Server e MySQL. Fuori da questo trittico, appaiono ogni tanto delle meteore come Teradata, Access o database di cui preferirei non parlare.
Portare dei prodotti "BigData" in questo tipo di ambienti, a meno che il progetto non parta da zero, diventa un'impresa quasi titanica.

* Architetture Javascript: ovunque ho trovato del lavoro mi sono scontrato con l'uso di Javascript. Ricordo con affetto i primi esperimenti, quando ancora il linguaggio era utilizzabile solo all'interno di Netscape.
Oggi, la realizzazione di pagine web dinamiche, client-side, viene fatta quasi esclusivamente con questo linguaggio: efficace e rozzo allo stesso tempo.
Con la proliferazione di librerie Javascript, ma soprattutto col rilascio in opensource del motore Javascript di Chrome ed il suo utilizzo all'interno di Node.js, il mondo è sicuramente cambiato. Javascript è diventato uno strumento utilizzabile anche server-side, con grande gioia di coloro che volevano riutilizzare quanto sviluppato lato browser.

Alla luce di queste considerazioni, ho deciso di iniziare a studiare un nuovo stack basato su Javascript, che mi permettesse di usare le conoscenze di linguaggio che avevo, mettere un piede all'interno di un nuovo tipo di application server e introdurre, all'occorrenza, un database BigData.
La scelta è quindi ricaduta su MEAN: MongoDB, Express, AngularJS e Node.js.

Inizia l'avventura
Sarà il caso, ma nel momento in cui ho deciso di studiare MEAN è uscito un aggiornamento di NetBeans, l'IDE che utilizzo normalmente per programmare, nel quale veniva supportato nativamente Node.js: wizard per la creazione di progetti, supporto nativo per NPM, debug di applicazioni e tanto altro.
Era un chiaro segno che qualcuno mi stava spingendo in questa direzione.

Serviva però anche un progetto nel quale poter introdurre questa architettura, senza che ci fosse una ritrosia da parte del cliente nell'affrontare una strada nuova e sopratutto non troppo grande, in modo da poter tornare sulla vecchia strada in caso di problemi.

Fortunatamente, nel giro di poco tempo, l'occasione è arrivata: dovevo realizzare un'architettura per la logistica, in grado di fornire servizi ad applicazioni scritte con linguaggi e piattaforme eterogenee.
Quest'architettura doveva maneggiare dati locali e remoti in modo asincrono, mischiarli fra di loro ed inviare elaborazioni ad alcuni server centrale.
Nulla di particolarmente complicato, ma sicuramente l'applicazione ideale per iniziare a sporcarsi le mani col nuovo stack.

Il problema maggiore in questo progetto era dato dal fatto che, solo una parte delle specifiche erano certe. Altre, come il tipo di device che avrebbe lavorato sull'applicazione, erano decisamente incerte.

Mi sentivo quindi sicuro nella realizzazione del backend, ma a livello di frontend il rischio di buttare via tutto era molto alto. Per questa ragione ho iniziato a progettare i servizi che dovevano essere condivisi fra le applicazioni, prima di pensare alla GUI.

Quali strumenti
Serviva quindi iniziare a pensare ad un'architettura di massima, per la parte di backend.

A livello di IDE avevo già fatto la mia scelta, avrei continuato ad utilizzare NetBeans: uno strumento che conoscevo, gratuito ed estremamente stabile.

Anche a livello di application server la scelta era fatta: Node.js.

Come web framework, per aderire allo stack MEAN, la scelta diventava quasi obbligatoria nei confronti di express.

Anche a livello di database c'era poca scelta: Oracle e Microsoft SQL Server.

Dovevo ora selezionare gli strumenti che potevano accelerarmi il lavoro. e forte di quanto realizzato in altre applicazioni, ho provato a fare un primo elenco di quanto ritenevo necessario per la scrittura del codice.

* Logger: per evitare di perdersi nel codice, quando c'è un errore, e soprattutto per capire cosa fa l'applicazione una volta installata dal proprio cliente, è necessario dotarsi di un motore di log che sia efficiente, facile da usare, ed estendibile. La mia scelta è caduta su Log4JS, nella sua versione per Node.js.

* Driver database. Dovevo interfacciare due diversi database di clienti: Microsoft SQL Server e Oracle. Mentre per il primo caso non ci sono stati grossi dubbi, node-mssql, per Oracle ho avuto delle titubanze, dovute principalmente ai prerequisiti necessario alla sua installazione del driver ufficiale.
Speravo di trovare qualcosa di pronto e installabile: purtroppo non ci sono riuscito e ho dovuto utilizzare la strada consigliata da Oracle stessa, che obbliga a installare Visual Studio, e a ricompilare oracledb. Solo per questa attività ho perso 4 ore di vita, fra download, problemi, test e compilazione. Il risultato finale è stata una libreria di circa 500k. Non vi descrivo cosa ho pensato al termine delle 4 ore :)

* Funzioni javascript aggiuntive. Esistono molti framework javascript che possono aiutare a scrivere codice più velocemente. Per mia esperienza so che è ricorrente utilizzare strutture in grado di contenere dati: mappe, array, etc e manipolare stringhe.
Per questo ho cercato qualcosa che potesse aiutarmi in questi aspetti e ho scelto, almeno per questo primo progetto, alcuni framework di supporto molto interessanti: underscore, underscore.string, MD5 e node-cache

Avevo quindi plasmato uno stack che partiva da MEAN ed arrivava ad abbracciare le specifiche di progetto.
Non avendo ancora pianificato come realizzare il frontend, AngularJS era ancora uno dei possibili candidati. L'unico dubbio era il suo corretto funzionamento all'interno di device Windows CE 5.

Fissato il primo set di strumenti, potevo iniziare a scrivere il programma.

Da dove partire?
In testa avevo una prima architettura che volevo rispettare, non sapevo quanto fosse corretta e probabilmente quando riguarderò il codice fra qualche mese, mi verrà da sorridere, ma da qualche parte occorre pur iniziare.

I paradigmi sui quali volevo lavorare erano

* Semplicità di gestione: quindi nessun mega-sorgente con dentro tutto
* Evoluzione software "by design": volevo realizzare dei servizi che avessero un certo grado di retro-compatibilità, ma anche la possibilità di passare ad una nuova versione, anche non retro-compatibile, con estrema facilità
* Utilizzo della piattaforma tramite autenticazione applicativa
* Utilizzo dei log i maniera trasversale in modo da segnalare ogni anomalia e capire, anche prima del cliente, se ci fossero della problematiche applicative

Le prime righe che ho scritto sono state quindi quelle del logger: la soddisfazione di poter vedere un "hello world" in un ambiente nuovo è qualcosa di indescrivibile.


var logger = require('log4js').getLogger("Main"); 
logger.debug("Hello world");



Il passo successivo era quello di istanziare Express


var express = require('express');
var app = express();                   



Da quanto avevo letto, Express consiglia la creazione di sub-applicazioni all'interno di script diversi, in modo da evitare il classico "sorgente lenzuolo". Ho lavorato quindi in questa direzione, creando singoli programmi che potessero contenere i router dei sotto servizi presenti nell'applicazione.
Più i servizi crescevano, più il numero di script separati sarebbero aumentati, mantenendo un certo livello di leggibilità del codice.

Ho anche creato un servizio minimale di autenticazione, da utilizzare nelle fasi di test, che non facesse uso di database, e creasse in memoria l'elenco degli utenti abilitati.
Il meccanismo si basa su oggetti di memory-cache e su un servizio /auth/login, in grado di ricevere l'utente in POST e di validarlo.
Mi rendo conto che il sistema, scritto così, non è affatto sicuro, ma lo scopo di questo servizio era quello di impostare un meccanismo di validazione interno, che creasse una cache in ram, contenente l'elenco delle sessioni attive che, per disegno applicativo, non erano molte.


var express = require('express');
var router = express.Router();
var cache = require('memory-cache');
var md5 = require('md5');

router.post('/auth/login', function (req, res) {
    var user = req.body.user;
    var password = req.body.password;

    var ret = {};
    // Se i dati sono consistenti 
    if (user !== undefined && user.lenght !== 0 && password !== undefined && password.length !== 0) {
        // Se login e pwd sono corretti
        if (user === "login" && password === "password") {
            // Creo un token con l'attuale timestamp
            var token = md5(new Date().getMilliseconds().toString());
            cache.put( token ,"ok");
            ret = {token: token };
        }
    }
    res.json(ret);
});

module.exports = router;



Il token così creato dal servizio di /auth/login veniva poi riutilizzato all'atto dell'invocazione dei vari sottoservizi.
In questo caso, ho trovato utile l'utilizzo del route GET, in grado frapporsi fra le chiamate applicative e i metodi invocati:


// Aggiungo l'autenticazione
app.use("/v1/barcode", function (req, res, next) {
    var token = req.query.token;
    var session = null;

    // Provo a prendere il token
    if (token !== undefined && token.length !== 0) {
        var cache = require('memory-cache');
        session = cache.get(token);
        logger.debug("Sessione: " + session);
    }

    // Se ho la sessione in ram
    if (session !== null) {
        next(); // Passo i dati al metodo
    } else {
        res.json({message: 'Autentication required'});
    }
});



A livello di gestione bastava quindi invocare Il servizio di login, prendere il token restituito e passarlo ad ogni chiamata, usando il seguente schema:

http://server/v1/servizio?token=XXXXXXXXXXXX

in modo che app.use estragga il token e lo utilizzi per verificare la consistenza in memoria dell'autenticazione.

Ammetto che l'ispirazione per questo tipo di meccanismo l'ho avuta da un prodotto usato di recente (zipbooks), ma mi sembrava abbastanza semplice da poter essere facilmente reimplementato.

A valle di questo ho aggiunto i servizi di gestione prodotti tramite barcode:


// Barcode
var barcoderouter = require("./router/barcode");
app.use('/v1', barcoderouter);



all'interno dei quali ho inserito i metodi in grado di interrogare database Oracle, sia in lettura che in scrittura.

Conclusioni
Quello che avete letto fino ad ora sono i primi due giorni di lavoro sul nuovo stack, con le mie riflessioni e le mie scelte applicative.
Tutto sommato, impostare un progetto con Node.js, Express e OracleDB è abbastanza semplice, l'unica lungaggine è data dalla creazione del driver Oracle.
Nelle prossime settimane inizierò ad aggiungere nuovi servizi e parti di frontend, utilizzando sia applicazioni WEB che "fat client Java".
Vi terrò aggiornati sui miei lavori e sulle scelte che farò, sperando che le mie riflessioni possano essere d'aiuto ad altri programmatori che si trovano nella stessa situazione.



2 commenti  Aggiungi il tuo

Vedi il profilo di alessandro ciao
alessandro
11 Gennaio 2016 - 11:09
 
Ciao Matteo,

articolo interessante, benvenuto nel pazzo mondo di JavaScript server-side :)
Ho notato che hai preso ispirazione da zipbook per la creazione del token.
Un approccio interessante potrebbe essere quello di usare JSON Web Token (https://github.com/auth0/node-jsonwebtoken). come fanno anche loro.
In questo modo non dovresti preoocuparti di gestire i token in cache o in sessione in quanto potresti verificarli per ogni request utilizzando un apposito middleware.
Vedi il profilo di Matteo Baccan JSON Web Token
Matteo Baccan
11 Gennaio 2016 - 11:52
 
Ciao Ale

avevo visto dei talk di Carlo Bonamico sull'argomento qualche mese fa.

Come hai notato ho separato la parte di autenticazione dall'implementazione dei servizi, perché sapevo che c'era del lavoro da fare e al momento non avevo ancora specifiche precise.

Metto in lista anche questo fra le estensioni di progetto, così, una volta chiarita l'autenticazione, vedo se si riesce ad utilizzare questo approccio.

Fai conto che la natura eterogenea dei client non mi lascia molta libertà, temo infatti di non poter usare javascript lato web-client, ma sulle componenti server non ho particolari vincoli, quindi una comunicazione server-server potrebbe tranquillamente usare JSON Web T.oken.

ciao
matteo



Per commentare occorre essere un utente iscritto
Iscriviti alla newsletter
Architetture
07/07/2015 - Come si realizza l'architettura di un sito web scalabile?
×
Ricevi gratuitamente i nostri aggiornamenti