java - style - spring layered architecture




Enforcing layered architecture in Java (10)

For software structuring, you need to take advantage of the best coding practices and design patterns. I have outline below few points that will definitely help.

  1. Creation of object(s) should be done only in the specialized Factory class(es)
  2. You should code-to and expose only the necessary "interfaces" between layers
  3. You should take advantage of the package scope (default one) class visibility.
  4. If necessary you should split your code into separate sub-projects and (if needed) create separate jar(s) to assure proper inter-layer dependency.

Having a good system design would complete and exceed your goal.

Given a software system written in Java consisting of three layers, A -> B -> C, i.e. layer A uses layer B and B uses layer C.

I want to make sure that a class of one layer only has access only to classes of the same layer or its direct dependency, i.e. B should be able to access C but not A. Also A should be able to access B but not C.

Is there an easy way to enforce such a restriction? Ideally I want eclipse to complain at once if one tries to access a class of the wrong layer.

The software currently uses maven. Therefore I tried to put A, B, and C into different maven modules and to declare dependencies properly. This works fine to prevent B to access A, but does not prevent A to access C.

Next I tried to exclude C from the dependency to B. This now also prevents access from A to C. However now I am no longer able to use copy-dependencies to collect all transitive dependencies needed for run time.

Is there a good way that allows me a clean separation of layers, but also allows me to collect all needed runtime dependencies?


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.


I'll suggest something that I've never actually tried myself -- writing unit tests with JDepend to verify architectural dependencies. JDepend documentation gives an example of this as a "Dependency Constraint Test". The two major caveats are

  1. I haven't seen any adoption of this practice in the community,
  2. The JDepend project seems to be abandoned.

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.

Looks like you're trying to do something that maven does out of the box.

If module A depends on B with an exclude C clause, C classes are not accessible in A without the explicit dependency on C. But they are there for B, since B depends on them directly.

Then when you're packaging your solution, you run assembly or whatever on module R, which is the parent of A, B and C, and collects their dependencies effortlessly.


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?


The best solution known to me is Structure101 software. It allows you to define rules about your code dependencies and to check them right in the IDE or during your build.


Why not simply use different projects for each layer? You put them into your workspace and manage build dependencies as you want.


You can define access rules for classpath artifacts in Eclipse. Access rules may be used to map a pattern e.g. "com.example.*" to a resolution, e.g. "Forbidden". This leads to a compiler warning when an import to a restricted location is defined.

While this works very well for small code sets, defining access rules can be very tedious on larger projects. Please keep in mind that this is a proprietary Eclipse feature and thus access rules are stored in the Eclpise specific project configuration.

To define access rules follow this clickpath: Project Properties > Java Build Path > Libraries > [Your Library or Maven Module] > Access Rules > Click "Edit"

Access rules may also be defined globally in the Settings menu.


You can describe your architecture using Sonargraph's new DSL:

artifact A
{
  // Pattern matching classes belonging to A
  include "**/a/**"
  connect to B
}  
artifact B
{
  include "**/b/**"
  connect to C
}
artifact C
{
  include "**/c/**"
}

The DSL is described in a series of BLOG articles.

Then you can run Sonargraph via Maven or Gradle or similar in your build and make the build fail when rule violations occur.







dependencies