Chiamate Asincrone con EJB3

Il web è sempre più veloce, o per lo meno deve dare questa percezione a chi lo usa: creare interfacce responsive al pari di quelle desktop è la sfida vinta col massiccio uso di chiamate Ajax che introducono i concetti di “render parziale” della pagina e “chiamate asincrone” rispetto al caricamento dell’intera pagina. Il concetto di asincrono lato server invece non è mai stato nuovo: riuscire ad inviare una richiesta che verrà soddisfatta in un secondo momento permette di distribuire il carico computazionale sul server nel tempo e soprattutto generare risposte veloci verso il client anche quando si richiedono operazioni onerose. La piattaforma Java Enterprise fornisce da tempo soluzioni architetturali o meno che permettono di raggiungere questo scopo. Dalla versione EJB 3.1 poi, è stato introdotto il concetto di EJB Asincrono che semplifica notevolmente le cose.

Chiamate asincrone e code JMS

Fino a ieri, chiamate asincrone al server su Java EE era sinonimo di Message Driven Bean (MDB). Con una chiamata sincrona al server, viene creato e inviato un messaggio ad una coda, dopodiché il controllo torna al client. Su questa coda sono in ascolto uno o più listener (gli MDB appunto) che prelevano il messaggio dalla coda e ne soddisfano le richieste. Abbiamo già discusso ampiamente di questa architettura in diversi post: abbiamo visto a livello teorico quali sono gli attori, come si configura, come si usa e come è possibile recuperare i risultati asincroni da un’altra coda tramite il Request/Reply Pattern.
E’ evidente che non è banale mettere in piedi un’architettura di questo tipo e se si ha bisogno di poche chiamate asincrone è proprio uno spreco di risorse e tempo.

Chiamate asincrone e EJB Timer

Qualcuno si chiederà: ma non basta staccare un semplice thread durante una chiamata e far proseguire il lavoro a lui? La risposta può essere banale, ma la spiegazione no. Diciamo che è meglio evitare di gestire manualmente thread all’interno di un Application Server per non andare incontro ad una morte lenta, ma inesorabile.

Invece che staccare un thread manualmente, possiamo farlo fare ad un EJB Timer Single Action, per esempio, schedulato a scadere al momento della della sua inizializzazione!! Con un semplice trucco, si riesce così a creare una chiamata asincrona eseguita in transazione con pochissimo sforzo : dopo l’inizializzazione del timer, il controllo verrà restituito subito al client e al timeout (ovvero subito) verrà eseguita l’operazione in modo asincrono su un altro thread. Se si tratta di una azione “fire and forget” (cioè che non necessità di feedback), questa soluzione è più che sufficiente, ma se è necessario recuperare il risultato dell’operazione o sapere se questa è andata a buon fine, è necessario inventarsi qualche strada.

Una soluzione potrebbe essere sfruttare la cache distribuita presente in molti Application Server (abbiamo già visto quella di WebSphere per esempio) per memorizzare il risultato restituito dal task asincrono. Con questa soluzione, anche in un ambiente clusterizzato, siamo sicuri che il risultato sia disponibile e univoco per tutti i nodi.

@Stateless
public class AsyncTimerEJB implements AsyncTimerEJBLocal {
   
   private static final Logger logger = 
         Logger.getLogger(AsyncTimerEJB.class);
   
   @EJB
   private TimerResultCacheEJB cacheEJB;
   
   @Resource
    private TimerService service;
   
   @Override
   public void prepareAsyncAction(String id) {
      logger.info("Request thread " + Thread.currentThread().getId());
      this.service.createTimer(0, id);
   }
   
   @Timeout
   public void doAsyncAction(Timer timer) {
      try {
         logger.info("Async thread " + Thread.currentThread().getId());
         TimeUnit.SECONDS.sleep(5);
         
         this.cacheEJB.getAsyncResult().put((String) timer.getInfo(), true);
      } catch (Throwable t) {
         this.cacheEJB.getAsyncResult().put((String) timer.getInfo(), false);
         t.printStackTrace();
      }
   }
   
