Skip to content

Commit

Permalink
GH-51 - Introduce Kotlin flavor of jMolecules DDD.
Browse files Browse the repository at this point in the history
The type-based flavor of the DDD building blocks uses methods that follow the Java Beans naming convention. Unfortunately, Kotlin currently does not implement these methods if properties are declared that match the convention [0]. This commit adds a kmolecules-ddd module that completely defines the same abstractions as jmolecules-ddd in Kotlin so that they can be properly used from Kotlin as well.

The types are located in the org.jmolecules.ddd package so that we can phase out this module as soon as that glitch in Kotlin is fixed and users can just drop this JAR here in favor of the jMolecules DDD one.

[0] https://youtrack.jetbrains.com/issue/KT-6653
  • Loading branch information
Jocelyn Ntakpe authored and odrotbohm committed May 16, 2022
1 parent 34bd90e commit 6b0a9d6
Show file tree
Hide file tree
Showing 16 changed files with 405 additions and 0 deletions.
61 changes: 61 additions & 0 deletions kmolecules-ddd/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">

<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>org.jmolecules</groupId>
<artifactId>jmolecules</artifactId>
<version>1.5.0-SNAPSHOT</version>
</parent>

<artifactId>kmolecules-ddd</artifactId>

<name>kMolecules - DDD</name>
<description>Kotlin flavor of jMolecules DDD</description>

<properties>
<module.name>org.kmolecules.ddd</module.name>
</properties>

<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
<version>${kotlin.version}</version>
</dependency>
</dependencies>

<build>

<sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>
<testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory>

<plugins>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>${kotlin.version}</version>

<executions>
<execution>
<id>compile</id>
<goals>
<goal>compile</goal>
</goals>
</execution>

<execution>
<id>test-compile</id>
<goals>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>

</build>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package org.jmolecules.ddd.annotation

/**
* Identifies an aggregate root, i.e. the root entity of an aggregate. An aggregate forms a cluster of consistent rules
* usually formed around a set of entities by defining invariants based on the properties of the aggregate that have to
* be met before and after operations on it. Aggregates usually refer to other aggregates by their identifier.
* References to aggregate internals should be avoided and at least not considered strongly consistent (i.e. a reference
* held could possibly have been gone or become invalid at any point in time). They also act as scope of consistency,
* i.e. changes on a single aggregate are expected to be strongly consistent while changes across multiple ones should
* only expect eventual consistency.
*
* Kotlin's counterpart of the Java's [AggregateRoot](https://github.com/xmolecules/jmolecules/blob/main/jmolecules-ddd/src/main/java/org/jmolecules/ddd/annotation/AggregateRoot.java)
*
* @author Jocelyn Ntakpe
*
* See also : [Domain-Driven Design Reference (Evans) - Aggregates](https://domainlanguage.com/wp-content/uploads/2016/05/DDD_Reference_2015-03.pdf)
*/
@Entity
@Retention
@MustBeDocumented
@Target(AnnotationTarget.CLASS)
annotation class AggregateRoot
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.jmolecules.ddd.annotation

/**
* Identifies an [Entity]. Entities represent a thread of continuity and identity, going through a lifecycle,
* though their attributes may change. Means of identification may come from the outside, or it may be an arbitrary
* identifier created by and for the system, but it must correspond to the identity distinctions in the model. The model
* must define what it means to be the same thing.
*
* Kotlin's counterpart of the Java's [Entity](https://github.com/xmolecules/jmolecules/blob/main/jmolecules-ddd/src/main/java/org/jmolecules/ddd/annotation/Entity.java)
*
* @author Jocelyn Ntakpe
*
* See also : [Domain-Driven Design Reference (Evans) - Entities](https://domainlanguage.com/wp-content/uploads/2016/05/DDD_Reference_2015-03.pdf)
*/
@Retention
@MustBeDocumented
@Target(AnnotationTarget.CLASS)
annotation class Entity
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.jmolecules.ddd.annotation

/**
* Identifies a [Factory]. Factories encapsulate the responsibility of creating complex objects in general and
* Aggregates in particular. Objects returned by the factory methods are guaranteed to be in valid state.
*
* Kotlin's counterpart of the Java's [Factory](https://github.com/xmolecules/jmolecules/blob/main/jmolecules-ddd/src/main/java/org/jmolecules/ddd/annotation/Factory.java)
*
* @author Jocelyn Ntakpe
*
* See also : [AggregateRoot]
* See also : [Domain-Driven Design Reference (Evans) - Factories](https://domainlanguage.com/wp-content/uploads/2016/05/DDD_Reference_2015-03.pdf)
*/
@Retention
@MustBeDocumented
@Target(AnnotationTarget.CLASS)
annotation class Factory
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.jmolecules.ddd.annotation

