Deptective

Build Status JitPack Status

🕵️ Deptective is a plug-in for the Java compiler (javac) that validates the dependencies amongst a project's packages against a description of allowed dependences and fails the compilation when detecting any unintentional dependencies.

Why Deptective?

🕵 In order to implement comprehensible and maintainable software systems, a well-defined structure between their components is needed. While module systems (multi-module builds, the Java Module System, OSGi etc.) are proven means of structuring large code bases, software structure should also be defined and enforced at a lower level, i.e. within individual modules and by defining which APIs should be accessed across module boundaries.

Deptective helps with this task by allowing you to define a software system's structure in terms of intended package relationships (e.g. com.example.service may read com.example.persistence, but not the other way around) and enforcing these relationships at compile time. Implemented as a plug-in for the javac compiler, Deptective will abort the compilation when detecting unwanted package dependencies, allowing you to fix the issue at the earliest time possible. Compared to traditional architecture monitoring tools (that for instance run once nightly on a CI server and whose reports are easy to ignore), hooking right into the compiler itself allows for very fast feedback cycles. The overhead is very low, e.g. less than 1 sec for a medium-sized code base such as Hibernate Validator (740 classes).

The following shows an example when using Deptective via Maven:

Unwanted package relationship causes compilation error

Optionally, you also can visualize the package relationships via GraphViz, highlighting any unwanted relationships. The following shows an example from the Spring PetClinic sample application, which has been modified to have an undesired reference from the model to the visit package:

GraphViz representation of package relationships

Requirements

🕵 JDK 8 or later is needed to run Deptective.

The plug-in is specific to javac, i.e. the compiler coming with the JDK, it does not work with other compilers such as the Eclipse Batch Compiler (ecj). Support for ecj may be added later on.

Deptective can be used with any Java build system such as Maven, Gradle etc.

Usage

🕵 Deptective is configured through a file deptective.json which describes the allowed dependencies amongst the project's packages.

Structure of deptective.json

🕵 The deptective.json file is structured like this:

{
    "components" : [
        {
            "name" : "ui",
            "contains" : [ "com.example.ui" ],
            "reads" : [
                "persistence",
                "service"
            ]
        },
        {
            "name" : "persistence",
            "contains" : [ "com.example.persistence" ]
        },
        {
            "name" : "service",
            "contains" : [ "com.example.service" ],
            "reads" : [
                "persistence"
            ]
        },
        {
            "name" : "rest",
            "contains" : [ "com.example.rest" ],
            "reads" : [
                "persistence",
                "service"
            ]
        }
    ],
    "whitelisted" : [
        "java.io*",
        "java.util*"
    ]
}

components is a list of Component objects. The Component object has the following properties:

whitelisted is a list of strings representing whitelisted packages, i.e. packages that always can be read by any other component. The * character can be used as a wildcard, so e.g. java.util* will whitelist the packages java.util, java.util.concurrent etc.

Note: access to the package java.lang is always allowed.

Place the configuration file in the root of your source directory (e.g. src/main/java for Maven projects) or on the classpath at META-INF/deptective.json (e.g. META-INF/src/main/resources/deptective.json for Maven projects). Alternatively you can specify the location of the config file using the configfile option (see below).

Configuring the Java compiler

🕵 In order to use Deptective, add deptective-javac-plugin-1.0-SNAPSHOT.jar to your project's annotation processor path and specify the option -Xplugin:Deptective when invoking javac.

Apache Maven

🕵 When using Maven, add the following configuration to your pom.xml:

...
<build>
    <plugins>
        <plugin>
            <artifactId>maven-compiler-plugin</artifactId>
            <configuration>
                <showWarnings>true</showWarnings>
                <compilerArgs>
                    <!-- add further options as needed -->
                    <arg>-Xplugin:Deptective reporting_policy=WARN</arg>
                </compilerArgs>
                <annotationProcessorPaths>
                    <path>
                        <groupId>org.moditect.deptective</groupId>
                        <artifactId>deptective-javac-plugin</artifactId>
                        <version>1.0-SNAPSHOT</version>
                    </path>
                </annotationProcessorPaths>
            </configuration>
        </plugin>
    </plugins>
</build>
...

See integration-test/pom.xml for a complete example.

Gradle

🕵 When using Gradle, add the following configuration to your build.gradle:

...
dependencies {
    annotationProcessor 'org.moditect.deptective:deptective-javac-plugin:1.0-SNAPSHOT'
}

tasks.withType(JavaCompile) {
    options.compilerArgs = [
            'Xplugin:Deptective' +
            'mode=VALIDATE' +
            'reporting_policy=ERROR' +
            'visualize=true' +
            "config_file=${projectDir}/src/main/resources/META-INF/deptective.json"
    ]
}
...

See integration-test/build.gradle for a complete example.

Configuration Options

🕵 The following options can be provided when running the plug-in:

Obtaining Deptective via Jitpack

🕵 Deptective is not yet available in Maven Central. For the time being, you can obtain the latest snapshot JARs via Jitpack. Add the following repository to your project's pom.xml or your Maven settings.xml file:

<repositories>
    <repository>
        <id>jitpack</id>
        <name>Jitpack</name>
        <url>https://jitpack.io</url>
    </repository>
</repositories>

Then reference the Deptective JAR using the GAV coordinates com.github.moditect.deptective:deptective-javac-plugin:master-SNAPSHOT.

See jitpack-example/pom.xml for a complete example.

For Gradle, add the repo like this:

allprojects {
    repositories {
        ...
        maven { url 'https://jitpack.io' }
    }
}

Contributing and Development

🕵 In order to build Deptective, OpenJDK 11 or later and Apache Maven 3.x must be installed. Then obtain the source code from GitHub and build it like so:

git clone https://github.com/moditect/deptective.git
cd deptective
mvn clean install

Your contributions to Deptective in form of pull requests are very welcomed. Before working on larger changes, it's recommended to get in touch first to make sure there's agreement on the feature and design.

IDE Set-Up

🕵 To work on the code base in Eclipse, please follow this steps:

  1. Run Eclipse with (at least) Version 2018-12/4.10.0 and make sure it runs with OpenJDK 11
  2. In Eclipse, register an OpenJDK 11 instance ("Preferences" -> "Java" -> "Installed JREs") if not already there
  3. Then run "File" -> "Import..." -> "Maven" -> "Existing Maven Projects" and select the root folder of this repository.
  4. After importing the project, make sure that Java 11 is on the build path of the javac-plugin module (right-click on that module, then "Properties" -> "Java Build Path" -> "Libraries").

Code Style

Please import the Eclipse formatter configuration from etc/eclipse-formatter.xml and enable it when working on the project. Run mvn spotless:format to apply the formatter automatically.

Also enable the following "Save Actions":

Related Work

🕵 Different projects exist that analyze Java package dependencies, validate and/or produce metrics on them. I'm not aware of any tool though that provides instantaneous feedback about any unwanted dependencies right during compilation. Some related tools are:

License

🕵 Deptective is licensed under the Apache License version 2.0.