Komplexe konfigurierbare Webanwendungen mit der JBoss Suite

Webanwendungen mit der JBoss Suite: Lessons Learned

Christian Groth

Nun, da Seam 2 der Vergangenheit angehört und sich alle freudig auf Seam 3, CDI und/oder auf Weld (die Referenzimplementierung von CDI) stürzen, scheint ein guter Zeitpunkt für einen Rückblick zu sein. Dieser Artikel behandelt eine komplexe Webanwendung, die mit Seam 2 umgesetzt wurde und weitere interessante Technologien wie JBoss jBPM , JBoss Rules und Hibernate integriert. Vor allem sollen Erfahrungen zu Stolpersteinen und Problemen weitergegeben werden, die die Entwickler einige Nerven gekostet haben.

Mit einem neuen Projekt bei Null auf „der grünen Wiese“ anzufangen, ist oft Fluch und Segen zugleich. Nachdem zumindest die grundlegenden Anforderungen geklärt sind, gilt es, sich für konkrete Technologien und Produkte zu entscheiden. Angesichts der Vielfalt der Möglichkeiten ist dieses im Java-EE-Umfeld keine leichte Aufgabe. Das Ziel war, eine möglichst dynamische, d. h. konfigurierbare, Webanwendung zur Unterstützung des Backoffice und der Customer-Care-Abteilung eines Kabelnetzbetreibers zu realisieren. Neben dem Kerngeschäft der Kundenauskunft und Vertragsabwicklung stand vor allem das dynamische Grundkonzept der Anwendung im Fokus. Es sollten Möglichkeiten geschaffen werden, die wichtigsten Komponenten der Applikation (Datenbankschema und Entitäten, Teile der Businesslogik sowie Oberflächen) weitestgehend zur Laufzeit konfigurierbar zu gestalten. Als Kerntechnologien wurden die folgenden Produkte eingesetzt:

  • JBoss Application Server 4.2.2.GA
  • JBoss Seam 2.0.2.SP1
  • JBoss Rules 4.0.7
  • JBoss jBPM 3.2.2
  • Hibernate 3.2.5.GA
  • JSF 1.2 mit Facelets 1.1.4 und Richfaces 3.3.2.GA

Der Einsatz vieler Produkte und Frameworks von JBoss schien hier am erfolgversprechendsten, da diese Produkte mit dem Webframework Seam zumindest schon mal unter einen Hut gebracht wurden. Dieser Artikel soll und will aber weder eine Einführung in die verwendeten Technologien geben, noch deren Konzepte anhand von Beispielen erklären. Vielmehr sollen anhand eines mittlerweile produktiven Projekts die Erfahrungen und Fallstricke aufgezeigt und weitergegeben werden. Die aus heutiger Sicht veralteten Versionen kommen durch eine Projektlaufzeit von inzwischen fast drei Jahren zustande.

JEE hoch konfigurierbar

Wir wollen diesen Artikel mit einem kurzen Systemüberblick (Abb. 1) zu dem entworfenen Produkt beginnen. Als Persistenzframework wird Hibernate via JPA eingebunden, jedoch haben wir noch einen zusätzlichen Layer darüber gelegt, um der Forderung nach dynamisch zur Laufzeit adaptierbaren Entitäten gerecht werden zu können. Ohne zu sehr in die Details der technischen Realisierung abzutauchen, sei gesagt, dass es sich dabei im Prinzip um persistente Dynabeans handelt. Das Datenbankschema wird zur Laufzeit von DDLUtils angepasst und durch ein dynamisch erzeugtes Hibernate Mapping verfügbar gemacht. In Drools kann dank MVEL transparent auf die Attribute zugegriffen werden und für die View-Schicht wurde ein erweiterter EL-Resolver implementiert.

Die Schicht der Geschäftslogik ist mit
Seam
[1], EJB und JBoss Rules [2] realisiert worden. Wir benutzen die Regeln, um konfigurierbare Parameter, aber auch Geschäftslogik auszulagern und das Verhalten des Systems so zur Laufzeit anpassen zu können. Der Zugriff auf eine konkrete Regeldatei erfolgt immer über einen Service, der den konkreten Kontext zur Verfügung stellt. Für das Hot-Deployment der Regeln haben wir eine einfache Komponente (Rules Access, Abb. 1) geschrieben, weil eine Integration der damaligen Guvnor-Version in keinem Verhältnis zum Nutzen stand.

Schließlich folgt als oberste Schicht noch die Ebene der Views und Pageflows. Durch ein Exploded EAR-Deployment ist es hier sehr einfach möglich, Änderungen an den XHTML-Seiten zur Laufzeit übernehmen zu können, ohne den Applikationsserver herunterfahren zu müssen.

Abb. 1: Systemüberblick
Erfahrungen und Fallstricke

Alleine das vorherige Kapitel kann mit den darin verborgenen Konzepten Lesestoff für einen ganzen Artikel bieten. Jedoch besteht das Ziel dieses Artikels nicht darin, eine komplexe Systemarchitektur bis ins letzte Bit darzustellen, sondern die bei der Umsetzung entstandenen Probleme zu identifizieren und mögliche Lösungen bzw. Lösungswege dafür anzubieten. In diesem Projekt liegt das Hauptaugenmerk aus technologischer Sicht auf dem Webframework Seam. Es wird in allen Schichten außer der Persistenzschicht verwendet und trägt so maßgeblich zur Systemarchitektur bei.

