MokaByte 105 - Marzo 2006
 
MokaByte 105 - Marzo 2006 Prima pagina Cerca Home Page

 

 

 

Architetture e tecniche di progettazione EJB
IV parte: il data model e la sincronizzazione dello stato tramite CMP2.0 - Introduzione

Inizia una nuova parte della serie dedicata alla progettazione di applicazioni EJB: in questo e nei prossimi articoli parleremo di quali siano le indicazioni da seguire per la realizzazione dello strato di mapping OO-RDB basato su Entity beans CMP.

Finalità di questi articoli
Negli articoli precedenti di questa serie sono state presentate alcune tecniche di progettazione utilizzate nell'ambito di applicazioni Java EE basate su tecnologia web e EJB; è stata ANCHE l’occasione per presentare alcuni dei patten più importanti sia per l’organizzazione e interconnessione dei vari strati applicativi (es business delegate in accoppiamento con session facade) sia per lo scambio delle informazioni fra gli strati e relativa copia in memoria (pattern DTO e VO).
Nella ultima puntata della serie dedicata ai meccanismi di migrazione dei dati fra gli strati applicativi, si era detto che lo strato di presentation layer (tipicamente una applicazione web) interagisce con quello di business logic (dove i dati vengono non solo ricavati dal layer di persistenza, ma anche manipolati secondo il workflow della applicazione) dal quale ricava le informazioni necessarie per la visualizzazione all’utente e viceversa.
Si era anche visto come lo scambio di informazioni debba essere eseguito per mezzo di appositi oggetti di trasporto detti Data Transfer Object, oggetti con il compito di contenere al loro interno le informazioni per il trasferimento delle stesse dallo strato mittente (es lo strato web nel caso si voglia inviare i dati per una modifica) verso il destinatario dove verranno consumate.
Questa modo di concepire una applicazione (ovvero seguendo i flussi dati) sarà una delle chiavi di lettura adottate in questa serie di articoli.

Tutte le considerazioni fatte in precedenza in definitiva riguardano gli aspetti relativi alla comunicazione fra i vari strati applicativi, la modalità con la quale i dati debbano essere manipolati e manenuti sul presentation layer: niente (o quasi) per adesso è stato detto circa quello che si nasconde “dietro” il Session Façade (del resto questo è un risultato cercato e ottenuto grazie alla motivazione di questo pattern).
Rimane da capire come sia possibile trasferire i dati contenuti all’interno del DTO da e verso il sistema di persistenza (tipicamente il database relazionale sul quale tutta l’applicazione si poggia). Utilizzando una terminologia Java Enterprise, quello che ancora non si è detto è come utilizzare con profitto un framework di mapping Object-Relational al fine di nascondere tutti i dettagli relativi alla gestione della connessione con il database.
Si vedrà anche come sia possibile gestire in maniera trasparente la trasformazione oggetto-tabella con particolare riferimento alle relazioni e aggregazioni di oggetti, cancellazioni a cascata e simili.

NOTA: la specifica Java EE impone che l'applicazione in esecuzione all'interno del container non debba e non possa gestire direttamente la connessione al database, essendo questo un compito a totale carico dell’application server. Questo, dopo aver instaurato una connessione fisica con il server dati, mette a disposizione una connessione virtuale con il supporto di un connection pooling, sicurezza, transazionalità e tutti i servizi tipici di una applicazione di alto livello.
Al programmatore resta da scegliere lo strumento tramite il quale effettuare il mapping Object-Relational (OO-RDB) ed eseguire tramite tale strumento la sincronizzazione dello stato degli oggetti con i dati residenti nel database.

