Skip to content

A plug-in for the Java compiler that validates a project's package relationships

License

Notifications You must be signed in to change notification settings

nilshartmann/deptective

 
 

Repository files navigation

Deptective

🕵️ 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:

{
    "packages" : [
        {
            "name" : "com.example.foo",
            "reads" : [
                "com.example.bar",
                "com.example.baz"
            ]
        },
        {
            "name" : "com.example.bar",
            "reads" : [
                "com.example.baz"
            ]
        }
    ],
    "whitelisted" : [
        "java.util*",
        "java.swing*"
    ]
}

packages is a list of Package objects. The Package object has a name property (fully-qualified name of the described package) and a reads property (list of strings representing the fully-qualified names of other packages read by the given package).

whitelisted is a list of strings representing whitelisted packages, i.e. packages that always can be read by any other package. 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 -Adeptective.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>
                    <arg>-Xplugin:Deptective</arg>
                    <!-- specify options like so -->
                    <!-- <arg>-Adeptective.reportingpolicy=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',
            '-Adeptective.mode=VALIDATE',
            '-Adeptective.reporting_policy=ERROR',
            '-Adeptective.visualize=true',
            "-Adeptective.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:

  • -Adeptective.config_file=path/to/deptective.json: Path of the configuration file in the file system
  • -Adeptective.reporting_policy=(ERROR|WARN): Whether to fail the build or just raise a warning when spotting any illegal package dependencies (defaults to ERROR; make sure to set <showWarnings> to true when using the plug-in via Maven)
  • -Adeptective.unconfigured_package_reporting_policy=(ERROR|WARN): Whether to fail the build or just raise a warning when detecting a package that's not configured in the config file (defaults to WARN)
  • -Adeptective.mode=(ANALYZE|VALIDATE): Whether the plug-in should validate the packages of the compiled package against the deptective.json file (VALIDATE) or whether it should generate a template for that file based on the current actual package relationships (ANALYZE). The latter can be useful when introducing Deptective into an existing code base where writing the configuration from scratch might be too tedious. Generating the configuration from the current "is" state and iteratively refining it into an intended target state can be a useful approach in that case. The generated JSON/DOT files are created in the compiler's class output path, e.g. target/classes in case of Maven. Defaults to VALIDATE
  • -Adeptective.whitelisted=...: A comma-separated list of whitelist package patterns which will be applied in ANALYZE mode. Any reference to a whitelisted package will then not be added to the reads section of the referencing package in the generated descriptor template. The special value *ALL_EXTERNAL* can be used to automatically whitelist all packages which are not part of the current compilation (i.e. packages from dependencies). This can be useful if you're only interested in managing the relationships amongst the current project's packages themselves but not the relationships to external packages.
  • -Adeptective.visualize=(true|false): Whether to create a GraphVize (DOT) file representing generated configuration template (in ANALYZE mode) or the dependency configuration and (if present) any illegal package dependencies (in VALIDATE mode). Defaults to false

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").

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:

  • ArchUnit aims at enforcing architectures described in a Java DSL. In contrast to Deptective it is not executed during compilation but via (JUnit) tests.
  • JDepend analyzes Java packages and produces metrics on them.
  • The Eclipse Java compiler allows to put access restrictions in place but they can only be used to limit access to types/packages in other JARs on the classpath, not to packages of the current compilation unit itself
  • code-assert allows to enforce dependency rules described in a Java DSL. Similarly to ArchUnit, these rules are executed as unit tests.
  • Macker enforces architectural rules at build time via dedicated build tool plug-ins. (not actively maintained)

License

🕵 Deptective is licensed under the Apache License version 2.0.

About

A plug-in for the Java compiler that validates a project's package relationships

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Java 99.9%
  • Shell 0.1%