jQAssistant DDD Plugin
The jQAssistant DDD Plugin provides means to map DDD language elements onto the code and to enrich this information in the graph in order to apply additional, DDD-general as well as project specific, concepts and constraints.
Usage of the jQAssistant DDD Plugin
To make use of the provided annotations (as will be shown in the next section), following dependency is needed to be defined:
<dependency>
<groupId>org.jqassistant.contrib.plugin</groupId>
<artifactId>jqassistant-java-ddd-annotations</artifactId>
<version>${jqassistant.version}</version>
</dependency>
Additionally, to have jQAssistant consider the annotations and to allow the execution of constraints, following dependency in the jqassistant-maven-plugin
needs to be defined:
<plugin>
<groupId>com.buschmais.jqassistant</groupId>
<artifactId>jqassistant-maven-plugin</artifactId>
<version>${jqassistant.version}</version>
<executions>
<execution>
<id>default-cli</id>
<goals>
<goal>scan</goal>
<goal>analyze</goal>
</goals>
<configuration>
<groups>
<group>java-ddd:Default</group> // (2)
</groups>
</configuration>
</execution>
</executions>
<dependencies>
<dependency> // (1)
<groupId>org.jqassistant.contrib.plugin</groupId>
<artifactId>jqassistant-java-ddd-plugin</artifactId>
<version>${jqassistant.version}</version>
</dependency>
</dependencies>
</plugin>
-
Definition of the plugin dependency
-
Definition of the java-ddd:Default group to be executed
DDD Concepts
The jQAssistant DDD plugin comes with jQA-concepts and Java-annotations for multiple DDD concepts. The supported DDD-concepts and how they are mapped in from source code to the graph is shown in the following list.
All supported DDD-concepts can be applied both on package level (by annotating the package definition in a package-info.java
file) or class level (by annotating a Java class).
Bounded Context
A bounded context is a well-defined, functional consistent, and cohesive subset of the complete solution domain. It therewith represents the implementation of one functional slice of the problem space.
A bounded context is identified by its name. In case that multiple bounded contexts with the same name were declared, only one node will be created.
The bounded context nodes will be labeled by :DDD:BoundedContext
.
- Package Level
-
All classes in the annotated package and its sub packages will be assigned to the corresponding node.
@BoundedContext(name = "catalog")
package com.buschmais.shop.catalog;
- Type Level
-
The annotated class will be assigned to the corresponding node.
@BoundedContext(name = "catalog")
public class Product { }
- Bounded Context Dependencies
-
Besides defining a bounded context with a name it’s also possible to define allowed dependencies between dependencies. The definition is done by specifying a list of bounded contexts by their name on which the defining bounded context may depend on.
@BoundedContext(name = "catalog", dependsOn = {"merchant"})
Domain Event
A domain event is a notice about the change of state in a functional sense, to which interested parties can listen.
- Package Level
-
All classes in the annotated package will be labeled as
:DDD:DomainEvent
.
@DomainEvent
package com.buschmais.shop.catalog.event;
- Type Level
-
The annotated class will be labeled as
:DDD:DomainEvent
.
@DomainEvent
public class ProductCreatedEvent { }
Aggregate
An aggregate is a logical, cohesive group of entities and value objects from the same bounded context. Manipulations to an aggregate or its contained entities and value objects may only be done through the aggregate root which itself represents some kind of an transaction boundary. Aggregates are used to group domain objects which need to stay absolutely consistent to each other and thus are rather small.
- Package Level
-
All classes in the annotated package will be labeled as
:DDD:AggregateRoot
.
@AggregateRoot
package com.buschmais.shop.catalog.model.aggregateroot;
- Type Level
-
The annotated class will be labeled as
:DDD:Aggregate
.
@AggregateRoot
public class Product { }
Entity
An entity is a domain object which is not defined by its properties but by its unique identifier which will not change throughout its existence.
Note: Both the @Entity
-annotation provided by this plug-in as well as @javax.persistence.Entity
will be treated as DDD entities.
- Package Level
-
All classes in the annotated package will be labeled as
:DDD:Entity
.
@Entity
package com.buschmais.shop.catalog.model.entity;
- Type Level
-
The annotated class will be labeled as
:DDD:Entity
.
@Entity
public class Product { }
Value Object
Value objects are immutable domain objects which have no unique identifies but are identified by their properties.
- Package Level
-
All classes in the annotated package will be labeled as
:DDD:ValueObject
.
@ValueObject
package com.buschmais.shop.catalog.model.valueobject;
- Type Level
-
The annotated class will be labeled as
:DDD:ValueObject
.
@ValueObject
public class Price { }
Service
A service is a stateless object providing access to domain objects and implementing business rules as methods (commands and queries). Services operate on aggregates.
- Package Level
-
All classes in the annotated package will be labeled as
:DDD:Service
.
@Service
package com.buschmais.shop.catalog.service;
- Type Level
-
The annotated class will be labeled as
:DDD:Service
.
@Service
public class ProductService { }
Repository
A repository represents an accessor to a persistent store by both providing functionality to create and modify domain objects. Repositories operate on an aggregate.
- Package Level
-
All classes in the annotated package will be labeled as
:DDD:Repository
.
@Repository
package com.buschmais.shop.catalog.repository;
- Type Level
-
The annotated class will be labeled as
:DDD:Repository
.
@Repository
public class ProductRepository { }
Factory
A factory takes care of creating a new entity or value object from given data or an already existing object and takes care of its invariants. A factory (method) can be present directly in the domain model class or as a separate class.
- Package Level
-
All classes in the annotated package will be labeled as
:DDD:Factory
.
@Factory
package com.buschmais.shop.catalog.factory;
- Type Level
-
The annotated class will be labeled as
:DDD:Factory
.
@Factory
public class ProdutFactory { }
Layer
Besides the definition of functional concepts in DDD there are also requirements to the technical layering of the application stated.
Per layer, a new node labeled as ':DDD:Layer' with a name property will be created. All classes annotated as being part of a specific layer will be associated to the respective layer node using a 'CONTAINS' relation.
Following nodes will be created:
-
(:DDD:Layer {name: 'Interface'})
-
(:DDD:Layer {name: 'Application'})
-
(:DDD:Layer {name: 'Domain'})
-
(:DDD:Layer {name: 'Infrastructure})
InterfaceLayer
The interface layer is the outermost layer in a DDD-architecture, providing access to the application to other services and the user. This layer is very thin and provides only rudimentary functionality for e.g. request handling. No domain logic shall be implemented by this layer.
- Package Level
-
All classes in the annotated package will associated to the (:DDD:Layer {name: 'Interface'}) node.
@InterfaceLayer
package com.buschmais.shop.catalog.interfaces;
- Type Level
-
The annotated class will be associated to the (:DDD:Layer {name: 'Interface'}) node.
@InterfaceLayer
public class ProductController { }
ApplicationLayer
The application layer is a thin layer orchestrating business use cases and spanning transactions. It implements no specific domain logic but coordinates the correct execution of scenarios and takes care of aspects like transaction handling.
- Package Level
-
All classes in the annotated package will be associated to the (:DDD:Layer {name: 'Application'}) node.
@ApplicationLayer
package com.buschmais.shop.catalog.application;
- Type Level
-
The annotated class will be associated to the (:DDD:Layer {name: 'Application'}) node.
@ApplicationLayer
public class ProductHandler { }
DomainLayer
The domain layer is the heart of a DDD-structured application and implements the business logic and objects of bounded contexts.
- Package Level
-
All classes in the annotated package will be associated to the (:DDD:Layer {name: 'Domain'}) node.
@DomainLayer
package com.buschmais.shop.catalog.domain;
- Type Level
-
The annotated class will be associated to the (:DDD:Layer {name: 'Domain'}) node.
@DomainLayer
public class ProductService { }
InfrastructureLayer
The infrastructure layer is the supporting layer for the other layers providing technical implementations like database access.
Infrastructure can both be present in the bounded context scope (like when providing access to the product table) or in global scope, e.g. for sending e-mails.
- Package Level
-
All classes in the annotated package will be associated to the (:DDD:Layer {name: 'Infrastructure}) node.
@InfrastructureLayer
package com.buschmais.shop.catalog.infrastructure;
- Type Level
-
The annotated class will be associated to the (:DDD:Layer {name: 'Infrastructure}) node.
@InfrastructureLayer
public class ProductRepositoryImpl { }
Default DDD Constraints
The jQAssistant DDD plug-in comes with several pre-defined constraints which check the implemented architecture against the basic DDD architectural principles.
The following constraints will be active by default and configured with a Major
-severity.
java-ddd:TypeInMultipleLayers
The constraint checks that each type is only part of one layer.
java-ddd:TypeInMultipleBoundedContexts
The constraint checks that each type is only part of one bounded context.
java-ddd:IllegalDependenciesBetweenBoundedContexts
The constraints checks that there are no dependencies between bounded contexts present when they are not defined.
java-ddd:UnneededDependenciesBetweenBoundedContexts
The constraint checks that there are no dependencies between bounded contexts defined which are not required by the implementation.
Strict DDD Constraints
Additionally to the default DDD constraints which are quite relaxed and thus easy to integrate into legacy projects, there is a more strict set of constraints provided by the DDD plug-in.
The constraints can be activated by declaring the name of it in the executed groups section of the plug-in configuration:
<groups>
<group>java-ddd:Default</group> // (1)
<group>java-ddd:Strict</group> // (2)
</groups>
-
Activation of the default rule set.
-
Activation of the strict rule set.
java-ddd:IllegalDependenciesBetweenTechnicalLayers
As defined by DDD, only dependencies between specific layers are allowed. These dependencies are visualized in the following diagram:
skinparam componentStyle uml2 component { component InterfaceLayer component ApplicationLayer component DomainLayer } component InfrastructureLayer InterfaceLayer --> ApplicationLayer InterfaceLayer --> DomainLayer ApplicationLayer --> DomainLayer InfrastructureLayer --> InterfaceLayer InfrastructureLayer -left-> ApplicationLayer InfrastructureLayer --> DomainLayer
The constraint checks for the correct implementation of the relations.
Visual Reporting
The DDD plug-in supports visualization of concepts using PlantUML in jQAssistant reports for the following concepts:
-
Bounded Context
-
Layer (Interface, Application, Domain, Infrastructure)
To use this functionality, define the jQAssistant concept with the following property set:
-
reportType="plantuml-component-diagram"
Licensing
The jqassistant-java-ddd-annotations
module is licensed under the Apache License, Version 2.0. Therefore, it can be added as a direct dependency to the project without the need to disclose the source code.
The jqassistant-java-ddd-plugin
module is licensed under the GNU GENERAL PUBLIC LICENSE, Version 3 due to the dependency to Neo4j. However, as this will be declared as a dependency of the jqassistant-maven-plugin
(which is a build plugin), it will not end up in the delivery.