L’evoluzione tecnologica degli ultimi anni ha ampiamente giustificato e anzi fortemente consigliato l'utilizzo di strumenti di mapping Object relational per sincronizzare lo stato di un oggetto. Lo stato dell’arte attuale vede lo scenario dominato da framework e tecnologie quali Hibernate, JDO, CMP 2.0 (con EJB 3.0 + Hibernate in fase di rilascio).
Quanto vedremo non deve essere considerata come l’unica soluzione possibile, dato che nella realtà dei progetti enterprise sempre più spesso si vedono mescolare con successo tecnologie differenti in modo da rispondere alle varie esigenze e requisiti del progetto. Tanto per fare un esempio in questo particolare momento è molto in voga per la realizzazione di applicazioni enterprise, l’utilizzo di session beans per la parte di business unitamente ad Hibernate per la parte di mapping.
Parlando invece di applicazioni “solo” EJB (oggetto di questa serie di articoli) lo strumento che andremo a vedere è il Container Managed Persistence, tecnologia giunta ormai alla versione 2.0.
In realtà CMP 2.0 non è altro che un pezzetto della specifica EJB, ma a causa della importanza della tecnologia e la diversità rispetto alle versioni precedenti (che non venivano considerate come una parte a se stante ma integrate a tutti gli effetti in EJB 1.0, 1.1), spesso viene indicata da sola per rafforzarne l’importanza.
Nota importante sulle motivazioni della serie
Questo nuovo gruppo di articoli dedicati alla persistenza con CMP, e facenti parte della serie sullo sviluppo di applicazioni enterprise con EJB, non è stato pensato per essere un corso teorico su EJB ne sui trucchi di CMP.
Lo scopo è piuttosto quello di fornire un approccio concreto per utilizzare alcune delle tecniche di progettazione più in voga attualmente; come fatto nei precedenti articoli dedicati allo strato session, si cercherà di mostrare come usare pattern e tecniche di progettazione al fine di progettare e implementare lo strato di persistenza cercando di perseguire gli obiettivi mostrati in precedenza, ovvero flessibilità, scalabilità, prestazioni, semplicità di manutenzione.
Non si seguirà una trattazione rigorosa e meticolosa su cosa sia un Enterprise Java Beans CMP, ne scenderemo nel dettaglio di aspetti come il ciclo di vita di un entity CMP o sulla miriade di messaggi che il bean e il container si scambiano durante tutto il corso della vita di un componente come questo.
Per questo genere di argomenti si può fare riferimento a un qualsiasi manuale su EJB (come ad esempio [EJB]) compresa la serie di articoli pubblicata in passato su MokaByte confluita infine nel capitolo su Enterprise Java Beans del Manuale Pratico di Java, realizzato dalla redazione di MokaByte (vedi [MokaEJB]).

 

Cosa è un entity bean
Un bean entity è un componente EJB il cui scopo principale è quello di rappresentare una entità astratta, come insegna comunemente un qualsiasi processo di analisi a oggetti. Un oggetto di questo tipo rappresenta quindi un dato semplice o un aggregato, il quale possiede attributi di stato e di relazione (con altri oggetti/entità).
Importante tenere sempre ben presente che un CMP vive all’interno di un application server il quale mette a disposizione alcuni servizi: il più importante è certamente il meccanismo di persistenza dello stato dell’oggetto, ma non si devono trascurare la transazionalità, la gestione della sicurezza, la gestione del ciclo di vita delle varie istanze di oggetti gestiti in modalità pooled.
Tralasciando il punto di vista dell’application server e ponendosi invece dal punto di vista del business logic layer (che sarà l’utilizzatore dei componenti EJB) si potrebbe dare una definizione molto sintetica e pragmatica: un CMP è un componente che mappa una entità astratta che potrà essere utilizzata nel workflow applicativo essendo sicuri che l’application server svolgerà per noi una serie di operazioni.

Non ci si preoccuperà quindi di transazionalità, sicurezza, resistenza al carico di lavoro (grazie al pooling di istanze) e persistenza: con la consapevolezza che l’application server svolge queste queste mansioni, si concentrerà l’attenzione su quelle tecniche e pattern architetturali che possano soddisfare le necessità del business layer, senza ostacolare il lavoro del container ma anzi facilitandone il compito.

 

