Enforcing layered architecture in Java
Maybe you can try this in the pom of A:
<dependency>
<groupId>the.groupId</groupId>
<artifactId>moduleB</artifactId>
<version>1.0</version>
<exclusions>
<exclusion>
<groupId>the.groupId</groupId>
<artifactId>moduleC</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>the.groupId</groupId>
<artifactId>moduleC</artifactId>
<version>1.0</version>
<scope>runtime</scope>
</dependency>
Can this help you?
If I was you I would do the following steps:
- For each layer create two modules. One for interfaces another for the implementation.
- Do a proper maven dependency avoiding transitive dependencies.
- Install Sonargraph-Architect plugin in eclipse. It will let you to configure your layer rules.
in maven you can use the maven-macker-plugin as following example:
<build>
<plugins>
<plugin>
<groupId>de.andrena.tools.macker</groupId>
<artifactId>macker-maven-plugin</artifactId>
<version>1.0.2</version>
<executions>
<execution>
<phase>compile</phase>
<goals>
<goal>macker</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
and here is an example macker-rules.xml example file: (put it on the same level as your pom.xml)
<?xml version="1.0"?>
<macker>
<ruleset name="Layering rules">
<var name="base" value="org.example" />
<pattern name="appl" class="${base}.**" />
<pattern name="common" class="${base}.common.**" />
<pattern name="persistence" class="${base}.persistence.**" />
<pattern name="business" class="${base}.business.**" />
<pattern name="web" class="${base}.web.**" />
<!-- =============================================================== -->
<!-- Common -->
<!-- =============================================================== -->
<access-rule>
<message>zugriff auf common; von überall gestattet</message>
<deny>
<to pattern="common" />
<allow>
<from>
<include pattern="appl" />
</from>
</allow>
</deny>
</access-rule>
<!-- =============================================================== -->
<!-- Persistence -->
<!-- =============================================================== -->
<access-rule>
<message>zugriff auf persistence; von web und business gestattet</message>
<deny>
<to pattern="persistence" />
<allow>
<from>
<include pattern="persistence" />
<include pattern="web" />
<include pattern="business" />
</from>
</allow>
</deny>
</access-rule>
<!-- =============================================================== -->
<!-- Business -->
<!-- =============================================================== -->
<access-rule>
<message>zugriff auf business; nur von web gestattet</message>
<deny>
<to pattern="business" />
<allow>
<from>
<include pattern="business" />
<include pattern="web" />
</from>
</allow>
</deny>
</access-rule>
<!-- =============================================================== -->
<!-- Web -->
<!-- =============================================================== -->
<access-rule>
<message>zugriff auf web; von nirgends gestattet</message>
<deny>
<to pattern="web" />
<allow>
<from>
<include pattern="web" />
</from>
</allow>
</deny>
</access-rule>
<!-- =============================================================== -->
<!-- Libraries gebunden an ein spezifisches Modul -->
<!-- =============================================================== -->
<access-rule>
<message>nur in web erlaubt</message>
<deny>
<to>
<include class="javax.faces.**" />
<include class="javax.servlet.**" />
<include class="javax.ws.*" />
<include class="javax.enterprise.*" />
</to>
<allow>
<from pattern="web" />
</allow>
</deny>
</access-rule>
<access-rule>
<message>nur in business und persistence erlaubt</message>
<deny>
<to>
<include class="javax.ejb.**" />
<include class="java.sql.**" />
<include class="javax.sql.**" />
<include class="javax.persistence.**" />
</to>
<allow>
<from>
<include pattern="business" />
<include pattern="persistence" />
</from>
</allow>
</deny>
</access-rule>
</ruleset>
</macker>
and in a simple multi module maven project simply put the macker-rules.xml in a central place and point to the directory where it is stored. then you need to configure the plugin in your parent pom.xml
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>de.andrena.tools.macker</groupId>
<artifactId>macker-maven-plugin</artifactId>
<version>1.0.2</version>
<executions>
<execution>
<phase>compile</phase>
<goals>
<goal>macker</goal>
</goals>
<configuration>
<rulesDirectory>../</rulesDirectory>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</pluginManagement>
</build>
Hmmmm - interesting. I've certainly run into this problem before, but have never tried to implement a solution. I'm wondering if you could introduce interfaces as an abstraction layer - something similar to the Facade pattern and then declare dependencies on that.
For example, for layers B, and C, create new maven projects that contain just the interfaces into those layers, let's call those projects B' and C'. Then, you would declare dependencies to just the interface layer, rather than the implementation layer.
So A would depend on B' (only). B would depend on B' (because it would implement the interfaces declared there) and C'. Then C would depend on C'. This would prevent the "A uses C" problem, but you would not be able to get the runtime dependencies.
From there, you would need to use maven scope tags to get the runtime dependencies (http://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html). This is the part that I really haven't explored, but I think you could use a 'runtime' scope to add the dependencies. So you would need to add A depends on B (with runtime scope) and similarly, B depends on C (with runtime scope). Using runtime scope will not introduce compile-time dependencies, so that should avoid reintroducing the "A uses C" problem. However, I'm not sure if this will provide the full transitive dependency closure that you are looking for.
I'd be very interested to hear if you can come up with a working solution.