Komplexes Conversation Handling mit Seam

Mit Seam erhalten die so genannten Conversations Einzug in Webanwendungen. Eine Conversation bildet einen Kontext, der langlebiger als der Page- aber kurzlebiger als der Session-Scope ist. Die Idee ist, einen mehrschrittigen Ablauf mit einem separaten Kontext versorgen zu können. Darüber hinaus können Nested Conversations definiert werden. Das sind Kind-Elemente einer anderen Conversation und haben dennoch ihren eigenen Kontext. Der Kontext der Vater-Conversation ist nur lesend zugreifbar. Achtung, auch hier lauert schon eine kleine Falle: Lesender Zugriff bedeutet, dass keine Objekte in den Kontext der Vater-Conversation eingefügt bzw. daraus entfernt werden können. Eine Manipulation der Bindungen von symbolischen Namen zu den Objekten ist demnach nicht möglich, sehr wohl jedoch die Veränderung der Kontextobjekte an sich.

Die Standardnavigationsmethoden einer Seam-Webanwendung sehen nun vor, dass neben einer neuen Zielseite auch eine (!) Aktion bezüglich der Conversations durchgeführt werden kann. Die Seitenführung im konkreten Projekt verlangte allerdings komplexere Conversation-Aktionen bei einem Seitenübergang. So sollte z. B. die aktuelle Nested Conversation verlassen und in der darüber liegenden Vater-Conversation eine neue Nested Conversation gestartet werden – also sozusagen eine Schwester-Conversation. Dieses Problem lässt sich mit Seam-Board-Mitteln bei nur einem Seitenwechsel leider nicht lösen. Wenn es also nach Seam geht, müsste man zunächst mit einem Seitenwechsel die aktuelle Conversation beenden und dann mit einer expliziten Aktion die neue starten – schade!

Der Versuch, das Conversation Handling zu kapseln und programmatisch zu steuern, führte leider nicht zum gewünschten Erfolg. Es war zwischenzeitlich eine Komponente entstanden, die solche komplexen Aktionen ermöglichte, allerdings war der Entwicklungsaufwand dafür übermäßig hoch. Schließlich mussten wir feststellen, dass Seam mit seinem internen Zustand öfter mal „durcheinander kam“, wenn solche komplexen Operationen bei nur einem Seitenwechsel erfolgten. Das äußerte sich leider nur in kuriosen Fehlern, sodass das Problem erst sehr spät erkannt wurde. Inzwischen sind wir bezüglich der Navigationsmöglichkeiten der Anwendung wieder zu den Seam-Basismitteln zurückgekehrt und haben die selbst implementierte Lösung in den virtuellen Papierkorb verschoben.

Extended Persistence Context

Weitere blutige Nasen haben wir uns im Verlauf des Projekts mit der Kombination aus dem Extended Persistence Context und der Seam Conversation geholt. Erstes definiert eine Datenbanksession, die über mehrere Datenbanktransaktionen hinweg geöffnet bleibt – in unserem Fall für den Zeitraum einer kompletten Conversation. Das bringt den Vorteil, dass Hibernate-Entitäten im ganzen Workflow durchgängig benutzt werden können und auch Lazy Loading kein Problem mehr darstellt. Hierzu haben wir eine Stateful Session Bean implementiert, die gleichzeitig Seam-Komponente im Conversation Scope ist. Diese EJB referenziert dann den entsprechenden Persistence Context mit dem entsprechenden Typ, wie in Listing 1 zu sehen ist

Listing 1
@Stateful
@Name("dataAccessService")
@Scope(ScopeType.CONVERSATION)
[...]
public class DataAccessServiceStateful extends DataAccessServiceBase {
  [.]
  @PersistenceContext(unitName ="...", type = PersistenceContextType.EXTENDED)
  EntityManager entityManager;
  [...]
}

Probleme gibt es allerdings an den Stellen, wo bereits geladene Entitäten zwischen verschiedenen Conversations weitergegeben werden. Wird mit solch einer Entität gearbeitet und es kommt dann zu Veränderungen an diesem Objekt, so passiert es schnell, dass eine Entität mit zwei offenen Sessions verbunden ist. Aus diesem Grund müssen alle Entitäten, die in eine Conversation hinein gereicht und für mehr als nur reine Anzeigezwecke verwendet werden sollen, neu aus der Datenbank geladen werden, natürlich mit der aktuellen Session der Conversation. Ein etwas verstecktes Problem lauert in solchen Situationen, auch wenn die übergebene Entität nicht verändert wird. Sind z. B. bestimmte Referenzen dieser Entität noch nicht initialisiert und werden diese bei einem Zugriff via Lazy Loading aufgelöst, kann es genau dann Probleme geben, wenn die ursprüngliche Conversation, in der diese Entität geladen wurde, nicht mehr existiert. Falls das der Fall ist, ist auch die dort zugeordnete Datenbanksession bereits geschlossen und ein Lazy Loading ist in dieser Situation nicht mehr möglich. Für Nested Conversations besteht dieses Problem jedoch nicht, da die Vater-Conversation nicht geschlossen sein darf, wenn die Kind-Conversation noch aktiv ist.

Geschrieben von
Christian Groth
Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.