Modularisierung von GWT Anwendungen

By | 14. Juli 2011

Es macht viel Sinn eine GWT Anwendung zu modularisieren. Denn, was mit GWT richtig gut gelingt, ist die Wiederverwendung bereits geschriebenen Codes. Und, da der GWT Compiler nur referenzierte Klassen übersetzt, braucht man sich keine Sorgen über Code zu machen, der sich zwar im Modul befindet aber nicht verwendet wird. Das ist eine die gute Nachricht.

Ich habe mir ein Szenario ausgedacht, das veranschaulichen soll, wie die Modularisierung von GWT Projekten funktionieren kann; und ich setze dabei auf Maven. Insbesondere auf das GWT Maven Plugin.

Modul Abhängigkeiten

Modul Abhängigkeiten

  • API Ist ein Modul, dass Transferobjekte, Service-Interfaces und Command-Objekte enthält. Also der kleinste gemeinsame Nenner von Server und Client. Dieses Modul hat keine Abhängigkeiten zu GWT oder sonstigen Bibliotheken. Es enthält nur Klassen, die sowohl der Client, als auch der Server kennen müssen. So dass dieses Modul praktisch auch für andere Clients genutzt werden kann. Ich denke dabei an eine iPhone oder Android App.
  • GWT-Base Ist das solide Fundament weiterer GWT Module. Es kennt GIN, einen EventBus, eine RPC Schnittstelle, allgemeine Events, Fehlerhandling, Mocks, Testklassen, usw. Also alles das, was eine GWT Anwendung grundsätzlich benötigt und für das jedes mal ein neues Setup notwendig wäre.
  • GWT-Shop enthält die Basisfunktionalität des Shopsystems.
  • Die weiteren Module sind dann die konkrete Anwendungen.

Der Anfang – das Wurzelprojekt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<?xml version="1.0" encoding="UTF-8"?>
<project ...>
  <groupId>com.example.client</groupId>
  <artifactId>root</artifactId>
  <packaging>pom</packaging>
  <version>1.0</version>
 
  <modules>
    <module>api</module>
    <module>gwt-base</module>
    <module>gwt-shop</module>
    <module>gwt-...</module>
    <module>...</module>
  </modules>
 
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.7</version>
      <scope>test</scope>
    </dependency>
    ...
  </dependencies>
 
  <properties>
    <projectVersion>0.0.3</projectVersion>
    <gwtVersion>2.3.0</gwtVersion>
    <maven.compiler.source>1.6</maven.compiler.source>
    <maven.compiler.target>1.6</maven.compiler.target>
  </properties>
 
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>2.0.2</version>
        <configuration>
          <source>${maven.compiler.source}</source>
          <target>${maven.compiler.target}</target>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-site-plugin</artifactId>
        <version>2.0-beta-6</version>
        <configuration>
          <inputEncoding>UTF-8</inputEncoding>
          <outputEncoding>UTF-8</outputEncoding>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

Ich beginne mit dem Wurzelprojekt und definiere die groupId, die artifactId und die version. Das packaging muss pom sein, um Maven zu erklären, dass es Untermodule gibt, die sich auf dieses Wurzelprojekt beziehen.

Zudem kommen die einzelnen Module hinzu. Dann kann ich hier bereits die Möglichkeit nutzen Abhängigkeiten anzugeben, die in allen anderen Modulen ebenfalls benötigt werden. z.B. jUnit. Auch welche Compiler-Version zu verwenden ist und welches Encoding. Alles das, was nur einmal gemacht werden sollte und was für die Untermodule ebenfalls gilt.

Zum Schluss definiere ich noch eine Eigenschaft, die ich mit projectVersion benenne und als Wert eine Versionsnummer vergebe. Wozu die ist, dazu komme ich gleich.

