questa settimana è arrivato un nuovo tassello nel mosaico dell'applicazione Java che il mio team sta sviluppando in questo periodo. è ormai in code complete la user-story che ho preso in carico, che si occupa di realizzare l'aggancio ad una coda, realizzata da sviluppatori di un altro team interno, e di supportare il formato dei dati concordato con loro. dato che per me rappresentava la possibilità di fare esperienza con una tecnologia che conosco ancora poco, lo standard Java Message Service (JMS), mi sono subito proposto, a inizio iterazione, per prendere io la carta.
la nostra parte di applicazione è particolare rispetto a quanto stanno sviluppando gli altri team sul progetto, dato che si tratta di un motore di regole per elaborare dati provenienti da diverse sorgenti, e notificare via posta elettronica agli utenti gli eventi che corrispondo ai criteri da loro scelti. è un dominio molto ricco, in cui la parte di infrastruttura è secondaria.
il porre in secondo piano l'aspetto di infrastruttura è stata, oltre che una scelta, un'esigenza dettata da requisiti non funzionali ancora in parte incompleti: quali sorgenti dati usare, quali standard usare, o in alcuni casi anche "chi" realizzerà determinati sistemi esterni. per i servizi offerti invece dagli altri team interni, volevamo renderci quanto più indipendenti dalla loro pianificazione, riducendo cioè al minimo i vincoli temporali inter-team.
queste condizioni hanno quindi favorito la nascita, in modo incrementale, di un'architettura in cui al centro si pone un Domain Model indipendente, slegato quanto più possibile dagli aspetti di infrastruttura: ogni volta che una storia richiedeva l'accesso ad un servizio (anche se non ancora definito) aggiungevamo nel dominio un'interfaccia per disaccoppiare i due sistemi - un provider, broker o sender - nell'ottica di realizzare in seguito un adattatore al servizio, quando questo fosse stato pronto. si tratta dell'architettura esagonale descritta da Cockburn.
per poter disaccoppiare da tutto il cuore del sistema, ad un certo punto abbiamo trovato naturale applicare il principio di inversione delle dipendenze (Dependency-Inversion Principle), in modo che la logica di alto livello (domain) fosse slegata dai dettagli di implementazione (adapters e infrastructure) e che questi dettagli dipendessero invece da un'astrazione (le interfacce, dichiarate all'interno del domain). rispetto ad uno schema di layering canonico, in cui il livello applicativo dipende da quello di infrastruttura, abbiamo ottenuto una struttura in cui il layer applicativo non dipende da nulla, ma sono invece UI e infrastruttura a dipendere dallo strato applicativo.
la forza che ha guidato l'emergere di questa struttura è stata, nuovamente, la necessità di rendere il sistema testabile in isolamento. la realizzazione di ciascuna storia è stata così guidata da una ricca suite di test:
- test unitari delle classi di dominio e del motore di regole, usando test-doubles (fake, stub o mock) delle interfacce dei servizi
- test unitari degli adattatori (quando possibile), usando test-doubles delle API esterne
- test di integrazione degli adattatori, usando istanze in memoria dei servizi
- test di accettazione, usando adattatori e servizi in memoria
[ sulla terminologia usata - dummy, fake, stub e mock - c'è ancora molta confusione in letteratura, per questo uso il termine più generico di test-double. io in genere faccio riferimento al lavoro di riorganizzazzione di Meszaros. ad esempio, un fake è una implementazione alternativa di una interfaccia (ma dal comportamento consistente), usata solo a scopi di test, come il classico Gateway al database realizzato con una hashmap in memoria. uno stub invece è un oggetto programmabile, che altrimenti implementa un comportamento di default. un mock, infine, realizza l'endo-testing o testing delle interazioni, con cui verificare le interazioni tra oggetti. ]
per i test di integrazione ci siamo affidati a implementazioni leggere dei vari servizi, realizzate in memoria, avviabili programmaticamente in fase di setUp e disattivati al termine della suite durante il tearDown. finora, siamo riusciti a collezionare un buon numero di tool open-source, ad esempio:
- come web-container, l'ultra leggero e versatile Jetty, abbandonando il ServletContainer di HttpUnit, ma solo dopo esserci scontrati con alcuni suoi limiti (primo tra tutti, il content-type a text/plain)
- come server SMTP, abbiamo scelto Dumbster, in ascolto sulla canonica porta 25 locale
- come broker JMS, si è dimostrato davvero ottimo ActiveMQ, che offre code in memoria non persistenti e visibili da tutta la virtual machine (url del tipo vm://localhost), oppure accessibili anche da client esterni (url del tipo tcp://localhost)
- come dataSource, non avendo ancora a disposizione un sistema di deploy automatico di schemi e stored-procedure (ma ci stiamo lavorando!), abbiamo scelto di usare direttamente l'ambiente di test condiviso, affidandoci però al supporto di transazionalità nei test offerto da Spring
il punto forza di questo approcio, a cui si aggiungono Dependency-Injection (ottenuta come effetto collaterale del TDD) e uso degli standard (come JMS, servlet-container, JDBC e STMP), ci ha inoltre permesso di ottenere un ulteriore enorme risultato: realizzare sulle macchine di sviluppo delle demo al cliente andando ad agganciarci alle istanze reali dei servizi. scrivendo solo una diversa configurazione - breve e localizzata - grazie al supporto di IoC fornita da Spring abbiamo potuto usare un vero application server, un vero broker JMS, un vero server SMTP e un vero database.
la nostra particolare situazione (requisiti ancora parziali sull'integrazione ai servizi esterni) e, non lo nego, una grossa flessibilità da parte dei customer interni, ci hanno permesso di consegnare e farci approvare tutte le user-stories sviluppato fino ad ora, nonostante manchino ancora intere parti di integrazione (DAO esterni, alcuni servizi SOAP e l'infrastruttura di Single Sign-On).
per questo, oltre che estremamente interessante, divertente e pieno di sfide, non posso che considerare finora questo progetto un successo, sia per il cliente che si è visto un po' alla volta consegnare pezzettini di funzionalità, sia per il team, che ha dato alla luce una architettura flessibile, in modo del tutto incrementale, usando come ferri del mestiere testabilità, disaccopiamento e semplicità.
ciao.
-papo-