SeamFramework.orgCommunity Documentation
In quasi tutte le applicazioni gestionali il database è il principale collo di bottiglia e lo strato meno scalabile dell'ambiente di esecuzione. Chi utilizza ambienti PHP/Ruby cercherà di sostenere che le architetture cosiddette "shared nothing" (nessuna condivisione) hanno una buona scalabilità. Benché questo possa essere letteralmente vero, non si conoscono molte applicazioni multi utente che possano essere implementate senza la condivisione di risorse tra diversi nodi di un cluster. In effetti ciò a cui questi sprovveduti stanno pensando è un'architettura con "nessuna condivisione eccetto il database". Ovviamente condividere il database è il principale problema di scalabilità di una applicazione multi utente, perciò affermare che questa architettura è altamente scalabile è assurdo e ci dice molto sul tipo di applicazioni sul cui sviluppo questi signori spendono la maggior parte del tempo.
Quasi tutto ciò che riusciamo a fare per condividere il database meno frequentemente è ben fatto.
E questo richiede una cache (memoria tampone). Un'applicazione Seam ben progettata comprenderà una ricca e stratificata strategia di cache che interessi ogni strato dell'applicazione:
Il database, chiaramente, ha la propria cache. Questo è enormemente importante, ma non può scalare come una cache nello strato applicativo.
La soluzione ORM scelta (Hibernate o qualche altra implementazione JPA) ha una cache di secondo livello per i dati provenienti dal database. Questa è una caratteristica molto potente, ma spesso male utilizzata. In un ambiente cluster, mantenere i dati in una cache in modo consistente dal punto di vista delle transazioni con l'intero cluster e con il database, è molto dispendioso. Ha più senso per i dati condivisi tra molti utenti e che vengono aggiornati raramente. Nelle architetture tradizionali senza stato, spesso si cerca di usare la cache di secondo livello per conservare lo stato di una conversazione. Questo è sempre un male ed è particolarmente sbagliato in Seam.
Il contesto Conversation di Seam è una cache per lo stato della conversazione. I componenti che vengono messi nel contesto conversation posso mantenere lo stato relativo all'interazione dell'utente.
In particolare, un persistence context gestito da Seam (o un persistence context EJB gestito dal container associato ad uno stateful session bean legato ad una conversazione) agisce con una cache per i dati che sono stati letti nella conversazione attuale. Questa cache tende ad avere una frequenza di utilizzo piuttosto alta! Seam ottimizza la replica dei persistence context gestiti da Seam in un ambiente cluster e non c'è bisogno di curare la consistenza transazionale con il database (il lock ottimistico è sufficiente) perciò non è necessario preoccuparsi troppo per le implicazioni sulle prestazioni di questa cache, a meno che non si leggano migliaia di oggetti con un singolo persistence context.
L'applicazione può conservare uno stato non transazionale nell'application context di Seam. Lo stato mantenuto nell'applicazione context ovviamente non è visibile agli altri nodi del cluster.
L'applicazione può conservare uno stato transazionale usando il componente cacheProvider
di Seam, il quale si integra con JBossCache, JBoss POJO Cache oppure EHCache nell'ambiente Seam. Questo stato risulterà visibile agli altri nodi se la cache gestisce il funzionamento in modalità cluster.
Infine Seam consente di conservare dei frammenti generati di una pagina JSF. A differenza della cache di secondo livello dell'ORM, questa cache non viene automaticamente invalidata quando i dati cambiano, perciò è necessario scrivere il codice applicativo necessario per realizzare una invalidazione esplicita, oppure importare degli opportuni criteri di scadenza.
Per ulteriori informazioni sulla cache di secondo livello è necessario fare riferimento alla documentazione della soluzione ORM scelta poiché si tratta di un argomento estremamente complesso. In questo paragrafo verrà affrontato l'uso diretto della cache, tramite il componente cacheProvider
o come cache dei frammenti di pagina, tramite il controllo <s:cache>
.
Il componente cacheProvider
fornito dal framework gestisce una istanza di:
org.jboss.cache.TreeCache
org.jboss.cache.Cache
org.jboss.cache.aop.PojoCache
net.sf.ehcache.CacheManager
E' possibile mettere con sicurezza nella cache qualsiasi oggetto Java immutabile; esso verrà conservato nella cache e replicato nel cluster (assumendo che la replica sia gestita e abilitata). Se si vuole mantenere degli oggetti mutabili nella cache occorre leggere la documentazione della cache sottostante per scoprire come notificare la cache dei cambiamenti negli oggetti.
Per usare cacheProvider
è necessario includere nel progetto i jar dell'implementazione della cache:
jboss-cache.jar
- JBoss Cache 1.4.1
jgroups.jar
- JGroups 2.4.1
jboss-cache.jar
- JBoss Cache 2.2.0
jgroups.jar
- JGroups 2.6.2
jboss-cache.jar
- JBoss Cache 1.4.1
jgroups.jar
- JGroups 2.4.1
jboss-aop.jar
- JBoss AOP 1.5.0
ehcache.jar
- EHCache 1.2.3
Se si sta usando JBoss Cache in un container diverso da JBoss Application Server, verificare sul wiki di JBoss Cache per le ulteriori dipendenze.
Se l'applicazione Seam viene assemblata in un EAR si raccomanda di inserire direttamente nell'EAR la configurazione e i jar della cache.
Sarà inoltre necessario fornire un file di configurazione per JBossCache. Posizionare treecache.xml
con un'opportuna configurazione di cache nel classpath (ad esempio nel jar ejb oppure in WEB-INF/classes
). JBossCache ha molte impostazioni ostiche e terribili perciò non verranno discusse qui. Per ulteriori informazioni fare riferimento alla documentazione di JBossCache.
E' possibile trovare un treecache.xml
di esempio in examples/blog/resources/treecache.xml
.
EHCache senza un file di configurazione funzionerà nella sua configurazione di default.
Per modificare il file di configurazione in uso, configurare la cache in components.xml
:
<components xmlns="http://jboss.com/products/seam/components"
xmlns:cache="http://jboss.com/products/seam/cache">
<cache:jboss-cache-provider configuration="META-INF/cache/treecache.xml" />
</components
>
A questo punto è possibile iniettare la cache in qualsiasi componente Seam:
@Name("chatroomUsers")
@Scope(ScopeType.STATELESS)
public class ChatroomUsers
{
@In CacheProvider cacheProvider;
@Unwrap
public Set<String
> getUsers() throws CacheException {
Set<String
> userList = (Set<String
>) cacheProvider.get("chatroom", "userList");
if (userList==null) {
userList = new HashSet<String
>();
cacheProvider.put("chatroom", "userList", userList);
}
return userList;
}
}
Se nell'applicazione si vogliono avere più configurazioni della cache basta usare components.xml
per configurare più componenti cacheProvider
:
<components xmlns="http://jboss.com/products/seam/components"
xmlns:cache="http://jboss.com/products/seam/cache">
<cache:jboss-cache-provider name="myCache" configuration="myown/cache.xml"/>
<cache:jboss-cache-provider name="myOtherCache" configuration="myother/cache.xml"/>
</components
>
L'uso più interessante della cache in Seam è la tag <s:cache>
, che è la soluzione offerta da Seam per il problema di conservare in cache frammenti di pagine JSF. Internamente <s:cache>
usa pojoCache
, perciò è necessario seguire i passi elencati in precedenza prima di poterla usare (mettere i jar nell'EAR, sistemare le terribili opzioni di configurazione, ecc).
<s:cache>
viene usata per conservare quei contenuti generati che cambiano raramente. Ad esempio la pagina di benvenuto del nostro blog mostra le voci del blog più recenti:
<s:cache key="recentEntries-#{blog.id}" region="welcomePageFragments">
<h:dataTable value="#{blog.recentEntries}" var="blogEntry">
<h:column>
<h3
>#{blogEntry.title}</h3>
<div>
<s:formattedText value="#{blogEntry.body}"/>
</div>
</h:column>
</h:dataTable>
</s:cache
>
La chiave (key
) consente di avere più versioni in cache di ogni frammento di pagina. In questo caso c'è una versione in cache per ogni blog. La regione (region
) determina la cache o il nodo region in cui tutte le versioni verranno conservate. Diversi nodi possono avere diverse regole di scadenza (queste sono le cose che si impostano con le summenzionate terribili opzioni di configurazione).
Ovviamente il grosso problema di <s:cache>
è che è troppo semplice sapere quando i dati contenuti cambiano (ad esempio quando il blogger pubblica uno nuovo contenuto). Così è necessario eliminare i frammento in cache manualmente:
public void post() {
...
entityManager.persist(blogEntry);
cacheProvider.remove("welcomePageFragments", "recentEntries-" + blog.getId() );
}
In alternativa, se non è critico che le modifiche siano immediatamente visibili agli utenti, è possibile impostare un tempo di scadenza breve nel nodo della cache.