Prima regola: stato si, comportamento no
La programmazione ad oggetti ci ha da sempre insegnato che unitamente allo stato, un oggetto possiede anche un comportamento (reso possibile grazie all’uso dei metodi), cosa che lo distingue da strutture dati tipiche dei linguaggi di programmazione non ad oggetti.
In una applicazione multistrato di fascia enterprise, sebbene la specifica non lo imponga e nemmeno lo preveda ufficialmente, è buona norma adottare una regola universale che porta a una sostanziale differenza fra un oggetto comune e un entity bean: essendo questo un elemento che rappresenta una entità astratta, dovrebbe sempre memorizzare uno stato e non offrire nessuna logica di business.
Una entità quindi non deve contenere metodi di business logic ma eventualmente metodi che consentano di gestire lo stato della entità stessa: si dovrebbero quindi inserire metodi accessori/modificatori (getXXX e setXXX) o metodi che compiano una qualche conversione dei dati stessi, possibilmente senza alterare lo stato del bean. Ad esempio se il bean contiene una variabile di tipo data che memorizza la data di nascita di una persona, potrebbe essere utile disporre di un metodo che restituisca tale data in formato stringa o che esegua una qualche conversione da data in formato europeo a formato statunitense.
Prendendo ad esempio il caso introdotto più avanti nell’articolo, se si considera una entità rappresentante un conto in banca o un bancomat, è lecito che sia possibile interrogare il conto per conoscere il saldo, ma è certamente poco piacevole che il conto corrente eseguire operazioni autonomamente senza il controllo di un soggetto attivo (il cassiere o genericamente la business logic controllata dal sistema web di home banking).

 

Solo interfaccia locale
Altra regole molto da seguire nella progettazione di una applicazione multistrato è quella di non implementare mai l’interfaccia remota di un entity bean (ovvero permettere ad un client di accedere direttamente all’entity senza l’intercessione dello strato di logica): se un entity rappresenta una struttura dati astratta che mappa in un qualche modo una entità del data model, risulta semplice comprendere quanto sia controindicato fornire diretto accesso alla applicazione client a tale entità. Per una moltitudine di ragioni è più saggio e corretto fornire accesso al data model tramite l’apposito strato di business logic dove si potranno concentrare tutti controlli su sicurezza, coerenza sui dati, e procedere secondo le regole imposte dal workflow applicativo.
Con un esempio un po’ pittoresco, si potrebbe pensare al caso in cui si debba effettuare un pagamento presso un negoziante di fiducia fornendo direttamente il bancomat con tanto di codice, piuttosto che usare il bancomat per eseguire il pagamento tramite POS. In questo caso il cliente del negozio svolge il compito di session façade agli occhi del negoziante: è lui che conosce il codice del bancomat e lo tiene segreto (session-security), ed è lui che sfrutta la logica di business per accedere in maniera controllata al datamodel (il conto corrente).



Figura 1
– Il client interagisce con un session façade con il quale scambia informazioni e dati per mezzo di DTO.
Cosa avvenga “dietro” al façade è ignoto per il client.>>


La creazione di un bean
Come dovrebbe essere noto, non è possibile creare direttamente un entity bean: come molti altri aspetti relativi al ciclo di vita del componente, anche la procedura di creazione dell’oggetto viene gestita dal container, il quale in un determinato momento del ciclo di vita del bean ne invoca il metodo ejbCreate() e ejbPostCreate().
Supponendo il caso in cui si debba procedere alla creazione di un bean con i dati inserite dall’utente tramite un form HTML: tali dati fluiscono tramite un DTO dallo strato web, passano per quello di business logic per arrivare all’entity bean in questione. Il DTO potrebbe contenere solamente le informazioni necessarie alla creazione della entità che si deve aggiungere al sistema, oppure anche dati (chiavi o DTO completi) relative a entità con le quali il nuovo oggetto dovrà instaurare relazioni di vario genere. Questi concetti sono stati ampiamenti analizzati negli articoli in cui si è parlato delle possibili tecniche di gestione dei DTO (vedi [DTO1] e [DTO2]).

 


Figura 2 – Il DTO potrebbe essere composto come un aggregato di DTO a grana più fine. Client e session façade non si preoccupano di questi aspetti, limitandosi a trasferire un DTO indipendentemente dalla sua composizione e organizzazione interna.>>

