diff --git a/jmolecules-archunit/src/main/java/org/jmolecules/archunit/JMoleculesDddRules.java b/jmolecules-archunit/src/main/java/org/jmolecules/archunit/JMoleculesDddRules.java
index 7500046..d76a1c1 100644
--- a/jmolecules-archunit/src/main/java/org/jmolecules/archunit/JMoleculesDddRules.java
+++ b/jmolecules-archunit/src/main/java/org/jmolecules/archunit/JMoleculesDddRules.java
@@ -35,6 +35,8 @@
import com.tngtech.archunit.base.DescribedPredicate;
import com.tngtech.archunit.core.domain.JavaClass;
import com.tngtech.archunit.core.domain.JavaField;
+import com.tngtech.archunit.core.domain.JavaParameterizedType;
+import com.tngtech.archunit.core.domain.JavaType;
import com.tngtech.archunit.core.domain.properties.CanBeAnnotated;
import com.tngtech.archunit.lang.ArchCondition;
import com.tngtech.archunit.lang.ArchRule;
@@ -58,7 +60,8 @@
* @author Oliver Drotbohm
* @author Torsten Juergeleit
* @author Hasan Kara
- * @see Advancing Enterprise DDD - Reinstating the Aggregate
+ * @see Advancing Enterprise DDD - Reinstating the
+ * Aggregate
*/
public class JMoleculesDddRules {
@@ -146,7 +149,8 @@ public static ArchRule entitiesShouldBeDeclaredForUseInSameAggregate() {
public static ArchRule aggregateReferencesShouldBeViaIdOrAssociation() {
DescribedPredicate referenceAnAggregateRoot = areAssignableTo(AggregateRoot.class)
- .or(hasFieldTypeAnnotatedWith(org.jmolecules.ddd.annotation.AggregateRoot.class));
+ .or(hasFieldTypeAnnotatedWith(org.jmolecules.ddd.annotation.AggregateRoot.class))
+ .or(hasParameterizedFieldOfTypeAnnotatedWith(org.jmolecules.ddd.annotation.AggregateRoot.class));
return ArchRuleDefinition.fields() //
.that(new OwnerMatches(IS_IDENTIFIABLE).and(referenceAnAggregateRoot)) //
@@ -194,6 +198,11 @@ private static FieldTypeIsAnnotatedWith hasFieldTypeAnnotatedWith(Class extend
return new FieldTypeIsAnnotatedWith(type);
}
+ private static DescribedPredicate super JavaField> hasParameterizedFieldOfTypeAnnotatedWith(
+ Class extends Annotation> type) {
+ return new ParameterizedFieldOfTypeAnnotatedWith(type);
+ }
+
private static class IsDeclaredToUseTheSameAggregate extends ArchCondition {
private static final ResolvableType COLLECTION_TYPE = ResolvableType.forClass(Collection.class);
@@ -284,6 +293,37 @@ public boolean test(JavaField input) {
}
}
+ private static class ParameterizedFieldOfTypeAnnotatedWith extends DescribedPredicate {
+
+ private final DescribedPredicate isAnnotatedWith;
+
+ public ParameterizedFieldOfTypeAnnotatedWith(Class extends Annotation> type) {
+
+ super("is collection of type annotated with %s", type.getSimpleName());
+
+ this.isAnnotatedWith = CanBeAnnotated.Predicates.annotatedWith(type);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see java.util.function.Predicate#test(java.lang.Object)
+ */
+ @Override
+ public boolean test(JavaField input) {
+
+ if (!(input.getType() instanceof JavaParameterizedType)) {
+ return false;
+ }
+
+ JavaParameterizedType parameterizedType = (JavaParameterizedType) input.getType();
+
+ return parameterizedType.getActualTypeArguments()
+ .stream()
+ .map(JavaType::toErasure)
+ .anyMatch(isAnnotatedWith);
+ }
+ }
+
private static class IsAssignableTypeField extends DescribedPredicate {
private static final ResolvableType COLLECTION_TYPE = ResolvableType.forClass(Collection.class);
diff --git a/jmolecules-archunit/src/test/java/org/jmolecules/archunit/JMoleculesDddRulesUnitTest.java b/jmolecules-archunit/src/test/java/org/jmolecules/archunit/JMoleculesDddRulesUnitTest.java
index 9bdf668..1bd87db 100644
--- a/jmolecules-archunit/src/test/java/org/jmolecules/archunit/JMoleculesDddRulesUnitTest.java
+++ b/jmolecules-archunit/src/test/java/org/jmolecules/archunit/JMoleculesDddRulesUnitTest.java
@@ -65,7 +65,11 @@ void detectsViolations(JavaClasses classes) {
violation(SampleValueObject.class, "annotatedEntity", AnnotatedEntity.class, null),
violation(SampleValueObject.class, "aggregate", SampleAggregate.class, null),
violation(SampleValueObject.class, "annotatedAggregate", AnnotatedAggregate.class, null),
- violation(SampleGrandChildEntity.class, "otherEntity", OtherEntity.class, OtherAggregate.class) // GH-222
+ violation(SampleGrandChildEntity.class, "otherEntity", OtherEntity.class, OtherAggregate.class), // GH-222
+ violation(OtherAnnotatedAggregate.class, "invalidAnnotatedAggregate", AnnotatedAggregate.class, null), //
+ violation(OtherAnnotatedAggregate.class, "invalidAnnotatedAggregateInCollection", Collection.class,
+ Association.class), //
+ violation(OtherAnnotatedAggregate.class, "invalidAnnotatedAggregateInMap", Map.class, Association.class) //
);
}
@@ -123,4 +127,20 @@ static class SampleValueObject implements ValueObject {
SampleAggregate aggregate;
AnnotatedAggregate annotatedAggregate;
}
+
+ @org.jmolecules.ddd.annotation.AggregateRoot
+ static class OtherAnnotatedAggregate {
+
+ @org.jmolecules.ddd.annotation.Identity Long id;
+ AnnotatedAggregate invalidAnnotatedAggregate;
+ Collection invalidAnnotatedAggregateInCollection;
+ Map invalidAnnotatedAggregateInMap;
+ }
+
+ @org.jmolecules.ddd.annotation.AggregateRoot
+ static class ThirdAnnotatedAggregate {
+
+ @org.jmolecules.ddd.annotation.Identity Long id;
+ AnnotatedEntity valid;
+ }
}