/**
* Identifies a [Repository]. Repositories simulate a collection of aggregates to which aggregate instances can be
* added and removed. They usually also expose API to select a subset of aggregates matching certain criteria. Access to
* projections of an aggregate might be provided as well but also via a dedicated separate abstraction.
*
* Implementations use a dedicated persistence mechanism appropriate to the data structure and query requirements at
* hand. However, they should make sure that no persistence mechanism specific APIs leak into client code.
*
* Kotlin's counterpart of the Java's [Repository](https://github.com/xmolecules/jmolecules/blob/main/jmolecules-ddd/src/main/java/org/jmolecules/ddd/annotation/Repository.java)
*
* @author Jocelyn Ntakpe
*
* See also : [AggregateRoot]
* See also : [Domain-Driven Design Reference (Evans) - Repositories](https://domainlanguage.com/wp-content/uploads/2016/05/DDD_Reference_2015-03.pdf)
*/
@Retention
@MustBeDocumented
@Target(AnnotationTarget.CLASS)
annotation class Repository
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.jmolecules.ddd.annotation

/**
* Identifies a domain [Service]. A service is a significant process or transformation in the domain that is not a
* natural responsibility of an entity or value object, add an operation to the model as a standalone interface declared
* as a service. Define a service contract, a set of assertions about interactions with the service. (See assertions.)
* State these assertions in the ubiquitous language of a specific bounded context. Give the service a name, which also
* becomes part of the ubiquitous language.
*
* Kotlin's counterpart of the Java's [Service](https://github.com/xmolecules/jmolecules/blob/main/jmolecules-ddd/src/main/java/org/jmolecules/ddd/annotation/Service.java)
*
* @author Jocelyn Ntakpe
*/
@Retention
@MustBeDocumented
@Target(AnnotationTarget.CLASS)
annotation class Service
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.jmolecules.ddd.annotation

/**
* Identifies a value object. Domain concepts that are modeled as value objects have no conceptual identity or
* lifecycle. Implementations should be immutable, operations on it are side effect free.
*
* Kotlin's counterpart of the Java's [ValueObject](https://github.com/xmolecules/jmolecules/blob/main/jmolecules-ddd/src/main/java/org/jmolecules/ddd/annotation/ValueObject.java)
*
* @author Jocelyn Ntakpe
*
* See also : [Domain-Driven Design Reference (Evans) - Value objects](https://domainlanguage.com/wp-content/uploads/2016/05/DDD_Reference_2015-03.pdf)
*/
@Retention
@MustBeDocumented
@Target(AnnotationTarget.CLASS)
annotation class ValueObject
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.jmolecules.ddd.types

/**
* Identifies an aggregate root, i.e. the root entity of an aggregate. An aggregate forms a cluster of consistent rules
* usually formed around a set of entities by defining invariants based on the properties of the aggregate that have to
* be met before and after operations on it. Aggregates usually refer to other aggregates by their identifier.
* References to aggregate internals should be avoided and at least not considered strongly consistent (i.e. a reference
* held could possibly have been gone or become invalid at any point in time). They also act as scope of consistency,
* i.e. changes on a single aggregate are expected to be strongly consistent while changes across multiple ones should
* only expect eventual consistency.
*
* Kotlin's counterpart of Java's [AggregateRoot](https://github.com/xmolecules/jmolecules/blob/main/jmolecules-ddd/src/main/java/org/jmolecules/ddd/types/AggregateRoot.java)
*
* @author Jocelyn Ntakpe
* @since 1.0
*
* See also: [Domain-Driven Design Reference (Evans) - Aggregates](https://domainlanguage.com/wp-content/uploads/2016/05/DDD_Reference_2015-03.pdf)
* See also: [John Sullivan - Advancing Enterprise DDD - Reinstating the Aggregate](https://scabl.blogspot.com/2015/04/aeddd-9.html)
*/
interface AggregateRoot<T : AggregateRoot<T, ID>, ID : Identifier> : Entity<T, ID>
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package org.jmolecules.ddd.types

/**
* An association to an [AggregateRoot].
*
* Kotlin's counterpart of Java's [Association](https://github.com/xmolecules/jmolecules/blob/main/jmolecules-ddd/src/main/java/org/jmolecules/ddd/types/Association.java)
*
* @author Jocelyn Ntakpe
* @since 1.0
* See also: [John Sullivan - Advancing Enterprise DDD - Reinstating the Aggregate](https://scabl.blogspot.com/2015/04/aeddd-9.html)
*/
interface Association<T : AggregateRoot<T, ID>, ID : Identifier> : Identifiable<ID> {

companion object {

/**
* Creates an [Association] pointing to the [Identifier] of the given [AggregateRoot].
*
* @param T the concrete [AggregateRoot] type.
* @param ID the concrete [Identifier] type.
* @param aggregate used to create the association
* @return an [Association] pointing to the [Identifier] of the given [AggregateRoot].
* @since 1.3
*/
@JvmStatic
fun <T : AggregateRoot<T, ID>, ID : Identifier> forAggregate(aggregate: T): Association<T, ID> {
return SimpleAssociation { aggregate.id }
}

/**
* Creates an [Association] pointing to the given [Identifier].
*
* @param T the concrete [AggregateRoot] type.
* @param ID the concrete [Identifier] type.
* @param identifier used to create the association
* @return an [Association] pointing to the given [Identifier].
* @since 1.3
*/
@JvmStatic
fun <T : AggregateRoot<T, ID>, ID : Identifier> forId(identifier: ID): Association<T, ID> {
return SimpleAssociation { identifier }
}
}

/**
* Returns whether the current [Association] points to the same [AggregateRoot] as the given one. Unlike
* [equals] and [hashCode] that also check for type equality of the [Association]
* itself, this only compares the target [Identifier] instances.
*
* @param other association compared with the current instance
* @return whether the current [Association] points to the same [AggregateRoot] as the given one.
* @since 1.3
*/
fun pointsToSameAggregateAs(other: Association<*, ID>): Boolean = id == other.id
}
17 changes: 17 additions & 0 deletions kmolecules-ddd/src/main/kotlin/org/jmolecules/ddd/types/Entity.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.jmolecules.ddd.types