   @Override
   public Boolean getResult(String id) {
      Boolean result = null;
      if (this.cacheEJB.getAsyncResult().containsKey(id)) {
         result = this.cacheEJB.getAsyncResult().get(id);
         this.cacheEJB.getAsyncResult().remove(id);
      }
      return result;
   }
}
  • TimerResultCacheEJB è un EJB che gestisce la cache distribuita e la presenta ad un client (cioè l’EJB che la usa) come una mappa. Sarà responsabilità del client inserire e rimuovere l’entry dalla cache. Si sarebbe potuto usare allora una semplice HashMap Java? Meglio di no: la cache infatti permette di sfruttare i servizi di aggiornamento, manutenzione e invalidazione che il server opera su di essa. Se un client quindi si dimenticasse di rimuovere un elemento dalla cache, col tempo verrebbe invalidato dal server.
  • prepareAsyncAction(String id) imposta il timer per scadere immediatamente e riceve un parametro, per esempio il nome utente loggato, che farà da chiave alla mappa e che permetterà di recuperare il risultato.
  • doAsyncAction(Timer timer) esegue l’operazione asincrona e inserisce l’esito di esecuzione positiva (true) nella cache se non si sono verificate eccezioni. Se lasciassimo passare le eccezioni dall’EJB, la transazione verrebbe marcata da “rollbackare” e il server tenterebbe di rieseguire l’operazione almeno una volta, come da specifica. Nel mondo reale il numero dei tentativi è indefinito e dipende dall’Application Server in uso: è sempre bene quindi catturare le eccezioni.
  • getResult(String id) restituisce il risultato dell’operazione, rimuovendolo prima dalla cache, se viene trovato.

I nuovi EJB Asincroni

Finché lavoriamo con Java EE 5, una delle soluzioni mostrate è pressoché obbligatoria. Fortunatamente Java EE 6 ha reso più naturale e strutturata quest’esigenza non così recondita dopotutto: EJB 3.1 infatti introduce la nuova annotazione @Asyncronous che permette di rendere asincrona la chiamata ad un intero EJB o solo ad un suo metodo. La firma di un metodo asincrono ha un solo vincolo:

  • se l’azione è di tipo “fire-and-forget“, il tipo ritornato sarà void;
  • se l’azione è di tipo “retrieve-result-later“, il tipo ritornato sarà Future.
@Stateless
public class AsyncEJB implements AsyncEJBLocal {