Das API Modul

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<?xml version="1.0" encoding="UTF-8"?>
<project ...>
  <groupId>com.example.client</groupId>
  <artifactId>api</artifactId>
  <version>${projectVersion}</version>
 
  <parent>
    <groupId>com.example.client</groupId>
    <artifactId>root</artifactId>
    <version>1.0</version>
  </parent>
 
  <build>
    <resources>
      <resource>
        <directory>src/main/java</directory>
        <includes>
          <include>**/*.java</include>
        </includes>
      </resource>
      <resource>
        <directory>src/main/resources</directory>
        <includes>
          <include>**/*.xml</include>
        </includes>
      </resource>
    </resources>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-source-plugin</artifactId>
        <version>2.1.2</version>
        <executions>
          <execution>
            <id>attach-sources</id>
            <phase>verify</phase>
            <goals>
              <goal>jar-no-fork</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

Das Wurzelprojekt steht, so dass ich mich nun an das API Modul machen kann. Das es sich um ein reines Java-Modul ohne weitere Abhängigkeiten handelt, gibt es hier nur drei Besonderheiten, auf die ich aufmerksam machen möchte:

  1. Die Version des Moduls bekommt den Wert aus der in dem Wurzelprojekt definierten ${projectVersion}-Variable. In Maven gibt es leider nicht die Möglichkeit auf die Version des übergeordneten Projekts zu verweisen (Die Verwendung von ${parent.version} als Version ist nicht erlaubt). Andererseits möchte ich nicht für jedes Modul eine eigene Version pflegen, deshalb diese Krücke.
  2. Da der GWT Compiler den Quellcode seiner Abhängigkeiten kennen muss, nutze ich das maven-source-plugin, um alle Quellen (*.java) mit in das Archiv zu packen.
  3. Damit GWT dieses Modul als ein GWT-Modul erkennt, muss noch die Api.gwt.xml definiert werden und sich ebenfalls im Archiv befinden. Die Moduldefinition muss dabei lediglich den Packagenamen der für GWT sichtbaren Quellen enthalten:

1
2
3
4
<?xml version="1.0" encoding="UTF-8"?>
<module rename-to='client-api'>
  <source path='client'/>
</module>

Das GWT-Base Modul

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
<?xml version="1.0" encoding="UTF-8"?>
<project ...>
  <groupId>com.example.client</groupId>
  <artifactId>gwt-base</artifactId>
  <packaging>jar</packaging>
  <version>${projectVersion}</version>
 
  <parent>
    <groupId>com.example.client</groupId>
    <artifactId>root</artifactId>
    <version>1.0</version>
  </parent>
 
  <dependencies>
    <dependency>
      <groupId>com.example.client</groupId>
      <artifactId>api</artifactId>
      <version>${projectVersion}</version>
    </dependency>
    <dependency>
      <groupId>com.google.gwt</groupId>
      <artifactId>gwt-user</artifactId>
      <version>2.3.0</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>com.google.gwt</groupId>
      <artifactId>gwt-servlet</artifactId>
      <version>2.3.0</version>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupId>com.google.gwt.inject</groupId>
      <artifactId>gin</artifactId>
      <version>1.5.0</version>
    </dependency>
    <dependency>
      <groupId>com.google.inject</groupId>
      <artifactId>guice</artifactId>
      <version>3.0</version>
    </dependency>
    <dependency>
      <groupId>com.google.inject.extensions</groupId>
      <artifactId>guice-servlet</artifactId>
      <version>3.0</version>
    </dependency>
  </dependencies>
 
  <build>
    <resources>
      <resource>
        <directory>src/main/java</directory>
        <includes>
          <include>**/*.java</include>
        </includes>
      </resource>
      <resource>
        <directory>src/main/resources</directory>
        <includes>
          <include>**/*.xml</include>
        </includes>
      </resource>
    </resources>
 
    <plugins>
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>gwt-maven-plugin</artifactId>
        <version>2.3.0</version>
        <configuration>
          <compileReport>true</compileReport>
          <modules>
            <module>com.example.client.gwtbase.Base</module>
          </modules>
          <compileSourcesArtifacts>
            <compileSourcesArtifact>com.example.client:api</compileSourcesArtifact>
          </compileSourcesArtifacts>
        </configuration>
      </plugin>
 
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-source-plugin</artifactId>
        <version>2.1.2</version>
        <executions>
          <execution>
            <id>attach-sources</id>
            <phase>verify</phase>
            <goals>
              <goal>jar-no-fork</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
 
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-jar-plugin</artifactId>
        <version>2.2</version>
        <executions>
          <execution>
            <id>add-test-files</id>
            <goals>
              <goal>test-jar</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
 
  <repositories>
    <repository>
      <id>gin</id>
      <url>http://repo2.maven.org/maven2/</url>
    </repository>
  </repositories>