Il metodo ejbCreate() è quindi il luogo deputato per eseguire il trasferimento dei dati dal DTO al bean.
Per rendere più chiara la trattazione è utile prendere un esempio concreto per spiegare i vari passaggi; al solito l’applicazione di riferimento è MokaByteCMS, il CMS open source sviluppato dal team di sviluppatori di MokaByte.
Si immagini quindi che si debba procedere nella creazione di un articolo da inserire nel CMS.
Per tale scopo il session bean ArticleManager (avente funzione di session façade) espone tramite la sua interfaccia remota il seguente metodo

public void createArticle(ArticleDTO articleDTO) throws FacadeInvocationException,                                                         SectionNotFoundException

Tale metodo viene in genere invocato all’interno di una classe dello strato web della applicazione (tipicamente una action MVC). Ad esempio si potrebbe sintetizzare con le seguenti operazioni la sequenza delle operazioni svolte all’interno del client

// inizializza la connessione con la interfaccia remota del session façade
String FACADE_NAME = "ArticleManagerSF";
Class FACADE_CLASS = com.mokabyte.cms.ejb.ArticleManagerSFHome.class;
ArticleManagerSFHome articleManagerSFHome;
try {
  ServiceLocator locator = ServiceLocator.getInstance(jndiProperties);
  articleManagerSFHome = (ArticleManagerSFHome) locator.getEjbHome(FACADE_NAME, FACADE_CLASS);
  if (articleManagerSFHome == null) {
   throw new Exception("Impossibile ricavare la home interface " + FACADE_NAME);
  }
  // ricava l’interfaccia remota del session façade
  articleManagerSFRemote = articleManagerSFHome.create();

}
catch (ServiceLocatorException e) {
  throw new Exception(e.getMessage());
}

...
try {
  logger.debug("invocazione di SF remoto");
  // invoca il metodo remoto per la creazione dell’articolo
  articleManagerSFRemote.createArticle(articleDTO);

}
catch (RemoteException re) {
  String msg = "Errore di invocazione remota sul metodo di façade createArticle() \n" + re;
  logger.error(msg);
  throw new FacadeInvocationException(msg);
}


Il codice di esempio appena riportato è stato così riassunto per esigenze di sintesi e chiarezza, ma nella realtà è eseguito dal client non direttamente ma tramite gli ormai noti pattern Business Delegate e Service Locator.

Passando al session façade, all’interno del metodo createArticle() si delega la creazione del bean al bean stesso invocandone il metodo create(), il quale riceve il DTO ricevuto dalla strato client.
Per fare questo il session bean dovrà aver precedentemente ricavato l’interfaccia home locale del bean Article: questa operazione in genere viene effettuata all'interno del metodo setSessionContext() che rappresenta il punto di ingresso del ciclo di vita di un session:

public void setSessionContext(SessionContext sessionContext) {
  this.sessionContext = sessionContext;
 
try {
  
  // inizializza le home interfaces
    if (articleLocalHome == null) {
  
    try {
  
     ServiceLocator locator = ServiceLocator.getInstance();
  
     // ricava la home interface con l’ausilio del service locator
  
     articleLocalHome = (ArticleLocalHome) locator.getEjbLocalHome(ENTITY_NAME);
  
    }
            catch (ServiceLocatorException e) {
  
      throw new EJBException(e.getMessage());
  
    }
    }
  }
  catch (Exception e) {
  
  throw new EJBException(e.getMessage());
  }
}

A questo punto può essere invocato il metodo createArticle() che al suo interno potrebbe essere così organizzato:

public void createArticle(ArticleDTO articleDTO) throws EJBException {
  try {
    // crea il bean invocando il metodo create sulla sua Home interface
    ArticleLocal articleLocal = articleLocalHome.create(articleDTO);
  }
  catch (Exception e) {
    throw new EJBException(e.getMessage());
  }
  // Esegue altre operazioni di sincronizzazione dello stato
  . . .
}


Si noti la presenza di ulteriori operazioni relative alla creazione di un articolo: ad esempio si potrebbe rendere necessario aggiornare un contatore che memorizza il numero complessivo di articoli presenti nel sistema. Esse potranno essere eseguite all'interno del metodo certi che ogni modifica al sistema sarà coerente con il processo di creazione dell'articolo, essendo il contesto processato in maniera transazionale.

