Hallo zusammen,
ich hatte ja versprochen, hier im Blog über den Fortschritt meines Demoprojektes zu berichten.
Das Demoprojekt ist meine kleine Spielwiese, die ich verwende, um mir neue Technologien anzusehen. Das Ganze ist eine Enterprise-Anwendung bestehend aus u.a folgenden Technologien in nicht sortierter Reihenfolge:
- EJB3.1
- Spring 2.5
- Maven2
- Glassfish V3
- Apache Wicket
- JRebel
- EasyMock
- Scala
- Git
- JPA / Hibernate
Der Sinn dieser Artikelreihe soll zum Einen Dokumentation für mich selbst sein, um spezielle Konfigurationen etc. irgendwo zu dokumentieren, andererseits soll es ebenfalls einen Einstieg in die Java Enterprise Entwicklung geben und so neue Entwickler möglichst schnell an den Produktiv zu bringen. Das Projekt befindet sich aktuell im Alpha-Stadium, wird aber kontinuierlich weiterentwickelt und hier im Blog dokumentiert.
Im Folgenden soll zunächst mal demonstiert werden, wie das ganze Projekt ausgecheckt werden kann (hierfür ist das DVCS Git notwendig), das ganze ist gehostet bei GitHUB.
Auschecken kann man das Ganze mit:
git clone git@github.com:dilgerma/wicket-ejb-maven-prototype.git wicket-prototype
Zusätzlich ist ein TestUtil Projekt von mir verwendet, das hier ausgecheckt werden kann:
git clone git@github.com:dilgerma/TestUtil.git TestUtil
Dann sollte man zunächst das TestUtil-Projekt bauen:
cd TestUtil
mvn clean install
Im Folgenden wird jetzt die grundlegende Struktur des Wicket-Prototypen demonstriert. Das Ganze ist wie bereits erwähnt ein Prototyp dessen Middletier auf EJB3.1 basiert. Das Frontend basiert aus Apache Wicket. Als Bindeglied zwischen Frontend und Middletier kommt Spring 2.5 zum Einsatz. Hierfür kommt ausserdem die Bibliothek Wicket-Spring zum Einsatz, da diese die Integration enorm vereinfacht.
Die Struktur der Maven-Module ist in folgendem Bild dargestellt.
Im Folgenden werde ich auf die Besonderheiten aller POMs ein wenig genauer eingehen.
Auf der untersten Hierarchiestufe gibt es ein Base-Pom. In diesem sind alle Dependencies festgelegt, die in allen hiervon abgeleiteten POMs ebenfalls benötigt werden.
…
<groupId>de.md</groupId>
<artifactId>base</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<name>base</name>
…
Dieses POM ist prinzipiell eher uninteressant, nichts desto trotz will ich ein wenig näher auf einige Details eingehen:
Viele Dependencies sind durch Dependency-Management deklariert:
…
<dependencyManagement>
<dependencies>
<dependency>
<groupId>javax.persistence</groupId>
<artifactId>persistence-api</artifactId>
<version>1.0</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
…
Man muss sich hierbei klar machen, das in Maven jede Abhängigkeit an die abgeleiteten Module vererbt wird, ich hätte jetzt also auch Problemlos das Ganze nicht im Berech Dependency-Management sondern als direkte Dependency deklarieren können.
…
<dependencies>
<dependency>
<groupId>javax.persistence</groupId>
<artifactId>persistence-api</artifactId>
<version>1.0</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
…
Nachteil hiervon wäre gewesen, dass wirklich alle Module (und tatsächlich erben alle Applikations-Maven-Module vom Base-POM) eine Abhängigkeit zur Java Persistence API hätten. Das kann und wird nicht die Lösung sein. Das andere Extremum wäre gewesen, die Dependency auf die JPA in jedem Modul explizit zu deklarieren. Das würde prinzipiell funktionieren, wäre aber ein Albtraum, sobald einige Module zusammengekommen sind. Man stelle sich nur mal vor, man müsste von JPA 1.o auf 2.0 upgraden, man müsste das POM von jedem Modul einzeln anfassen und die Version hochzählen, brrrr….
Eine elegante Lösung bietet das Maven Dependency Management.
Die Abhängigkeiten werden nicht explizit sondern implizit vererbt, d.h. ich deklariere eine Abhängigkeit wie weiter oben beschrieben mit Version etc. einmalig im Base-POM im Bereich Dependency Management, und definiere dann in jedem POM, das diese Dependency benötigt nur noch folgendes:
…
<dependency>
<groupId>javax.persistence</groupId>
<artifactId>persistence-api</artifactId>
</dependency>
…
Der Unterschied sollte klar sein, man muss hier keine Version, keinen Scope, keinen Typ mehr definieren, weil dies bereits alles im Base-POM definiert ist. Käme ich jetzt also in die Situation, dass ich die Version der JPA inkrementieren müsste, würde ich dies im Base-POM tun und automatisch würden alle abhängigen Module, die eine Abhängigkeit auf die JPA deklariert haben die neue Version anziehen. Sehr elegant, sehr schön.
Natürlich bietet sich hier auch an, die Compile-Einstellungen etc. vorzunehmen, ich habe mich für JDK1.6 entschieden, das Ganze funktioniert so:
…
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
…
Zusätzlich habe ich die Generierung der rebel.xml für die Verwendung mit JRebel hier konfiguriert, so dass für jedes Abhängige Maven-Modul die Rebel.xml automtisch mit generiert wird.
…
<plugin>
<groupId>org.zeroturnaround</groupId>
<artifactId>javarebel-maven-plugin</artifactId>
<executions>
<execution>
<id>generate-rebel-xml</id>
<phase>process-resources</phase>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
</plugin>
…
JRebel selbst werde ich irgendwann in nächster Zeit in einem eigenen Artikel vorstellen.
Wie man bereits etwas weiter oben gesehen hat sind folgende Module direkt vom Base-POM abhängig:
…
<modules>
<module>webtier</module>
<module>ear</module>
<module>ejb</module>
</modules>
…
Werfen wir einen Blick in das EJB-Pom:
…
<groupId>de.md</groupId>
<artifactId>ejb</artifactId>
<packaging>ejb</packaging>
<version>1.0-SNAPSHOT</version>
…
Man beachte das Packaging EJB.
Das einzig interessante hier ist prinzpiell die Konfiguration des Maven-EJB-Plugins:
…
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-ejb-plugin</artifactId>
<configuration>
<ejbVersion>3.0</ejbVersion>
<generateClient>true</generateClient>
<clientExcludes>
<clientExclude>**/impl/**</clientExclude>
</clientExcludes>
</configuration>
</plugin>
</plugins>
</build>
…
Durch die Konfiguration kann man sich ein „virtuelles“ Maven-Modul erzeugen lassen, einen EJB-Client, der die API für die EJB-Services enthält, ohne dass man hierfür explizit ein eigenes
Projekt erstellen müsste. Eine wie ich finde sehr nützliche Art und Weise, einen EJB-Client zu erstellen. Die Konfiguration gibt hierbei an, dass alle Klasse, die nicht in einem Package **/impl… vorkommen im ejb-client enthalten sein sollen. Der EJB-Client kann auf folgende Art und Weise referenziert werden:
<dependency>
<groupId>de.md</groupId>
<artifactId>ejb</artifactId>
<version>1.0-SNAPSHOT</version>
<type>ejb-client</type>
</dependency>
Also groupId, artifactId etc. direkt vom EJB-Projekt, nur der type ist ejb-client statt ejb, das wars schon.
Werfen wir einen Blick ins Webtier-POM:
…
<groupId>de.md</groupId>
<artifactId>webtier</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
…
Am Packaging pom erkennt man direkt, dass es sich hier wiederum um ein Multimodule-Projekt handelt (also quasi ein weiteres Base-POM für das Webtier), warum wir diese weitere Indirektion brauchen werden wir gleich sehen.
Auch dieses Pom ist wiederum relativ uninteressant, das einzig interessante hierbei ist die Verwendung von Maven Profilen. Ein Maven Profil gibt mir als Entwickler quasi die Möglichkeit, jede beliebige Einstellung im POM durch eine Profilspezifische Einstellung zu überschreiben. Warum brauche ich das hier?
Um das Layout für das Frontend zu entwickeln brauche ich prinzipiell die ganzen EJB-Serviecs nicht. Der Roundtrip ist einfach zu gross (Datenbankanbindung, Application Server, Datenbank befüllen) etc..
Das Ganze geht viel einfacher, wenn man es über Mock-Implementierungen löst. Ich habe also eine Abstraktion eingeführt, die sich Service-Connector nennt. Prinzipiell ist das nichts anderes als ein weiteres Maven-Modul, das für die Produktiv-Umgebung nichts weiter als eine Spring-Context-Defintion enthält.
Die Spring-Context Definition ist quasi für das Laden der EJBs zuständig, das Frontend weiß also prinzipiell gar nicht, dass es mit EJBs arbeitet, sondern das Frontend (speziell Wicket) wird direkt durch Spring bedient. Der Trick hierbei ist, das so während der Entwicklung völlig problemlos die EJB-Implementierung durch eine dumme Mock-Implementierung ersetzt werden kann, und zwar habe ich hierfür einenn Service-Mock-Connector geschrieben (ein weiteres Maven-Modul mit Spring-Context-Definition und Mock-Implementierungen, die zwar die Business-Interfaces der EJBs implementieren aber weder Datenbank noch Applicationserver benötigen, sondern beispielsweise auch direkt in einem Tomcat deployed werden könnten). Dem Frontend ist das wie bereits erwähnt völlig egal, da die Beans direkt aus Spring kommen und ob das nun echte EJBs oder nur Mocks sind ist völlig transparent.
Wie aber kann ich nun bestimmen, ob meine Anwendung mit Mocks oder mit echten EJBs arbeitet? Ganze einfach, in dem ich das entsprechende Connector-Modul in meine Anwendung einbinde. Und genau diesen Use-Case löst man üblicherweise mit Maven-Profilen, die Definition eines Profiles sieht man im Folgenden:
…
<profiles>
<profile>
<id>default</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<modules>
<module>service-connector</module>
</modules>
</profile>
…
Zunächst habe ich ein Modul „Default“ definiert, das per Default aktiv ist (activeByDefault..), und ein Modul „Service-Connector“ definiert. D.h. wenn ich mein Maven-Projekt mit „mvn clean install“ baue, ist dieses Profil aktiv und deklariert die Abhängigkeit zum Modul „service-connector“. Dies ist quasi mein Produktiv-Profil, da das Modul Backend-Connector die Abhängigkeiten zu den richtigen EJBs deklariert.
Für die Arbeit mit den Mocks definiere ich jetzt ein weiteres Profil:
…
<profile>
<id>mock</id>
<modules>
<module>mock-service-connector</module>
</modules>
</profile>
…
Das Profil kann mit folgendem Befehl beim Bauen aktiviert werden „mvn clean install -Pmock“, baue ich das Projekt mit diesem Befehl wird das Profil „default“ deaktiviert und stattdessen das Profil „mock“ aktiviert, d.h. ich habe von jetzt an keine Abhängigkeit mehr zum Modul „service-connector“ sondern zum Modul „mock-service-connector“. Und durch die Definition der Module werden eben genau auch nur diese gebaut, d.h. im Fall „-Pmock“ ist das (Jar-)Modul „service-connector“ später überhaupt nicht in meiner deploybaren Anwendung vorhanden.
Die Poms zu den Connectoren sind völlig unspannend, deswegen lasse ich die hier mal aussen vor und wir werfen einen letzten Blick in das POM für die Webanwendung:
…
<groupId>de.md</groupId>
<artifactId>web</artifactId>
<packaging>war</packaging>
<version>1.0-SNAPSHOT</version>
…
Auch im Web-Projekt benötigen wir nun natürlich die entsprechenden Profile, denn nach der Moduldeklaration muss das Web-Projekt die entsprechenden Connectoren natürlich noch als Abhängigkeit deklarieren. Dies folgt dem selben Prinzip wie zuvor erläutert, so dass hier das entsprechende XML sprechend genug sein sollte:
<profiles>
<profile>
<id>default</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<dependencies>
<!– Enable EJB Deployment by default –>
<dependency>
<groupId>de.md</groupId>
<artifactId>backend-connector</artifactId>
</dependency>
</dependencies>
</profile>
<profile>
<id>mock</id>
<dependencies>
<!– Enable Mocks–>
<dependency>
<groupId>de.md</groupId>
<artifactId>mock-backend-connector</artifactId>
</dependency>
</dependencies>
</profile>
</profiles>
Der Witz bei der Sache ist jetzt, dass man nun ohne Probleme das komplette Frontend in einem Webcontainer testen kann ohne dass man eine EJB Container braucht. Ich verwende hierfür gerne den Jetty-Webserver und das entsprechende Maven-Plugin.
So kann ich die Anwendung in einem Rutsch deployen und testen ohne grossen Roundtrip. Hierzu wird einfach in der pom für das Webprojekt folgende Konfiguration in das bereits vorhandene Mock-Profil eingetragen:
<build>
<plugins>
<plugin>
<groupId>org.mortbay.jetty</groupId>
<artifactId>maven-jetty-plugin</artifactId>
<configuration>
<scanIntervalSeconds>5</scanIntervalSeconds>
<connectors>
<connector implementation=“org.mortbay.jetty.nio.SelectChannelConnector“>
<port>8080</port>
<maxIdleTime>60000</maxIdleTime>
</connector>
</connectors>
</configuration>
</plugin>
</plugins>
</build>
Wenn man jetzt die Webanwendung mit „mvn clean install -Pmock jetty:run“ baut, startet automatisch der
Jetty-Webserver und man kann direkt testen.
So, das sollte die Wichtigsten Punkte der Maven Konfiguration abgedeckt haben, ich hoffe es war ein wenig interessant, speziell für Entwickler mit noch wenig Erfahrung mit Maven. Im nächsten Teil des Artikels werde ich näher auf den Ansatz mit den Service-Connectoren und deren Verschaltung mit Spring eingehen, bis dahin freue ich mich auf Kommentare.