</project>

Jetzt wird es spannend, denn es geht um das erste richtige GWT Modul. Folgendes ist wichtig:

  1. Es wird die Abhängigkeit zum API Modul definiert.
  2. Es werden weitere Abhängigkeiten zu GWT, GIN und Guice definiert.
  3. Das GWT Maven Plugin kommt zum Einsatz und wird so konfiguriert, dass die Quellen des API-Moduls gezogen und mit übersetzt werden. Siehe compileSourcesArtifacts in der POM und mehr dazu gibt es hier.
  4. Dieses Modul wird ebenfalls mit den Quellen in das Maven Repository gepackt. Darüber hinaus werden auch noch die Quellen aus den Testverzeichnissen mitgenommen. Der Grund ist, auf diese Weise kann bereits hier die Testumgebung mit Mocks und Basis-Testklassen aufgebaut werden. Da Guice als DI Framework verwendet wird, bietet es sich an Guice-TestModule zu implementieren und sie dann in den anderen GWT Modulen wieder zu verwenden.

Wie die GIN und Guice Konfiguration für das Base-Modul aussieht, erkläre ich in einem anderen Post.

Weitere GWT Module

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
<?xml version="1.0" encoding="UTF-8"?>
<project>
  <artifactId>gwt-shop</artifactId>
  <version>${projectVersion}</version>
 
  <parent>
    <groupId>com.example.client</groupId>
    <artifactId>gwt</artifactId>
    <version>1.0</version>
  </parent>
 
  <dependencies>
    <dependency>
      <groupId>com.example.client</groupId>
      <artifactId>gwt-base</artifactId>
      <version>${projectVersion}</version>
    </dependency>
    <dependency>
      <groupId>com.example.client</groupId>
      <artifactId>gwt-base</artifactId>
      <version>${projectVersion}</version>
      <type>test-jar</type>
    </dependency>
    <dependency>
      <groupId>com.googlecode.gwtquery</groupId>
      <artifactId>gwtquery</artifactId>
      <version>1.0.0</version>
      <classifier>2.3.0</classifier>
      <scope>provided</scope>
    </dependency>
  </dependencies>
 
  <build>
    <plugins>
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>gwt-maven-plugin</artifactId>
        <version>2.3.0</version>
        <executions>
          <execution>
            <goals>
              <goal>compile</goal>
              <goal>test</goal>
              <goal>generateAsync</goal>
            </goals>
          </execution>
        </executions>
        <configuration>
          <modules>
            <module>com.example.client.gwtshop.Shop</module>
          </modules>
          <compileSourcesArtifacts>
            <compileSourcesArtifact>com.example.client:api</compileSourcesArtifact>
            <compileSourcesArtifact>com.example.client:gwt-base</compileSourcesArtifact>
          </compileSourcesArtifacts>
 
          <inplace>true</inplace>
          <webappDirectory>${gwt.war.dir}</webappDirectory>
          <warSourceDirectory>${gwt.war.dir}</warSourceDirectory>
        </configuration>
      </plugin>
    </plugins>
  </build>
 
  <repositories>
    <repository>
      <id>gwtquery-plugins</id>
      <url>http://gwtquery-plugins.googlecode.com/svn/mavenrepo</url>
    </repository>
  </repositories>
 
  <properties>
    <gwt.loglevel>INFO</gwt.loglevel>
    <gwt.war.dir>${basedir}/../../GRAILS_ROOT/web-app</gwt.war.dir>
  </properties>
