This module contains a ByteBuddy plugin implementation that will generate technology specific boilerplate code based on concepts expressed via jMolecules DDD building block interfaces.
-
Adds Spring-specific stereotype annotations (such as
@Serviceetc.) to types annotated with jMolecules stereotype annotations (see Technology annotation translation). -
It automatically adds default mapping annotations to make aggregates work with persistence implementations out of the box and transparently add
Persistableimplementations forAggregateRoottypes for JPA, JDBC and MongoDB if Spring Data is used in the project (see JPA-backed aggregates andPersistableimplementations for JPA, JDBC and MongoDB).
All you need to do to get the technology derivation started is adding the ByteBuddy build plugin to your project and let it work with the org.jmolecules.integrations:jmolecules-bytebuddy dependency.
It will automatically detect which transformations to apply based on your classpath arrangement.
|
Important
|
The following configuration is automatically picked up for application on incremental compilation in Eclipse. In IDEA, you manually have to configure it to be executed for IDE-induced builds by finding the goal to be executed in the build plugin goals window, right-clicking it and selecting “Execute after build” and “Execute after rebuild”. For details, see the corresponding section of the IDEA reference documentation. |
|
Note
|
Find complete examples of the setup using different sets of technology in the jMolecules Examples repository. |
First of all, make sure you use the jMolecules BOM to avoid having to deal with version numbers. Find the most recent version number here.
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.jmolecules</groupId>
<artifactId>jmolecules-bom</artifactId>
<version>${jmolecules.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>Next, add the jmolecules-bytebuddy-nodep dependency and declare it in provided scope, so that its not packaged in your runtime application.
<dependencies>
<dependency>
<groupId>org.jmolecules.integrations</groupId>
<artifactId>jmolecules-bytebuddy-nodep</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>Now add jMolecules integration JARs to your project, depending on the technology stack you’re using. They can usually be declared in runtime scope, unless you need to directly interact with APIs exposed.
| Technology | Artifact ID | Description |
|---|---|---|
Spring Data |
|
Contains code needed by Spring Data-related code generation and other runtime artifacts. |
JPA |
|
(Spring Data) JPA-specific code needed by the code generation. |
|
Note
|
There are other technology integration artifacts available, but the ones listed above are the ones needed to get the code generation working. |
Finally, declare the byte-buddy-maven-plugin in the <plugins> section of your pom.xml
<plugin>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy-maven-plugin</artifactId>
<version>${bytebuddy.version}</version>
<executions>
<execution>
<goals>
<goal>transform-extended</goal> (1)
</goals>
</execution>
</executions>
<configuration>
<classPathDiscovery>true</classPathDiscovery> (2)
</configuration>
</plugin>-
The
transform-extendedgoal makes sure the plugin runs with theruntimeclasspath and thus discovers the integration artifacts declared in that scope, too. -
The
classPathDiscoveryflag causes the ByteBuddy build plugin to autmatically discover the jMolecules extension injmolecules-bytebuddy-nodep.
If you use jMolecules stereotype annotations with Kotlin, you will need to make sure that the Kotlin compiler opens up the types annotated with those. To achieve that, you usually use the kotlin-allopen or kotlin-spring plugins. To get the plugins consider jMolecules annotations as well, you need to declare them in the plugin configuration like this:
<pluginOptions>
<option>all-open:annotation=org.jmolecules.ddd.annotation.Service</option> (1)
</pluginOptions><1>
Furthermore, the setup of the ByteBuddy Maven Plugin will need to get an additional configuration applied to skip the parameter name validation:
<plugin>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy-maven-plugin</artifactId>
<version>${bytebuddy.version}</version>
…
<configuration>
<initialization> (1)
<classPathDiscovery>true</classPathDiscovery>
<validated>false</validated>
</initialization>
</configuration>
</plugin>-
The validation on initialization has to be disabled.
For more information, see this ByteBuddy ticket and this Kotlin ticket.
|
Note
|
Using Gradle, ByteBuddy and Kotlin in combination is currently not possible due to incompatibilities between the ByteBuddy Gradle plugin and Kotlin. Find more information here. |
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath platform("org.jmolecules:jmolecules-bom:$jMoleculesBomVersion")
classpath 'org.jmolecules.integrations:jmolecules-bytebuddy'
}
}
plugins {
id "java"
id "net.bytebuddy.byte-buddy-gradle-plugin" version "$byteBuddyVersion"
}
dependencies {
// Depending on which technologies you integrate with
}
byteBuddy {
transformation{
// Needs to be declared explicitly
plugin = org.jmolecules.bytebuddy.JMoleculesPlugin
}
}The plugin will translate jMolecules architectural annotations into framework specific ones and vice versa.
This allows user code to use jMolecules annotations like @Service and they’re still fully functional e.g. Spring beans as they get Spring’s @Service annotation added at compile time but at the same time avoids having to double annotate types.
At the same time, code that uses Spring specific annotations is still able to use tools that expect to find jMolecules annotations for e.g. documentation purposes.
-
o.j.d.a.Repository<→o.s.s.Repository -
o.j.d.a.Service<→o.s.s.Service -
o.j.d.a.Factory<→o.s.s.Component -
o.j.e.a.DomainEventHandler<→o.s.c.e.EventListener
|
Note
|
A repository interface annotated with o.j.d.a.Repository will not cause it to be supported by Spring Data out of the box as the jMolecules annotation currently lacks the generics information for the corresponding aggregate root and identifier type that’s needed for Spring Data to work properly.
|
Similarly to the annotation translation, the build plugin will translate jMolecules DDD Repository interface into the Spring Data equivalent if Spring Data is on the classpath.
interface Orders implements o.j.ddd.types.Repository<Order, OderId> {}The transformation also carries over the declared generics so that the application repository interface will become a fully-working Spring Data repository instance.
-
Annotates
AggregateRootandEntitytypes with@Entityand adds a default constructor if missing. -
Annotates fields implementing
Identifierwith@EmbeddedId. -
Annotates types implementing
Identifierwith@Embeddable, implementsSerializable(required by Hibernate) and declares a default constructor if missing. -
Annotates fields of type
Entitywith@OneToOne, collections ofEntitywith@OneToManydefaulting to cascade all persistence operations (i.e. applying composition semantics to the aggregate: the lifecycle of the related entities is tied to the one of the aggregate). -
Registers a dedicated
AttributeConverterimplementation for the identifier types defined inAssociationfields so that they’re automatically persisted as the target identifier. The base implementation for that can be found in thejmolecules-springmodule.
Annotations are only added unless the relevant annotations are already present.
That means, the following code is a model that can be persisted using JPA as is:
import org.jmolecules.ddd.types.*;
class Order implements AggregateRoot<Order, OrderId> { // (1)
private final OrderId id; // (2)
private List<LineItem> lineItems; // (3)
private Association<Customer, CustomerId> customer; // (4)
Order(Customer customer) {
this.id = OrderId.of(UUID.randomUUID());
this.customer = Association.forAggregate(customer);
}
/* … */
}
@Value(staticConstructor = "of")
class OrderId implements Identifier { // (2)
UUID id;
}
class LineItem implements Entity<Order, LineItemId> { // (5)
private final LineItemId id; // (2)
/* … */
}
@Value(staticConstructor = "of")
class LineItemId implements Identifier {
UUID id;
}
class Customer implements AggregateRoot<Customer, CustomerId> { // (1)
private final CustomerId id; // (2)
/* … */
}
@Value(staticConstructor = "of")
class CustomerId implements Identifier {
UUID id;
}-
AggregateRootimplementations will automatically implement Spring Data’sPersistableand get annotated with@Entity. They will also get a default constructor added. -
The field will get annotated with
@EmbeddedIdas its type implementsIdentifier. The type itself will be annotated with@Embeddableand additionally implementSerializable(required by Hibernate). It will also get a default constructor added. -
lineItemswill be mapped to@OneToManycascading all persistence operation as we assume a composition arrangement for entities contained in the aggregate. -
The
Associationwill get a dedicatedAttributeConverterimplementation generated and that in turn registered for the field via@Convert(converter = …). See the jMolecules Spring integration module for details. -
An
Entitywill be annotated with JPA’s@Entityannotation and get a default constructor added. In contrast to the aggregate root, it will not implementPersistable.
The plugin automatically makes all AggregateRoot implementations implement Spring Data’s Persistable so that they work properly with manually assigned identifier types (usually based on UUIDs).
The implementation is based on MutablePersistable defined in the jmolecules-spring module and the store specific NotNewCallback implementations that interact with the callback APIs of the dedicated stores.
It also generates a transient boolean flag to keep the new state around and properly set that to false upon instance load.
Also, Entity implementations are annotated with the store-specific marker like @Document for MongoDB and @Table for JDBC.