Demonstrates how Architecture Decision Records can be enhanced with jQAssistant.
Note
|
This tutorial has been written for jQAssistant 1.8.0 |
1. Prerequisites
-
Any Java application for which an ADR shall be written.
2. Overview
jQAssistant comes with the ability to render AsciiDoc documents and to execute rules (i.e. concepts and constraints) which were defined inside them.
With that it qualifies perfectly to connect with Michael Nygards Architecture Decision Records to document architectural decisions and to enforce their adherence.
This tutorial will show:
For this, the tutorial uses the following example scenario:
The database for the product catalog of an online shop shall be migrated from a relational to a documented-oriented one. This decision was made due to performance and scalability reasons, but is out of scope for this example.
However, document-oriented databases requires the modeling of aggregates in the data model and, above that, forbid deep links between aggregates. E.g., an entity must be referenced only inside one aggregate.
Thus, the decision was made to prepare the migration by implementing aggregates and removing deep links between them. The example is therefore build around the deep linking decision.
3. What are Architecture Decision Records?
Architecture Decision Records are a lightweight way to document architectural decisions. They were first described by Michael Nygard (see [2]) and aim to:
-
spread the knowledge about architectural decisions to the team
-
make the decisions and their history transparent
-
provide context and implications in a nutshell
Architecture Decision Records consist of the following parts:
Section | Content |
---|---|
Title |
Numbered title of the ADR |
Status |
Current phase in the lifecycle of ADRs, one of: Proposed, Accepted, Declined, Superseded. |
Context |
Brief description of the context for which the decision is valid. Describes the forces which lead to the decision. |
Decision |
Overview of the agreed on decision. |
Consequences |
Lists the implications for the developers both for exisitng and new source code. This may also contain needed refactorings. |
4. Setting up Architecture Decision Records in the Project
First, jQAssistant needs to be setup in the project.
Therefore, the jqassistant-maven-plugin
must be added to the build plugin section in the pom.xml
s shown in the following listing.
<plugin>
<groupId>com.buschmais.jqassistant</groupId>
<artifactId>jqassistant-maven-plugin</artifactId>
<version>${jqassistant.version}</version>
<executions>
<execution>
<goals>
<goal>scan</goal>
<goal>analyze</goal>
</goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.jqassistant.contrib.plugin</groupId>
<artifactId>jqassistant-java-ddd-plugin</artifactId>
<version>${jqassistant.version}</version>
</dependency>
</dependencies>
</plugin>
Note
|
The additional jqassistant-java-ddd-plugin is not needed to work with ADRs, but will be used later to simplify the example.
Information about the DDD-Plugin can be found at [3].
|
Secondly, the folder and file structure must be created as shown in Folder Structure for ADRs.
The index.adoc
is the entry point for jQAssistant and will include the other documents.
Note
|
For larger projects, it’s helpful to group ADRs by topic into subfolders, e.g. one folder for Structure-related topics, one for Logging, one for Testing, and so on. |
skinparam Legend { BackgroundColor transparent BorderColor transparent FontSize 17 } legend <project-root> |_ jqassistant |_ index.adoc |_ 001-First-Example-ADR.adoc |_ 002-Second-Element-ADR.adoc |_ ... end legend
The following listing shows the basic structure of the index.adoc
.
It’s used to build an index with cross-references to other documents and to include them.
= Example project for Architecture Decision Records
- <<adr:DeepLinking>> // (1)
include::001-Deep-Linking.adoc[] // (2)
-
Generates a link to the anchor defined in the ADR
-
Includes the ADR
The following listing shows the basic structure of an Architecture Decision Record.
The complete ADR can be found at jqassistant/001-Deep-Linking.adoc
.
Important to mention is the definiton of the anchor adr:DeepLinking
which can be used for creating cross-references in the document.
[[adr:DeepLinking]] // (1)
== 001 - No deep linking between aggregates
=== Status: Proposed // (2)
=== Context // (3)
...
=== Decision // (4)
...
=== Consequences // (5)
...
-
Anchor for referencing the ADR
-
Status of the ADR
-
Context section of the ADR
-
Decision section of the ADR
-
Consequences section of the ADR
Note
|
For the full example, please check out the ADR directly. |
5. Defining Concepts and Constraints to Secure Decisions
Now that the basic setup is done, it is time to check that the architecture decision will be implemented correctly.
For that, two parts are crucial:
-
Identify aggregates in the code
-
Determine violations regarding deep-linking
For this, jQAssistant knows concepts (1) and constraints (2).
With concepts it’s possible to enrich the created graph with additional information, e.g. that a type node represents an Aggregate (Root).
To simplify the 101, we’ll use the jqassistant-java-ddd-plugin
to take care of this.
Further information how to use it can be found in the 101 about the plugin (see [3]).
In our example, after integrating the plugin as shown in the pom.xml, we have to annotate all aggregate roots as @DDD.AggregateRoot
and the entities as @DDD.Entity
.
That way, the information will be automatically enriched in the graph.
Note
|
It’s also possible to enrich the information manually via a self-defined concept. In this 101, it’s however only shown how to define the constraint. |
Next, the we need to define the constraint in the ADR. The following listing shows how to do this.
[[adr:DeepLinking]]
[role=group,includesConstraints="adr:*"] // (1)
== 001 - No deep linking between aggregates
...
=== Consequences
...
[[adr:NoDeepLinkingBetweenAggregates]] // (2)
[source,cypher,role=constraint,requiresConcepts="java-ddd:*"] // (3)
.Finds all entities that are referenced by more than one aggregate.
----
MATCH shortestPath((a:DDD:AggregateRoot)-[:DEPENDS_ON*]->(e:DDD:Entity)) // (4)
WITH e, collect(a.name) AS aggregates
WHERE size(aggregates) > 1
RETURN e.name AS Entity, aggregates AS Aggregates
----
-
Defines the jQAssistant group and what constraints will be executed
-
Defines the name of the constraint
-
Defines the jQAssistant constraint and required concepts, here the DDD-Plugin
-
Defines the query to execute
Afterward, the group must be added to the default group of jQAssistant to be executed as shown below.
...
[[default]] // (1)
[role=group,includesGroups="adr:*"] // (2)
- <<adr:DeepLinking>>
...
-
Defines the name of the group
-
Defines what groups are contained
When the project now gets build via maven, you’ll see a constraint violation which leads to a failing build.
[ERROR] --[ Constraint Violation ]----------------------------------------- [ERROR] Constraint: adr:NoDeepLinkingBetweenAggregates [ERROR] Severity: MAJOR [ERROR] Number of rows: 1 [ERROR] Finds all entities that are referenced by more than one aggregate. [ERROR] Entity=ProductOption, Aggregates=Category, Product [ERROR] -------------------------------------------------------------------
Whenever there will be a new violation, the build will fail and we are forced to fix the issue.
However, when introducing a new decision, it may make sense to either lower the severity of the constraint to MINOR
in order to not fail build or to update the query to have a list of allowed violations.
That way, the decision can be incorporated step-by-step.
6. Resources
Architecture Decision Records are a lightweight way to document architectural decision. They were first described by Michael Nygard in [