   @Override
   @Asynchronous
   public Future doLongProcessingAction() {
      long startMillis = System.currentTimeMillis();
      try {
         TimeUnit.SECONDS.sleep(5);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
      long stopMillis = System.currentTimeMillis();
      return new AsyncResult("Elapsed time in millis: " + (stopMillis - startMillis));
   }
}
  • doLongProcessingAction() eseguirà l’operazione in modo asincrono rispetto alla chiamata: il client si troverà un riferimento all’oggetto Future che, una volta conclusa l’operazione, indicherà il tempo che ha impiegato in una stringa.
  • l’oggetto Future è un wrapper del risultato. I suoi metodi più usati sono:
    • cancel(boolean mayInterruptIfRunning): cancella l’esecuzione asincrona;
    • isDone(): controlla che il processo sia terminato e il risultato salvato (è vera anche nel caso in cui sia stato cancellato);
    • get(long timeout, TimeUnit unit): preleva il risultato o attende per timeout unit di tempo;
    • get(): preleva il risultato o attende finché non è pronto (cioè finché isDone() è vera).

Conclusioni

EJB 3.1 permette finalmente di creare processi asincroni rispetto all’elaborazione principale in modo estremamente semplice, soprattutto per quanto riguarda la gestione del risultato dell’operazione. Come abbiamo visto nelle prime due soluzioni, le azioni del tipo “retrieve-result-later” sono sempre quelle più difficili da gestire e richiedono il supporto dei servizi dell’Application Server, come le code o la cache, per poter recuperare i risultati. Con i metodi @Asyncronous, il server sfrutta appieno la potente concurrent API di Java, liberando lo sviluppatore dal problema della gestione dei thread e classi callable, nascondendo il tutto dietro una semplice chiamata EJB. Probabilmente infatti, il codice nascosto dal proxy EJB sarà qualcosa che somiglia a:

...
   ExecutorService threadExecutor = Executors.newSingleThreadExecutor();		
   Future futureResult = threadExecutor.submit(new Callable() {
      @Override
      public String call() throws Exception {
         return asyncEJB.doLongProcessingAction();
      }
   });
...

dove cioè la chiamata al metodo EJB sarà wrappata ed eseguita come task futuro.

Andrea Como

Sono un software engineer focalizzato nella progettazione e sviluppo di applicazioni web in Java. Presso OmniaGroup ricopro il ruolo di Tech Leader sulle tecnologie legate alla piattaforma Java EE 5 (come WebSphere 7.0, EJB3, JPA 1 (EclipseLink), JSF 1.2 (Mojarra) e RichFaces 3) e Java EE 6 con JBoss AS 7, in particolare di CDI, JAX-RS, nonché di EJB 3.1, JPA2, JSF2 e RichFaces 4. Al momento mi occupo di ECM, in particolar modo sulla customizzazione di Alfresco 4 e sulla sua installazione con tecnologie da devops come Vagrant e Chef. In passato ho lavorato con la piattaforma alternativa alla enterprise per lo sviluppo web: Java SE 6, Tomcat 6, Hibernate 3 e Spring 2.5. Nei ritagli di tempo sviluppo siti web in PHP e ASP. Per maggiori informazioni consulta il mio curriculum pubblico. Follow me on Twitter - LinkedIn profile - Google+

  • Gianpll

    ciao. prima di tutto, grazie per questo articolo interessantissimo.
    domanda ralativa a EJB 3.0: secondo te sarebbe possibile usare i callable per rendere generico AsyncTimerEJB, in modo da non dover implementare un bean per ogni task asincrono? 

    • Ciao! Se per usare il Callable intendi fare come l’ultimo esempio nelle conclusioni, mi sento di sconsigliartelo. Staccare thread “manualmente” dall’applicativo nell’application server può causare problemi, come per esempio generare thread morti che rimangono appesi senza mai concludersi. Potresti invece passare al metodo “service.createTimer” l’oggetto che vuoi che venga eseguito, che magari implementa una tua interfaccia con un metodo del tipo “startTask()”. Al timeout lo recuperi da “timer.getInfo()” e chiami il metodo “startTask()”. In questo modo disaccoppi i task dall’esecutore asincrono. Spero di aver risposto alla tua domanda.

  • Paolo

    Ciao. Volevo chiedere un informazione se possibile, sapete se c’è in giro un buon libro su EJB 3.1 in italiano?? cerco da un po di tempo ma nulla…. grazie anticipatamente!!

    • Andrea Como

      Ciao, purtroppo non credo di poterti essere d’aiuto. Personalmente preferisco leggere i manuali tecnici in inglese, per evitare le interpretazioni dei traduttori.
      Un ottimo libro su EJB 3 è “Pro EJB 3” (http://astore.amazon.com/cose07-20/detail/1590596455), non so però se esiste la versione italiana. Per quanto riguarda la specifica 3.1 le cose nuove non sono molte per cui basta qualche documentazione che trovi in rete, che forse trovi anche in italiano. Andrea

  • Pingback: MDB e JMS con JBoss AS 7 | Cose Non Javiste()

  • matfur92

    Ciao! Complimenti, proprio un bel articolo! Non so se posso, ma sarebbe possibile vedere come si trasformerebbe AsyncTimerEJB utilizzando al suo interno Spring?
    Grazie in anticipo e complimenti ancora!