</project>

Das GWT-Shop Modul soll hier das letzte sein. Aber auch aus dieser pom.xml gibt es was zu lernen:

  1. Zum einen definiert dieses Modul wie erwartet eine Abhängigkeit zu dem GWT-Base Modul. Aber diese Abhängigkeit existiert zwei mal. Einmal für die eigentlichen Quellen, und das zweite mal für die Test-Quellen. Das wird mit dem <type>test-jar</type> erreicht. Auf diese Weise kann auf die Testklassen des Basismoduls zugegriffen werden.
  2. Dann gibt es in der Konfiguration des GWT Maven Plugins ein executions Block, der dafür sorgt, dass in der Übersetzungsphase nicht nur der Java-Code übersetzt wird, sondern auch der GWT-Compiler anschließend anspringt und das JavaScript Kompilat erzeugt.
  3. Unter compileSourcesArtifacts werden nicht nur die API Quellen gezogen und in die Übersetzung mit einbezogen, sondern auch die Quellen des GWT-Base Moduls.
  4. Ich baue meine Anwendungen mit TeamCity. Das Deployment funktioniert so, dass zuerst die GWT Anwendungen übersetzt werden, dann die Grails Anwendung übersetzt und gepackt und anschließend in ein Tomcat deployed wird. Das alles geschiet automatisch und da bietet es sich an, die GWT Kompilate direkt in ein Verzeichnis der Grails Anwendung zu kopieren, damit es automatisch in das WAR-Archiv kopiert wird. Das geschieht mit der Angaben von webappDirectory und warSourceDirectory, die auf das Zielverzeichnis in der Grails Anwendung zeigen.

So, wenn ich alles richtig gemacht und nichts vergessen habe, dann sollte ein mvn clean install auf dem Wurzelprojekt zuerst das API-Modul und dann das GWT-Base Modul in das lokale Maven Repository installieren. Anschließend sollte das GWT-Shop Modul kompilieren. Und wenn das alles hingehauen hat und kein Test fehlgeschlagen ist, sollte sich das Kompilat in einem Verzeichnis innerhalb der Grails Anwendung befinden. Erste Sahne 🙂

Weiter geht es in einem anderen Post um das Setup des GWT-Moduls. Ich werde zeigen wie GIN aufgesetzt und die „Ginjectoren“ über mehrere Module hinweg organisiert werden können. Außerdem geht es um RPC und den EventBus…

5 thoughts on “Modularisierung von GWT Anwendungen

  1. andi

    Vielen Dank für den Post. Wir haben ein ähnliches Setup, jedoch ziehen wir über das „build-helper-maven-plugin„ die Sourcen aus den Subprojekten an. Das jedoch auch nur, wenn ein Dev-Profil im Maven gesetzt ist. Ohne das Profil wird dann noch „compileSourcesArtifacts„ interessant.

    Was mir sehr geholfen hat, ist der Tip mit „${projectVersion}„ und die „execution„-„goal„s.

    Andi

    PS: unser Blog ist im Aufbau 😉

  2. Sergej Post author

    Vielen Dank Andi. Ich werde mal bei euch im Blog vorbeischauen.

  3. Alexander

    Äußerst interessanter Blogeintrag.
    Ist denn PartII mit dem Setup, GIN und dem EventBus noch geplant? Würde mich sehr interessieren.

    Gruß Alexander

  4. Sergej Post author

    Hallo Alexander,

    vielen Dank! Ich habe mal eine Weiterführung geplant, aber einfach vergessen. Manchmal braucht es eben ein Kommentar als Anstupser;) Also… coming soon…

  5. iuiz

    Thx for this. The source code would be great. I am now trying two days to get a similar setup done and there are always some small bugs. GWT and Maven is currently a painfull thing. The poms get just too bloated with plugin stuff.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.