Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 

jMolecules ByteBuddy plugin

This module contains a ByteBuddy plugin implementation that will generate technology specific boilerplate code based on concepts expressed via jMolecules DDD building block interfaces.

Quickstart

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.

Maven

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

jmolecules-spring

Contains code needed by Spring Data-related code generation and other runtime artifacts.

JPA

jmolecules-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>
  1. The transform-extended goal makes sure the plugin runs with the runtime classpath and thus discovers the integration artifacts declared in that scope, too.

  2. The classPathDiscovery flag causes the ByteBuddy build plugin to autmatically discover the jMolecules extension in jmolecules-bytebuddy-nodep.

Kotlin specialties

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>
  1. The validation on initialization has to be disabled.

For more information, see this ByteBuddy ticket and this Kotlin ticket.

Gradle

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
  }
}

Technology annotation translation

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.

Repository interface translation

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.

Reduce boilerplate for AggregateRoot implementations

JPA-backed aggregates

  • Annotates AggregateRoot and Entity types with @Entity and adds a default constructor if missing.

  • Annotates fields implementing Identifier with @EmbeddedId.

  • Annotates types implementing Identifier with @Embeddable, implements Serializable (required by Hibernate) and declares a default constructor if missing.

  • Annotates fields of type Entity with @OneToOne, collections of Entity with @OneToMany defaulting 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 AttributeConverter implementation for the identifier types defined in Association fields so that they’re automatically persisted as the target identifier. The base implementation for that can be found in the jmolecules-spring module.

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;
}
  1. AggregateRoot implementations will automatically implement Spring Data’s Persistable and get annotated with @Entity. They will also get a default constructor added.

  2. The field will get annotated with @EmbeddedId as its type implements Identifier. The type itself will be annotated with @Embeddable and additionally implement Serializable (required by Hibernate). It will also get a default constructor added.

  3. lineItems will be mapped to @OneToMany cascading all persistence operation as we assume a composition arrangement for entities contained in the aggregate.

  4. The Association will get a dedicated AttributeConverter implementation generated and that in turn registered for the field via @Convert(converter = …). See the jMolecules Spring integration module for details.

  5. An Entity will be annotated with JPA’s @Entity annotation and get a default constructor added. In contrast to the aggregate root, it will not implement Persistable.

Persistable implementations for JPA, JDBC and MongoDB

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.