CDI für Rich Clients

MyFaces CODI zum Ersten: @Transactional

Ein weiteres Problem war die Transaktionsbehandlung in der Persistenz- und der Serviceschicht. Da in den DAOs alle datenmanipulierenden Methoden in einer Transaktion abgehandelt werden sollten, die darüber liegende Serviceschicht allerdings mehrere DAO-Operationen in einer Transaktion zusammenfassen musste und wir darüber hinaus das Transaktionsmanagement aufgrund der hohen Fehlergefahr nicht manuell machen wollten, benötigten wir eine allgemeine Lösung. Diese fanden wir im JPA-Modul von Apache MyFaces CODI [5]. Dieses Modul stellt die Annotation @Transactional zur Verfügung, die unter anderem auch in Spring zu finden ist [6]. Methoden, die mit @Transactional annotiert sind, werden in einer Transaktion ausgeführt. Wenn die Methode eine Exception wirft, wird die Transaktion zurückgerollt. Andernfalls wird sie committet. Dabei wird allerdings nicht für jede @Transactional-Methode eine neue Transaktion erstellt und sofort wieder beendet, sondern nur für die äußerste dieser Methoden im Aufrufbaum. Alle inneren Methoden verwenden einfach die bereits geöffnete Transaktion weiter. Dieser Mechanismus erlaubt somit ein komplett deklaratives Transaktionsmanagement, das wir wie folgt verwendeten: Jegliche Methoden in einem DAO, die Daten manipulieren, annotierten wir mit @Transactional. Zudem annotierten wir jegliche Methoden in der Serviceschicht mit @Transactional, die mehrere logisch zusammengehörende, datenmanipulierende DAO-Operationen hintereinander ausführen. Damit hatten wir ein einfaches, voll funktionsfähiges Transaktionsmanagement mit sehr niedrigem Fehlerpotenzial. Intern arbeitet @Transactional übrigens mithilfe eines CDI-Interceptors, der – wie bereits angesprochen – nur in Klassen mit Proxy-fähigen Scopes (also in allen außer Dependent und Singleton) funktioniert.

MyFaces CODI zum Zweiten: Project Stages

Da wir bis zu diesem Zeitpunkt auf einer gemeinsamen Entwicklungsdatenbank arbeiteten, die natürlich kaum einen gleichbleibenden Datenstand aufwies, hatten wir bei unseren Tests das Problem, keinen definierten Ausgangszustand zur Verfügung zu haben. Somit war es unmöglich, automatisierte Systemtests zu erstellen. Um das Problem zu beseitigen, entschieden wir uns, Project Stages zu verwenden. Das Konzept der Project Stages wurde von JSF 2.0 eingeführt, um auf den aktuellen Deployment-Status der Applikation (z. B. Production, Development, Systemtest etc.) Rücksicht nehmen zu können. So werden in JSF 2.0 beispielsweise erweiterte Debugging-Informationen ausgegeben, wenn die Applikation im Entwicklungsmodus läuft [7]. Die ursprüngliche Idee dazu stammt allerdings von Ruby on Rails [8]. MyFaces CODI hat dieses Konzept von JSF 2.0 übernommen und um einige CDI-spezifische Features erweitert [9]. Beispielsweise erlaubt es, die Annotation @ProjectStageActivated, bestimmte CDI-Beans nur für gewisse Project Stages zu aktivieren. Somit entschlossen wir uns, mehrere Datenbanken aufzusetzen und anhand der aktuellen Project Stage zu entscheiden, welche verwendet werden soll. Dazu erstellten wir für jede Datenbank einen EntityManager Producer, d. h. eine Klasse, die den EntityManager der zugehörigen Datenbank erzeugt, konfiguriert und über eine Methode, die mit @Produces annotiert ist, zur Verfügung stellt. Diese Klassen annotierten wir anschließend mit @ProjectStageActivated und den entsprechenden Project Stages; in unserem Fall Production, Development bzw. SystemTest. In der Project Stage Production wurde die produktive Datenbank eingebunden und nicht weiter verändert. In der Development Stage wurde eine In-Memory-Datenbank gestartet und mit wohldefinierten Testdaten aus einer SQL-Datei befüllt. Die gleiche In-Memory-Datenbank wurde auch in der Project Stage SystemTest gestartet. Sie blieb allerdings leer, um in den individuellen Testfällen mit den benötigten Daten befüllt werden zu können. Listing 7 zeigt den EntityManager Producer für die Project Stage Development.

Listing 7
@ApplicationScoped
@ProjectStageActivated(ProjectStage.Development.class)
public class DevelopmentEntityManagerProducer {
    
    private static final String TEST_DATA_FILE = "META-INF/data/testData.sql";

    @PersistenceContext(unitName = "ticketline-development")
    protected EntityManager entityManager;
    
    @PostConstruct
    private void insertTestData()
    {
        // lese TEST_DATA_FILE und führe SQL-Statements am EntityManager aus
    }

    @Produces 
    public EntityManager getEntityManager() {
        return entityManager;
    }

    protected void disposeEntityManager(@Disposes EntityManager entityManager) {
        if (entityManager.isOpen()) {
            entityManager.close();
        }
    }
}

Am Ende hatten wir bei jedem Neustart im Entwicklungsmodus einen definierten Datenzustand, nämlich den aus unserer SQL-Datei. Zusätzlich konnten wir ohne großen Aufwand für jeden Testfall exakt die benötigten Daten in die In-Memory-Datenbank einfügen. So fiel es uns sehr leicht, beliebige Szenarien in den Testfällen durchzuspielen, ohne an der Entwicklungsdatenbank (oder noch schlimmer, an der Produktionsdatenbank) etwas ändern zu müssen. Die zu verwendende Project Stage zum Starten des Projekts mussten wir dabei mittels des VM-Arguments –Dorg.apache.myfaces.extensions.cdi.ProjectStage=<ProjectStage> angeben.

Zusammenfassung

Trotz einiger Anfangsschwierigkeiten ist es uns gelungen, Apache OpenWebBeans erfolgreich in einem Eclipse-RCP-Projekt zu verwenden. Durch die Dependency Injection war es uns möglich, das Projekt gut zu strukturieren und eine klare Schichtentrennung vorzunehmen. Darüber hinaus konnten wir uns mithilfe der zahlreichen Features, die OWB und MyFaces CODI zur Verfügung stellen, viel Arbeit ersparen, einige Bereiche des Projekts besser wartbar machen und zudem übersichtlicher gestalten. Alle Klassen und alle Konfigurationsdateien, die für eine erfolgreiche Integration von OpenWebBeans in Eclipse RCP benötigt werden, sind in unserem Open-Source-Projekt unter [10] verfügbar.

Jakob Korherr arbeitet als Softwareentwickler bei IRIAN Solutions in Wien. Gleichzeitig studiert er Software and Information Engineering an der TU Wien. Er ist Apache MyFaces Committer und PMC Member sowie JSF 2.2 Expert Group Member. Jakob bloggt auf http://www.jakobk.com.
Kommentare

Schreibe einen Kommentar

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