|
Supponiamo di dovere sviluppare un Business Object (BO) il cui compito è di elaborare i dati, restituiti da un Data Access Object (DAO), che rappresentano le informazioni di Account di un utente.
L’interfaccia di business del nostro semplice BO (AccountBusinessObject) è composta da un solo metodo di business doFoo():
public interface AccountBusinessObject{ public AccountModel doFoo(String id) throws BusinessException; }
La classe concreta del Business Object (BO) implementa questa interfaccia
public class AccountBusinessObjectSpringImpl implements AccountBusinessObject {
e utilizza, nel metodo di business doFoo(), un oggetto DAO per accedere al Database (Pattern DAO):
public AccountModel doFoo(String id)throws BusinessException { try{ AccountModel model = this.dataAccessObject.readAccount(id); model=this.businessLogic(model); return model; } catch(DaoException daoe){ throw new BusinessException(daoe.getMessage()); } }
La classe DAO concreta implementa la seguente interfaccia AccountDao:
public interface AccountDao { public AccountModel readAccount(String pk) throws DaoException; }
La classe di business viene sollevata dall’incarico di istanziare il DAO, a tale operazione penserà Spring. L’unica cosa necessaria è dichiarare nella classe di business una proprietà d’interfaccia AccountDao e il relativo metodo setter:
public class AccountSpringBusinessObjectImpl implements AccountBusinessObject { private AccountDao dataAccessObject;
public void setDataAccessObject(AccountDao dao) { String method = "SpringBusinessObjectImpl.setDataAccessObject: "; this.dataAccessObject = dao; }
Sarà compito di Spring iniettare, mediante il relativo metodo setter, l’opportuno oggetto DAO configurato nel file XML.
A questo punto passiamo allo sviluppo della classe DAO e alla definizione delle varie dipendenze tra le classi all’interno del file di configurazione di Spring, nel nostro caso è la semplice relazione: “BO usa DAO”.
Supponiamo di non avere ancora il DBMS pronto, per non perdere tempo prezioso, possiamo “rimediare” sviluppando un DAO mock che, senza accedere realmente al DB, restituisca dei dati fittizi (hardcoded, configurati su file, …) in modo da permettere lo sviluppo della classe di Business e la predisposizione della suite di test:
public class AccountDaoMockImpl implements AccountDao{
public AccountDaoMockImpl() throws DaoException{ }
public AccountModel readAccount(String pk) { AccountModel accountOM = null; // Leggo i dati mock da file (properties, xml, ...) // per semplicità in questo esempio i dati mock sono harcoded if(pk.equalsIgnoreCase("001")){ accountOM = new new AccountModel("001", "Ale Maio", "amaio@aaa.it", 80.0); } return accountOM; }
La dipendenza tra il BO ed il DAO (quest’ultimo per il momento in versione mock) viene definita nel seguente file di configurazione xml:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans> <bean id="mockDAO" class="it.kok.samples.ditdd.dao.AccountDaoMockImpl" singleton="true"> </bean> <bean id="springBusinessObject" class="it.kok.samples.ditdd.business.AccountSpringBusinessObjectImpl"> <property name="dataAccessObject" ref="mockDAO" /> </bean> </beans>
Con questa configurazione l’environment Spring inietta nella nostra classe it.kok.samples.ditdd.business.AccountSpringBusinessObjectImpl l’oggetto it.kok.samples.ditdd.dao.AccountDaoMockImpl mediante il metodo: AccountBusinessObjectSpringImpl.setDataAccessObject():
Non rimane che sviluppare la classe di test unit:
public class TestUnitAccountBusinessObject extends TestCase{
public void testBoDoFoo() { try { ApplicationContext ctx = new FileSystemXmlApplicationContext(SPRING_CONFIG_FILE); AccountBusinessObject bo = (AccountBusinessObject) ctx.getBean("springBusinessObject"); // Test utente esistente AccountModel res=bo.doFoo("001"); assertNotNull(res); assertEquals("001", res.getId()); assertEquals("Ale Maio", res.getName()); assertEquals("amaio@aaa.it", res.getAddress()); assertEquals(80.0, res.getBalance()); // Test utente NON esistente res=bo.doFoo("123"); assertNull(res); }catch(Throwable t) { fail(t.getMessage()); } } Come si può vedere siamo in grado di testare le funzionalità del BO POJO in un ambiente cosiddetto Unmanaged (Out-of-Container), e quindi in assenza sia dell’Application Server che del Database.
Figura 2 - Scenario di test del Business Object con Dao mock in ambiente Unmanaged
Una volta pronto il DBMS si procede con lo sviluppo del DAO vero e proprio sostituendo quello Mock. Per la parte di accesso ai dati usufruiamo di un altro Framework java, Hibernate. Lo scopo principale di Hibernate è quello di fornire una soluzione Object-Relational Mapping (ORM), ovvero di mappare classi Java (Plain Old Java Object) con le tabelle di un database relazionale; sulla base di questo mapping, Hibernate fornisce uno strato di accesso ai dati trasparente all'utilizzatore.
La classe concreta DAO viene creata implementando l’interfaccia AccountDao (la stessa interfaccia che veniva implementata precedentemente dalla classe Mock):
public class AccountDaoSpringImplHybernate implements AccountDao{
private String CLASS_NAME = "AccountDaoSpringImplHybernate"; private SessionFactory sessionFactory = null;
public AccountDaoSpringImplHybernate() throws DaoException{ }
public void setSessionFactory(SessionFactory sessionFactory) { this.sessionFactory = sessionFactory; }
public AccountModel readAccount(String pk) {
HibernateTemplate template = new HibernateTemplate(sessionFactory); // lettura account AccountModel accountOM=(AccountModel)template.get(AccountModel.class, pk); return accountOM; }
Il file di mapping Hybernate per l’esempio proposto è il seguente:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="it.kok.damples.ditdd.dao"> <class name="it.kok.samples.ditdd.dao.AccountModel" table="account"> <id name="id" column="id"> <generator class="native"/> </id> <property name="name" type="java.lang.String" column="name"/> <property name="address" type="java.lang.String" column="address"/> <property name="balance" type="double" column="balance"/> </class> </hibernate-mapping>
A questo punto è nostro interesse verificare che mapping Hybernate (e il relativo SQL generato) siano corretti. Anche in questo caso puntiamo inizialmente a verificare il corretto funzionamento del DAO eseguendo il test in ambiente Unmanaged.
Per questo test del DAO Out-Of-Container, nel file XML di Spring si devono specificare le proprietà: • driverClassName • url • username • password
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans> <bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="org.gjt.mm.mysql.Driver" /> <property name="url" value="jdbc:mysql://localhost:3306/kokdb" /> <property name="username" value=""/> <property name="password" value=""/> </bean>
<bean id="mySessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> <property name="dataSource" ref="myDataSource"/> <property name="mappingResources"> <list> <value>account.hbm.xml</value> </list> </property> <property name="hibernateProperties"> <value> hibernate.dialect=org.hibernate.dialect.MySQLDialect hibernate.show_sql=true </value> </property> </bean>
<bean id="hibernateDAO" class="it.kok.samples.ditdd.dao.AccountDaoSpringImplHybernate" singleton="true"> <property name="sessionFactory" ref="mySessionFactory" /> </bean>
</beans>
Sviluppiamo ed eseguiamo un test di unità per verificare il corretto funzionamento del DAO appena sviluppato:
public class TestUnitAccountDaoHybernate extends TestCase { public void testSelect() { try { // Read the configuration file ApplicationContext ctx = new FileSystemXmlApplicationContext(SPRING_CONFIG_FILE);
//Instantiate an object AccountDaoSpringImplHybernate dao = (AccountDaoSpringImplHybernate) ctx.getBean("hibernateDAO"); AccountModel res=dao.readAccount("001"); assertNotNull(res); assertEquals("001", res.getId()); assertEquals("Ale Maio", res.getName()); assertEquals("amaio@aaa.it", res.getAddress()); assertEquals(80.0, res.getBalance());
. . .
// Test utente non esistente res=dao.readAccount("123"); assertNull(res); } catch(Exception ex) { ex.printStackTrace(); fail(ex.getMessage()); } }
Una volta ottenuto verde, rieseguiamo, sempre in ambiente Unmanaged, il test relativo al BO in modo da verificarne il corretto funzionamento con il DAO appena sviluppato.
Solo quando tutti i test Out-of-Container risulteranno verdi, ovvero dopo aver certificato la correttezza del codice sviluppato (nel nostro caso il BO e il DAO), saremo pronti a procedere con il deploy nel Container e a rieseguire i precedenti test in un ambiente Managed (In-Container). Di fronte a uno scenario in cui i test Out-of-Container hanno esito positivo (verde) e i test In-Container hanno esito negativo (rosso), il problema è da imputare al deploy o alla configurazione dell’Application Server, l’errore è già ben circoscritto e la problem determination già indirizzata.
 Figura 3 - Scenario di test Out-Of-Container del Business Object con il DB
Notare come con Spring le risorse vengono iniettate dall’esterno evitando di “sporcare” le nostre classi, le quali risultano essere così indipendenti dall’ambiente di esecuzione (Managed, Unmanaged) e più semplici da testare.
Con la Dependency Injection è possibile spostare le nostre classi da un ambiente di test all’altro cambiando opportunamente i file di configurazione Spring che pilotano la DI. Se nello scenario Out-of-Container (Unmanaged) era necessario specificare tutte le configurazioni (DataSource, SessionFactory, DAO, BO ) nello scenario In-Container (Managed) non è necessario specificarle tutte in quanto alcune informazioni sono già presenti nei file di deploy (es: configurazione del DataSource all’interno del file mysql-ds.xml nel caso di JBoss) comportando una modifica del file di configurazione Spring atta a recuperare le risorse da iniettare attraverso JNDI (org.springframework.jndi.JndiObjectFactoryBean).
Figura 4 - Differenze di configurazione tra ambiente Unmanaged e Managed
Per usare il nostro BO in un Application Server (es: JBOSS), ad esempio per essere utilizzato da un EJB, senza dovere toccare alcuna riga di codice possiamo procedere al seguente deploy:

Figura 5 - Utilizzo del BO da un EJB (EJB Container JBoss)
Analogo discorso è possibile farlo nel caso in cui il BO debba essere usato in un Servlet Container (es: TOMCAT). Sempre a parità di .class, in caso di chiamata del BO da parte di una Servlet, il deploy risulta come riportato nell’immagine sottostante:
Figura 6 - Utilizzo del BO all’interno di una WebApp (Servlet Container Tomcat)
|