A questo punto il container, dopo avere effettuato i debiti controlli sulla validità dei parametri passati e dopo aver ricavato dal pool di oggetti il bean opportuno (vuoto se si sta creandone uno nuovo come in questo caso), scinde la chiamata del metodo create() nei due corrispettivi ejbCreate() e ejbPostCreate().
Non si entrerà nei dettagli di quelle che sono le regole relative alla firma (parametri ed eccezioni) dei vari metodi create(), ejbCreate() ed ejbPostCreate(): per quello che concerne la creazione del bean è sufficiente ricordare che è necessario inserire nel metodo ejbCreate() tutta la logica relativa alla valorizzazione degli attributi semplici del bean, rimandando al metodo ejbPostCreate() la assegnazione delle relazioni fra il bean e i suoi bean dipendenti (operazione che verrà mostrata in seguito).
Dovrebbe essere quindi piuttosto intuitivo il significato del contenuto del metodo ejbCreate() riportato qui di seguito

public String ejbCreate(ArticleDTO articleDTO) throws CreateException {
  this.setId(articleDTO.getId());
  this.setAbstractText(articleDTO.getAbstractText());
  this.setBody(articleDTO.getBody());
  this.setIntroducion(articleDTO.getIntroducion());
  this.setName(articleDTO.getName());
  this.setStatus(articleDTO.getStatus());
  this.setNotes(articleDTO.getNotes());
  this.setSubTitle(articleDTO.getSubTitle());
  this.setTitle(articleDTO.getTitle());

  this.setSerialName(articleDTO.getSerialName());
  this.setColumnName(articleDTO.getColumnName());
  this.setKeywords(articleDTO.getKeywords());
  return "";
}


Si tratta di una semplice impostazione delle variabili del bean semplici; tutti gli attributi di relazione verranno assegnati in seguito nel metodo ejbPostCreate().

 

La creazione del DTO: il legame con il DTOAssembler
Dopo aver analizzato il flusso di dati relativo al processo di creazione di un entity bean, flusso che vede migrare un DTO dallo strato client a quello server, potrebbe essere interessante prendere in considerazione il percorso inverso che i dati intraprendono: dal bean verso il client.
Questo tragitto è quello che usualmente viene effettuato in corrispondenza di una interrogazione del client sullo strato di business logic, ad esempio per una comune ricerca per chiave primaria.
In questo caso il session effettua la ricerca dell’entity utilizzando i parametri di ricerca ricevuti e preleva i dati dell’entity bean individuato per immetterli in un DTO che viene poi inviato al client.
Per prima cosa il client invoca il metodo di ricerca sul session façade: come mostrato negli articoli precedenti, tale invocazione verrà eseguita all'interno del business delegate associato al façade, ma per semplicità tralasceremo questo dettaglio, dando per già fatto (come mostrato in precedenza) le operazioni di lookup della interfaccia remota di ArticleManagerSF;

ArticleDTO articleDTO = null;
try {
  articleDTO = articleManagerSFRemote.articleFindByPrimaryKey(articleId);
}
catch (RemoteException re) {
  String msg = "Errore di invocazione remota sul metodo di façade." + re;
  logger.error(msg);
  throw new FacadeInvocationException(msg);
}

Il metodo remoto del session façade articleFindByPrimaryKey() è semplice nella sua implementazione:

public ArticleLocal articleFindByPrimaryKey(String id) throws EJBException {
  try {
    ArticleLocal articleLocal = articleLocalHome.findByPrimaryKey(id);
    return ArticleDTOAssembler.createDto(articleLocal);
  }
  catch (ObjectNotFoundException onfe) {
    logger.debug("nessun articolo trovato con Id=" + id);
    return null;
  }
    catch (Exception e) {
    throw new EJBException(e.getMessage());
  }
}

Si noti l'invocazione del metodo statico createDTO() sull'oggetto ArticleDTOAssembler. Questo era stato già presentato in un articolo precedente della serie ma per semplicità verrà qui riproposto:

