Utilizzare Spring nelle web application – parte 8

Abbiamo percorso molta strada fino a qui: siamo partiti da un’analisi decisamente poco “formale” per arrivare, in poco tempo, ad un’applicazione funzionante. Ma non basta! Mancano infatti almeno due caratteristiche essenziali:

  • il database: i dati infatti al momento non vengono letti e scritti da nessuna parte;
  • uno stile grafico accattivante, assolutamente necessario per avere più clienti.

Per il momento ci occuperemo del primo punto, ovvero il salvataggio permanente e sicuro dei dati. Ma prima, come al solito, ci facciamo un giro di refactoring!

Gerarchia e separazione dei test

A questo punto l’applicazione sta cominciando a strutturarsi definitivamente nei classici tre strati: web, service e DAO (Data Access Objects). Quest’ultimo strato si occupa dell’interazione vera e propria con il database sottostante (cfr linkografia – DAO), “sporcandosi” le mani con query SQL o altro.

In questa situazione l’elevato numero di sorgenti del sistema diventa un problema per quanto riguarda i test problema che risolveremo separando le gerarchie delle classi di test: una per i componenti “interni” (DAO e service) e una per i componenti web. Le modifiche in realtà sono minime, ma ci risparmieranno parecchi problemi in futuro!

Per prima cosa nel file src/main/resources/applicationContext.xml va modificata la voce <context:component-scan>, specificando solamente i package it.artera.springtut.dao e it.artera.springtut.service, contenenti i service e i DAO:

&lt;context:component-scan base-package=&quot;it.artera.springtut.dao, it.artera.springtut.service&quot;/&gt;

Discorso simile per il file src/main/webapp/WEB-INF/dispatcher-servlet.xml, limitando lo scanning dei componenti al package it.artera.springtut.controller:

&lt;context:component-scan base-package=&quot;it.artera.springtut.controller&quot;/&gt;

Creiamo quindi il file src/test/java/it/artera/springtut/controller/AbstractWebTest.java, che sarà farà da classe base per i nostri test sui controller:

package it.artera.springtut.controller;

import it.artera.springtut.AbstractTest;

import org.springframework.test.context.ContextConfiguration;

@ContextConfiguration(inheritLocations = true, locations = &quot;file:src/main/webapp/WEB-INF/dispatcher-servlet.xml&quot;)
public abstract class AbstractWebTest extends AbstractTest {
}

Come potete notare questa classe è configurata per caricare, oltre ad applicationContext.xml, anche dispatcher-servlet.xml; in questo modo Spring configurerà per noi tutti i controller, permettendoci di eseguire i test in condizioni molto simili a quelle reali in cui opererà l’applicazione.

L’ultima modifica è molto semplice, si tratta di far estendere AbstractWebTest (al posto di AbstractTest) a IndexControllerTest:

public class IndexControllerTest extends AbstractWebTest
[...]

DAO – Data Access Objects

Il primo DAO che realizzeremo sarà it.artera.springtut.dao.InsertionDao. Sempre nell’ottica di procedere un passo alla volta, realizzeremo dapprima una implementazione “dummy” (fantoccio), per facilitare l’integrazione con gli altri componenti; successivamente vedremo come implementare il collegamento vero e proprio al database. Quindi per ora banalmente copieremo i metodi getMostRecentInsertions() e getInsertion() da InsertionService a InsertionDao:

package it.artera.springtut.dao;

import it.artera.springtut.model.Insertion;

import java.math.BigDecimal;
import java.util.*;

import org.springframework.stereotype.Repository;

@Repository
public class InsertionDao {
    public List&amp;lt;Insertion&amp;gt; getMostRecentInsertions() {

        // implementazione finta, da utilizzare per sviluppare la parte web
        List&amp;lt;Insertion&amp;gt; result = new ArrayList&amp;lt;Insertion&amp;gt;();
        result.add(new Insertion(1L, &quot;Chitarra acustica&quot;, &quot;Lorem ipsum docet bla bla bla&quot;, null,
                new BigDecimal(&quot;50.0&quot;), new Date()));
        result.add(new Insertion(2L, &quot;Collezione figurine Panini&quot;, &quot;Lorem ipsum docet bla bla bla&quot;, null,
                new BigDecimal(&quot;100.0&quot;), new Date()));
        result.add(new Insertion(3L, &quot;Chitarra acustica&quot;, &quot;Lorem ipsum docet bla bla bla&quot;, null, null, new Date()));

        return result;
    }

    public Insertion getInsertion(long id) {
        // implementazione finta, da utilizzare per sviluppare la parte web
        return new Insertion(id, &quot;Chitarra acustica&quot;, &quot;Lorem ipsum docet bla bla bla&quot;, null, new BigDecimal(&quot;50.0&quot;),
                new Date());
    }

}

La classe InsertionService va ora modificata, facendogli utilizzare i metodi di InsertionDao per l’interazione con il database:

package it.artera.springtut.service;

import it.artera.springtut.dao.InsertionDao;
import it.artera.springtut.model.Insertion;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class InsertionService {

    private InsertionDao insertionDao;

    @Autowired
    public void setInsertionDao(InsertionDao insertionDao) {
        this.insertionDao = insertionDao;
    }

    public List&amp;lt;Insertion&amp;gt; getMostRecentInsertions() {
        return insertionDao.getMostRecentInsertions();
    }

    public Insertion getInsertion(long id) {
        return insertionDao.getInsertion(id);
    }
}

Come potete notare infatti ora InsertionService è un semplice “passacarte” che si limita a girare le richieste al DAO. Questo stile di implementazione non è esente da difetti e soprattutto critiche, perché apparentemente ogni chiamata viene implementata due volte! In realtà ciò avviene solamente all’inizio di un progetto, quando i service si limitano a caricare e salvare oggetti molto semplici. Mano a mano che la complessità degli use case sale infatti, una singola chiamata ad un service avrà bisogno di più chiamate verso lo strato DAO o verso altri service. Inoltre dobbiamo ricordare che, per come abbiamo configurato l’applicazione, ogni chiamata ad un service è una transazione a sè stante; i DAO quindi vanno implementati dando per assodato il fatto che ci sia una transazione in corso, per evitare di doversi occupare della gestione delle stesse.

Un aspetto positivo che vorremmo farvi notare è che nessun client di InsertionService è stato modificato: addirittura il test di InsertionService continuerà a dare esito positivo, per il semplice fatto che abbiamo solamente aggiunto uno strato applicativo più “in basso”, mantenendo inalterato il contratto (o interfaccia) con gli strati superiori. Se lanciamo i test del progetto infatti:

mvn test

dovremmo ottenere un bel messaggio BUILD SUCCESSFUL

Sviluppi

Nel prossimo articolo configureremo un database vero e proprio (MySql) e realizzeremo un’implementazione di tipo JDBC del nostro DAO.

Linkografia

  • Data Access Objects – DAO (Wikipedia);