Home | Blog | Impress | Data Protection Policy

Validate your software structure with jQAssistant

March 20, 2019, written by Torsten Mosis, torsten.mosis@systemticks.de

Intro

In this blog post I want to show a way how it can be checked if the actual code structure of a software project reflects the intended architecture.

This apporach is based on a tool called jQAssistant, which allows the definition and validation of project specific rules on a structural level. It is built upon the graph database Neo4j.

I will demonstrate the procedure by applying its principles on the project EB solys. We as systemticks are mainting this project, which is based on Eclipse RCP and developed for performing system runtime analysis.

Download and install

A self-contained jQAssistant executable can be downloaded from here. Just follow the installation steps. It contains Neo4j as a built-in dependency.

In case you want to retrace the subsequent examples on your own you can also download the sources from EB solys and build it with maven.

git clone https://github.com/Elektrobit/eb-solys
cd src/com.elektrobit.ebrace.releng.ui.ecl.aggregator
mvn clean verify

Scan your software artifacts

How do we start?

In a first phase we need to acquire the relevant data of our software project. This will be the basis for all following analysis and validation measures.

This step is called scanning and gathers all important project artifacts, such as source code items like classes, interfaces, methods, fields, etc., but also valuable metadata like the content of maven pom files or manifest files.

All those items will be represented as nodes in the database. In addition the dependencies between the items are stored as relationships, like a class implements an interface or a package contains classes.

Thus the entire software project will be mapped into a coherent graph where all scanned artifacts are interconnected with each other.

neo4j 2

The scanning procedure can be triggered via command line or embedded into the maven build process. I have done the latter option and put the following code into the plugins section of our parent pom file.

A detailed description can be found in the user manual.

<plugin>
  <groupId>com.buschmais.jqassistant</groupId>
  <artifactId>jqassistant-maven-plugin</artifactId>
  <version>${jqassistant.version}</version>
  <executions>
  <execution>
    <id>default</id>
    <goals>
    <goal>scan</goal>
    <goal>analyze</goal>
    </goals>
    <configuration>
    <warnOnSeverity>INFO</warnOnSeverity>
    <failOnSeverity>MAJOR</failOnSeverity>
    <store>
      <uri>file:../../jqassistant/store</uri>
    </store>
    <useExecutionRootAsProjectRoot>true</useExecutionRootAsProjectRoot>
    </configuration>
  </execution>
  </executions>
</plugin>

Exploring your data

Now since the scanning process has finished all our project artifacts are now accessible with the underlying graph database.

The database server can be started from the command line.

jqassistant.cmd server -storeURI file:<PATH_TO_YOUR_DB>

Then the database can be reached as a local web-service on port 7474. Just open a browser of your choice and enter following URL:

http://localhost:7474

Check for unimplemented Interfaces

How can we make use of the database to explore our scanned software structure?

Let’s write a simple query to check if we have interfaces without any implementation in our project.

MATCH
  (i:Java:Interface) -[:DECLARES]-> (m:Method {visibility: 'public'})
WHERE NOT
  (:Type)-[:IMPLEMENTS]->(i)
RETURN
  i.fqn, count(m) as methods
ORDER BY
  methods DESC

Neo4j queries are expressed based on the cypher language, which is inspired by ascii art. I had no previous experience with cypher, but it is very expressive and easy to read.

In short, the query above means:

Give me all interfaces, which declare public methods and are not implemented by any other class or interface (type). And then sort the result set by the number of methods per interface.

You can copy+paste this query into your browser and execute it.

neo4j 1

VoilĂ . We have some findings. Obviously something we need to work on. :)

Enrich your scanned data

This was already a good example to play with the data model, cypher and the Neo4j browser.

But the real power comes with the facility to add project specific semantics or architecture paradigms to your data model.

Let me explain this by an example:

In our project we have introduced an architectural layer named Use Cases for encapsulating and implementing all of the use cases of our application. An Use Case, like a facade, has the goal to make a complex subsystem easier to use. It is providing a simple interface for a set of OSGi services in the subsystem.

Now we can simply add this project-specific concept to your data model, by tagging all interfaces which implement the BaseUseCase interface with a label named UseCase:

You can execute following query in your browser:

MATCH
  (uc:Java:Interface) -[:IMPLEMENTS]-> (i:Java:Interface {name: 'BaseUseCase'})
SET
  uc:UseCase
RETURN
  u AS UseCase

Check for violated naming conventions

We can now perform some very simple checks regarding the compliance of naming conventions, by making use of the new label UseCase.

One rule e.g. is, that the developers need to adhere to that all Use Case Interfaces should have a prefix with either Interaction or Notify.

This can be checked with following constraint:

MATCH
  (u:UseCase)
WHERE NOT
  u.name =~ ".*(Interaction|Notify)UseCase"
RETURN
  u AS UseCaseWithWrongNaming

The same rule should also apply for the implementation of Use Cases:

MATCH
  (c:Java:Class) -[:IMPLEMENTS]-> (u:UseCase)
WHERE NOT
  c.name =~ ".*(Interaction|Notify)UseCaseImpl"
RETURN
  c AS UseCaseImplWithWrongNaming

And a more complex constraint could look like this:

MATCH
  (creator:Java:Class) -[:DECLARES]-> (m:Method) -[:INVOKES]-> (c:Constructor) <-[:DECLARES]- (impl:Java:Class) -[:IMPLEMENTS]-> (u:UseCase)
WHERE NOT
  (creator.name ENDS WITH 'Test' OR creator.name = 'UseCaseFactoryServiceImpl')
RETURN
  creator AS

Any idea what it does?

It validates that the constructor of an Use Case implementation is called either by the Use Case Factory or a Test. In other words, it should not be allowed to invoke a use case constructor from any other class.

Conclusion

I hope this blog post was helpful for getting a first impression about the potential of jQAssistant and its facilities for validating your project specific architecture rules. We at least will use this environment as another piece of the puzzle to improve our quality.

The queries from our example above for enriching and validating the data are called concepts and contraints in terms of jQAssistant. They are usually not executed manually in the browser - like I did for the purpose of demonstration - but embedded into your maven build as well.

Means all those checks and validations can be executed in your CI environment, including the generating of corresponding reports or even breaking the build in case of violating the rules.

This is what we will do next and probably describe in an upcoming blog post.

Feel free to contact me via mail torsten.mosis@systemticks.de, in case you have questions or if you need any further support.