public static ArticleDTO createDto(ArticleLocal articleLocal) {
  ArticleDTO articleDTO = new ArticleDTO();
    if (articleLocal != null) {
      articleDTO.setName(articleLocal.getName());
      articleDTO.setAbstractText(articleLocal.getAbstractText());
      articleDTO.setBody(articleLocal.getBody());
      articleDTO.setColumnName(articleLocal.getColumnName());
      articleDTO.setId(articleLocal.getId());
      articleDTO.setIntroducion(articleLocal.getIntroducion());
      articleDTO.setKeywords(articleLocal.getKeywords());
      articleDTO.setNotes(articleLocal.getNotes());
      articleDTO.setSerialName(articleLocal.getSerialName());
      articleDTO.setStatus(articleLocal.getStatus());
      articleDTO.setSubTitle(articleLocal.getSubTitle());
      articleDTO.setTitle(articleLocal.getTitle());
      articleDTO.setImpressions(articleLocal.getImpressions());
      if (articleLocal.getArticlelink() != null){
         articleDTO.setLinkPosition(articleLocal.getArticlelink().getPosition());
          }
      // crea i DTO dipendenti e li lega al DTO da restituire come risposta del metodo
      articleDTO.setFigureDTOs(FigureDTOAssembler.createDtos(articleLocal.getFigures()));
      articleDTO.setIconDTO(IconDTOAssembler.createDto(articleLocal.getIcon()));
     articleDTO.setLightSectionDTO(SectionDTOAssembler.createLightDTO(articleLocal.getSection()));
      articleDTO.setWriterDTOs(WriterDTOAssembler.createDTOs(articleLocal.getWriters()));
      articleDTO.setAttachmentDTOs(AttachmentDTOAssembler.createDtos(
                                                               articleLocal.getAttachments()));
      }
   return articleDTO;
}

Interessante osservare l'uso come nella parte conclusiva del metodo ci si preoccupi di procedere ad assegnare gli oggetti DTO dipendenti (DTO per le figure, icone, sezione, autori, allegati).
Il come siano gestiti gli oggetti dipendenti nel bean e conseguentemente anche nei DTO è un aspetto di cui ci occuperemo nel prossimo articolo.

 


Figura 3 – La sincronizzazione dello stato del bean è una operazione sotto la completa responsabilità del container
EJB che tramite il framework utilizzato gestisce la persistenza delle informazioni memorizzate nell’entity bean.

 

Conclusione
In questo primo articolo dedicato alla persistenza si è iniziato un cammino piuttosto lungo: gli argomenti appena introdotti rappresentano la base per gli approfondimenti delle puntate successive.
E’ importante comprendere lo spirito di questa serie e soprattutto l'obiettivo principale: non un corso di EJB ma una raccolta di indicazioni utili sia in senso assoluto che per sfruttare nel migliore dei modi quanto appreso negli articoli precedenti.
Nel prossimo articolo parleremo di campi di relazione e dell'importante ruolo che ricoprono sia nella gestione delle relazioni fra i vari Entity Beans, sia nel trasferimento dei dati su i DTO.

 

Bibliografia
[EJB] – “Enterprise Java Beans” di Richard Monson-Haefel, Bill Burke, and Sacha Labourey. O'Reilly Media, Inc. ISBN: 059600530X.
[MokaEJB] – Capitolo su EJB del “Manuale Pratico di Java”, II Ed. - volume 2. - http://www.mokabyte.it/libri/manualejava2/
[DTO1] “Architetture e tecniche di progettazione EJB - III parte: la trasmissione dei dati fra gli strati tramite il pattern DTO (introduzione)” di Giovanni Puliti, MokaByte 102 – Dicembre 2005 - http://www.mokabyte.it/2005/12/ejbarchitectures-3.htm

[DTO2] “Architetture e tecniche di progettazione EJB - III parte: la trasmissione dei dati fra gli strati tramite il pattern DTO (approfondimenti)” di Giovanni Puliti, MokaByte 103 - Gennaio 2006 - http://www.mokabyte.it/2006/01/ejbarchitectures-4.htm