/**
* Identifies an [Entity]. Entities represent a thread of continuity and identity, going through a lifecycle,
* though their attributes may change. Means of identification may come from the outside, or it may be an arbitrary
* identifier created by and for the system, but it must correspond to the identity distinctions in the model. The model
* must define what it means to be the same thing.
*
* Kotlin's counterpart of Java's [Entity](https://github.com/xmolecules/jmolecules/blob/main/jmolecules-ddd/src/main/java/org/jmolecules/ddd/types/Entity.java)
*
* @author Jocelyn Ntakpe
* @since 1.0
*
* See also: [Domain-Driven Design Reference (Evans) - Entities](https://domainlanguage.com/wp-content/uploads/2016/05/DDD_Reference_2015-03.pdf)
* See also: [John Sullivan - Advancing Enterprise DDD - Reinstating the Aggregate](https://scabl.blogspot.com/2015/04/aeddd-9.html)
*/
interface Entity<T : AggregateRoot<T, *>, ID> : Identifiable<ID>
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.jmolecules.ddd.types

/**
* An identifiable type, i.e. anything that exposes an [Identifier].
*
* Kotlin's counterpart of Java's [Identifiable](https://github.com/xmolecules/jmolecules/blob/main/jmolecules-ddd/src/main/java/org/jmolecules/ddd/types/Identifiable.java)
*
* @author Jocelyn Ntakpe
* @since 1.0
*/
interface Identifiable<ID> {

/**
* Identifier
*/
val id: ID
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.jmolecules.ddd.types

/**
* Marker interface for identifiers. Exists primarily to easily identify types that are supposed to be identifiers
* within the code base and let the compiler verify the correctness of declared relationships.
*
* Kotlin's counterpart of Java's [Identifier](https://github.com/xmolecules/jmolecules/blob/main/jmolecules-ddd/src/main/java/org/jmolecules/ddd/types/Identifier.java)
*
* @author Jocelyn Ntakpe
* @since 1.0
* @see Identifiable
*/
interface Identifier
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.jmolecules.ddd.types

/**
* Identifies a [Repository]. Repositories simulate a collection of aggregates to which aggregate instances can be
* added and removed. They usually also expose API to select a subset of aggregates matching certain criteria. Access to
* projections of an aggregate might be provided as well but also via a dedicated separate abstraction.
*
* Implementations use a dedicated persistence mechanism appropriate to the data structure and query requirements at
* hand. However, they should make sure that no persistence mechanism specific APIs leak into client code.
*
* Kotlin's counterpart of Java's [Repository](https://github.com/xmolecules/jmolecules/blob/main/jmolecules-ddd/src/main/java/org/jmolecules/ddd/types/Repository.java)
*
* @author Jocelyn Ntakpe
* @since 1.0
* @see [AggregateRoot]
*
* See also: [Domain-Driven Design Reference (Evans) - Repositories](https://domainlanguage.com/wp-content/uploads/2016/05/DDD_Reference_2015-03.pdf)
*/
interface Repository<T : AggregateRoot<T, ID>, ID : Identifier>
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package org.jmolecules.ddd.types

/**
* Simple implementation of [Association] to effectively only define [equals] and
* [hashCode] on [Association]'s static factory methods.
*
* Kotlin's equivalent of Java's [SimpleAssociation](https://github.com/xmolecules/jmolecules/blob/main/jmolecules-ddd/src/main/java/org/jmolecules/ddd/types/SimpleAssociation.java)
*
* @author Jocelyn Ntakpe
* @since 1.0
* @see Association.forId
* @see Association.forAggregate
*/
class SimpleAssociation<T : AggregateRoot<T, ID>, ID : Identifier>(private val identifier: () -> ID) :
Association<T, ID> {

override val id: ID
get() = identifier()

override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false

other as SimpleAssociation<*, *>

if (id != other.id) return false

return true
}

override fun hashCode(): Int = id.hashCode()

override fun toString(): String = id.toString()
}
Loading

0 comments on commit 6b0a9d6

Please sign in to comment.