Expressing architectural concepts in code often stops at naming conventions and technologies implicitly describing these concepts. jMolecules provides annotations and types to describe architectural context and technology specific integration to derive concept specific default mappings and boilerplate code to minimize the gap between architectural idea and the code written to implement those.
Note: This is a follow up blog post to “Implementing DDD Building Blocks in Java” as the libraries discussed in that have evolved significantly. While I thought about updating the original article I eventually decided to write a completely new one as the extend of the changes is too great.
The general journey from architectural idea usually follows the following pattern: the team has a rough idea of the fundamental logical structure of the code they want to write. That structure can be driven by patterns solely established within the team but is usually influenced by widely adapted architectural approaches like Ports and Adapters or Domain-Driven Design building blocks like entities, aggregates, repositories and services. The teams agree on a strategy to actually implement these concepts. The means to achieve that commonly range from naming conventions for individual types or packages and package structures. Then, the final step towards running code is mapping the logical and technical constraints implied by the concept to the implementation technologies.
Let us take a look at a very simple example.
Assume a simple Customer
aggregate that is responsible to implement business constraints.
For example, we should be allowed to assign multiple addresses to the customer, but at least a single Address
instance needs to be assigned to it.
This is admittedly simplistic but the complexity of the constraints does not actually matter to the point I am trying to make.
Let us further assume, we would want to map the model code to the database directly using JPA and Spring Data repositories.
I know this is a topic worth debating on its own, but let us stick with that for now.
Even in that very reduced example, the actual implementation has significant impact on the code to be written:
Serializable
, etc.@EmbeddedId
.
The list of addresses has to be mapped as cascading @OneToMany
relationship to express the governing nature of the conceptual aggregate.There might be well justified exceptions to those mappings but fundamentally, there are rules that can and have to be applied to express the logical concept.
Zooming out a bit, there are a couple of things going on here at the same time.
First, the logical concept of an aggregate is not explicit.
It is scattered amongst different technologies that kind of help expressing the concept but suffer a fundamental problem themselves, which is – second – they are not designed with the architectural abstraction in mind in the first place.
Arguably, Spring Data is built around the concept of a DDD repository. Also, Spring provides stereotype annotations like @Service
and @Repository
to assign roles to container managed components.
However, they are driven by the needs of the particular technology’s requirements and functionality like applying transactions and persistence exception translation in the examples given.
Let us take it one step at a time. jMolecules is a project that attempts to tackle the first step: expressing architectural concepts in code directly, either through annotations or interfaces. It has evolved from the jDDD library discussed in this earlier blog post but found a new home in the xMolecules project that in turn attempts to provide similar architectural abstractions for different programming ecosystems.
So we would start by using the Domain-Driven Design abstractions provided by jMolecules in our domain model:
import org.jmolecules.ddd.types.AggregateRoot;
import org.jmolecules.ddd.types.Entity;
import org.jmolecules.ddd.types.Identifier;
class Customer implements AggregateRoot<Customer, CustomerId> {
private final CustomerId id;
private List<Address> addresses;
/* Other properties and business methods omitted. */
@Value(staticConstructor = "of")
public static class CustomerId implements Identifier {
private final UUID id;
}
}
class Address implements Entity<Customer, AddressId> {
/* Other properties and business methods omitted. */
@Value(staticConstructor = "of")
static class AddressId implements Identifier {
private final UUID id;
}
}
We’re able to express Domain-Driven Design concepts here:
CustomerId
and AddressId
are identifiers.Customer
is an aggregate with CustomerId
as its identifier type.Address
is an entity belonging to the Customer
aggregate using AddressId
as identifier type.This looks very neat so far. Except the slightly verbose identifier wrapper types, we are writing only the minimal amount of code necessary and the higher level concepts are both explicitly visible to the developers and also extractable for tools that might want to derive documentation. However, if this code was supposed to be persisted using for example JPA, we would now have to go ahead and add quite a bit of boilerplate annotations and implementation code:
Address
and Customer
would need to be annotated with JPA’s @Entity
and get default constructors.
As we can only null
the properties, this would effectively prevent us from imposing non-nullability constraints on any fields.Identifier
would have to additionally implement Serializable
and ideally also declare a serial version UUID.@Embeddable
, the identifier fields within the entities as @EmbeddedId
and map the addresses
property using an @OneToMany
.Besides the core artifacts containing abstractions to express architectural patterns, jMolecules provides a set of technology integration libraries. At its heart, there is a ByteBuddy plugin that will translate jMolecules annotations and interfaces into the technology specific annotations and declarations to make sure they work out of the box within in a particular technology context. The complete example can be found here.
<build>
<plugins>
<plugin>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy-maven-plugin</artifactId>
<version>1.10.21</version>
<executions>
<execution>
<goals>
<goal>transform</goal>
</goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.jmolecules.integrations</groupId>
<artifactId>jmolecules-bytebuddy</artifactId>
<version>0.1.0</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
The jmolecules-bytebuddy
plugin will – depending on the classpath arrangement of the project being built – inspect the code to be compiled for architectural concepts expressed via jMolecules annotations and types.
It will then add the technology specific annotations that are required to implement the concept and add it to the code compiled.
With that plugin integrated into the build, the code Customer
aggregate code shown above is indeed a fully functional JPA entity.
All default constructors, additional interfaces and default mappings as described above will be generated.
Also, if the nullability annotations arrangement of the type describes fields as non-nullable, a generated @PrePersist
/@PostLoad
method will verify the instance to be persisted or just loaded to be in valid state, in other words, that no non-nullable fields are actually null
.
Besides the integration for JPA, the ByteBuddy plugin also translates jMolecules @Service
, @Repository
etc. annotations into their Spring counterparts so that application components can stay free of Spring annotations for the sole purpose of component definition but still work out of the box with it.
Also, jMolecules’ @DomainEventHandler
annotation is translate into Spring’s @EventListener
to implement annotation driven event listeners.
jMolecules allows to express architectural concepts like Domain-Driven Design building blocks as annotations or types within Java code. Its integration library projects those concepts into particular technologies like JPA and Spring.