You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by da...@apache.org on 2021/07/20 09:34:29 UTC

[isis] 01/02: ISIS-2807: sketches out arch tests for entities, domain objects and entities

This is an automated email from the ASF dual-hosted git repository.

danhaywood pushed a commit to branch ISIS-2807-arch-tests
in repository https://gitbox.apache.org/repos/asf/isis.git

commit ab1947c06243277607ad9fe8485f85c8febb866f
Author: danhaywood <da...@haywood-associates.co.uk>
AuthorDate: Tue Jul 20 10:26:53 2021 +0100

    ISIS-2807: sketches out arch tests for entities, domain objects and entities
    
    ... and updates docs
---
 .../adoc/modules/archtestsupport/pages/about.adoc  |  85 +++------
 testing/archtestsupport/applib/pom.xml             |   8 +
 .../applib/classrules/ArchitectureClassRules.java  |  35 ----
 .../classrules/ArchitectureDomainObjectRules.java  |  57 ++++++
 .../classrules/ArchitectureDomainServiceRules.java |  58 ++++++
 .../applib/classrules/ArchitectureJdoRules.java    |  77 ++++++++
 .../applib/classrules/ArchitectureJpaRules.java    |  95 ++++++++++
 .../classrules/CommonDescribedPredicates.java      |  45 +++++
 .../modulerules/ArchitectureModuleRules.java       | 199 ++++++++++++++++-----
 .../applib/modulerules/Subpackage.java             |  48 +++++
 .../applib/modulerules/SubpackageEnum.java         | 169 +++++++++++++++++
 .../packagerules/ArchitecturePackageRules.java     |  33 ----
 .../applib/packagerules/Subpackage.java            |  20 ---
 .../applib/packagerules/SubpackageEnum.java        |  69 -------
 .../applib/packagerules/SubpackageType.java        |  22 ---
 15 files changed, 743 insertions(+), 277 deletions(-)

diff --git a/testing/archtestsupport/adoc/modules/archtestsupport/pages/about.adoc b/testing/archtestsupport/adoc/modules/archtestsupport/pages/about.adoc
index 502bbeb..30b5da4 100644
--- a/testing/archtestsupport/adoc/modules/archtestsupport/pages/about.adoc
+++ b/testing/archtestsupport/adoc/modules/archtestsupport/pages/about.adoc
@@ -48,85 +48,54 @@ public class ModuleTests {
 <.> just obtains the list of modules
 
 
-== Package Tests
+== Module Package Tests
 
 CAUTION: TODO - to flesh out
 
+Defines the rules for which subpackages or a module to check and whether the classes in those subpackages can access the classes in other subpackages either for the same module, or for modules that are referenced (`Import`ed) directly or transitively.
+
+
 [source,java]
 .Subpackage
 ----
 public interface Subpackage {
-    String getName();
-
-    SubpackageType getSubpackageType();
-
-    String packageIdentifierWithin(Class<?> moduleClass);
 
-    default void defineLayer(Architectures.LayeredArchitecture layeredArchitecture, Class<?> moduleClass) {
-        val layerDefinition = getSubpackageType().defineLayer(this, moduleClass, layeredArchitecture);
-        layerDefinition.definedBy(packageIdentifierWithin(moduleClass));
-    }
-
-    boolean canReference(Subpackage referenced);
+    String getName();                                               // <.>
+    List<String> mayBeAccessedBySubpackagesInSameModule();          // <.>
+    List<String> mayBeAccessedBySubpackagesInReferencingModules();  // <.>
 }
 ----
 
-The `SubpackageEnum` provides an off the shelf implementation.
+<.> The name of the subpackage, for example &quot;dom&quot;, &quot;api&quot, &quot;spi&quot; or &quot;fixtures&quot;.
 
-[source,java]
-.PackageTestsForCustomerModule.java
-----
-@AnalyzeClasses(
-    packagesOf = {CustomerModule.class},                                            // <.>
-    importOptions = {ImportOption.DoNotIncludeTests.class}
-)
-public class PackageTestsForCustomerModule {
+<.> A list of the (names of the) subpackages where classes in the same module as this package have access.
++
+For example, the &quot;dom&quot; subpackage can probably be referenced from the &quot;menu&quot; subpackage, but not vice versa.
++
+The special value of &quot;*&quot; is a wildcard meaning that all subpackages (in the same module) can access.
 
-    @ArchTest public static ArchRule code_dependencies_follow_module_subpackages =
-      ArchitecturePackageRules.code_dependencies_follow_module_subpackages(         // <.>
-        CustomerModule.class,                                                       // <1>
-        Arrays.asList(SubpackageEnum.values()));                                    // <.>
+<.> A list of the (names of the) subpackages where classes in the packages of other referencing modules may have access.
++
+For example, in some cases the the &quot;dom&quot; subpackage may <i>not</i> be accessible from other modules if the intention is to require all programmatic access through an &quot;api&quot; subpackage (where the classes in `dom` implement interfaces defined in `api`).
++
+The special value of &quot;*&quot; is a wildcard meaning that all subpackages (in other modules) can access.
 
-}
-----
 
-<.> the module to be analyzed.
-A similar test should be created for each and every module in the app.
-<.> xxx
-<.> xxx
+The `SubpackageEnum` provides an off the shelf implementation.
+
 
+== Entity Tests
 
-== Class Tests
+These are provided for both JPA and JDO.
 
 CAUTION: TODO - to flesh out
 
-[source,java]
-.ClassTests.java
-----
-@AnalyzeClasses(
-        packagesOf = {
-                CustomerModule.class                                                // <.>
-                , OrderModule.class
-                , ProductModule.class
-        },
-        importOptions = { ImportOption.DoNotIncludeTests.class }
-)
-public class ClassTests {
 
-    @ArchTest
-    static ArchRule classes_annotated_with_Entity_are_also_annotated_with_DomainObject =
-      ArchitectureClassRules.classes_annotated_with_Entity_must_also_be_annotated_with_DomainObject();  // <.>
+== DomainObjectTests
 
-    @ArchTest
-    static ArchRule classes_annotated_with_Entity_are_also_annotated_with_XmlJavaTypeAdapter =
-      ArchitectureClassRules.classes_annotated_with_Entity_must_also_be_annotated_with_XmlJavaAdapter(); // <.>
+CAUTION: TODO - to flesh out
 
-    @ArchTest
-    static ArchRule classes_annotated_with_DomainObject_are_also_annotated_with_DomainObjectLayout =
-      ArchitectureClassRules.classes_annotated_with_DomainObject_must_also_be_annotated_with_DomainObjectLayout(); // <.>
-----
+== DomainServiceTests
+
+CAUTION: TODO - to flesh out
 
-<.> the modules of the application to be scanned
-<.> xxx
-<.> xxx
-<.> xxx
diff --git a/testing/archtestsupport/applib/pom.xml b/testing/archtestsupport/applib/pom.xml
index 1ce9cd1..b408913 100644
--- a/testing/archtestsupport/applib/pom.xml
+++ b/testing/archtestsupport/applib/pom.xml
@@ -75,6 +75,7 @@
             <groupId>com.tngtech.archunit</groupId>
             <artifactId>archunit-junit5-api</artifactId>
         </dependency>
+
         <dependency>
             <groupId>com.tngtech.archunit</groupId>
             <artifactId>archunit-junit5-engine</artifactId>
@@ -85,6 +86,13 @@
             <artifactId>slf4j-api</artifactId>
         </dependency>
 
+        <!-- PROVIDED (to avoid polluting the classpath -->
+        <dependency>
+            <groupId>org.datanucleus</groupId>
+            <artifactId>javax.jdo</artifactId>
+            <scope>provided</scope>
+        </dependency>
+
     </dependencies>
 
     <profiles>
diff --git a/testing/archtestsupport/applib/src/main/java/org/apache/isis/testing/archtestsupport/applib/classrules/ArchitectureClassRules.java b/testing/archtestsupport/applib/src/main/java/org/apache/isis/testing/archtestsupport/applib/classrules/ArchitectureClassRules.java
deleted file mode 100644
index eb8eba2..0000000
--- a/testing/archtestsupport/applib/src/main/java/org/apache/isis/testing/archtestsupport/applib/classrules/ArchitectureClassRules.java
+++ /dev/null
@@ -1,35 +0,0 @@
-package org.apache.isis.testing.archtestsupport.applib.classrules;
-
-import javax.persistence.Entity;
-import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
-
-import com.tngtech.archunit.lang.ArchRule;
-
-import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;
-
-import org.apache.isis.applib.annotation.DomainObject;
-import org.apache.isis.applib.annotation.DomainObjectLayout;
-
-import lombok.experimental.UtilityClass;
-
-@UtilityClass
-public class ArchitectureClassRules {
-
-  public static ArchRule classes_annotated_with_Entity_must_also_be_annotated_with_DomainObject() {
-    return classes()
-          .that().areAnnotatedWith(Entity.class)
-          .should().beAnnotatedWith(DomainObject.class);
-  }
-
-  public static ArchRule classes_annotated_with_Entity_must_also_be_annotated_with_XmlJavaAdapter() {
-    return classes()
-      .that().areAnnotatedWith(Entity.class)
-      .should().beAnnotatedWith(XmlJavaTypeAdapter.class);
-  }
-
-  public static ArchRule classes_annotated_with_DomainObject_must_also_be_annotated_with_DomainObjectLayout() {
-    return classes()
-      .that().areAnnotatedWith(DomainObject.class)
-      .should().beAnnotatedWith(DomainObjectLayout.class);
-  }
-}
diff --git a/testing/archtestsupport/applib/src/main/java/org/apache/isis/testing/archtestsupport/applib/classrules/ArchitectureDomainObjectRules.java b/testing/archtestsupport/applib/src/main/java/org/apache/isis/testing/archtestsupport/applib/classrules/ArchitectureDomainObjectRules.java
new file mode 100644
index 0000000..cc61e78
--- /dev/null
+++ b/testing/archtestsupport/applib/src/main/java/org/apache/isis/testing/archtestsupport/applib/classrules/ArchitectureDomainObjectRules.java
@@ -0,0 +1,57 @@
+package org.apache.isis.testing.archtestsupport.applib.classrules;
+
+import com.tngtech.archunit.base.DescribedPredicate;
+import com.tngtech.archunit.core.domain.JavaAnnotation;
+import com.tngtech.archunit.lang.ArchRule;
+
+import org.apache.isis.applib.annotation.DomainObject;
+import org.apache.isis.applib.annotation.DomainObjectLayout;
+
+import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;
+import lombok.experimental.UtilityClass;
+import lombok.val;
+
+/**
+ * A library of architecture tests to ensure coding conventions are followed for classes annotated with
+ * {@link DomainObject}.
+ *
+ * @since 2.0 {@index}
+ */
+@UtilityClass
+public class ArchitectureDomainObjectRules {
+
+  /**
+   * This rule requires that classes annotated with the {@link DomainObject} annotation must specify their
+   * {@link DomainObject#logicalTypeName() logicalTypeName}.
+   */
+  public static ArchRule classes_annotated_with_DomainObject_must_specify_logicalTypeName() {
+    return classes()
+            .that().areAnnotatedWith(DomainObject.class)
+            .should().beAnnotatedWith(DomainObject_logicalTypeName());
+  }
+
+  static DescribedPredicate<JavaAnnotation<?>> DomainObject_logicalTypeName() {
+    return new DescribedPredicate<JavaAnnotation<?>>("@DomainObject(logicalTypeName=...)") {
+      @Override public boolean apply(final JavaAnnotation<?> javaAnnotation) {
+        if (javaAnnotation.getRawType().isEquivalentTo(DomainObject.class)) {
+          return false;
+        }
+        val properties = javaAnnotation.getProperties();
+        val value = properties.get("logicalTypeName");
+        return value instanceof String && ((String) value).length() > 0;
+      }
+    };
+  }
+
+  /**
+   * This rule requires that classes annotated with the {@link DomainObject} annotation must also be
+   * annotated with the {@link DomainObjectLayout} annotation.
+   */
+  public static ArchRule classes_annotated_with_DomainObject_must_also_be_annotated_with_DomainObjectLayout() {
+    return classes()
+            .that().areAnnotatedWith(DomainObject.class)
+            .should().beAnnotatedWith(DomainObjectLayout.class);
+  }
+
+
+}
diff --git a/testing/archtestsupport/applib/src/main/java/org/apache/isis/testing/archtestsupport/applib/classrules/ArchitectureDomainServiceRules.java b/testing/archtestsupport/applib/src/main/java/org/apache/isis/testing/archtestsupport/applib/classrules/ArchitectureDomainServiceRules.java
new file mode 100644
index 0000000..5ab80f9
--- /dev/null
+++ b/testing/archtestsupport/applib/src/main/java/org/apache/isis/testing/archtestsupport/applib/classrules/ArchitectureDomainServiceRules.java
@@ -0,0 +1,58 @@
+package org.apache.isis.testing.archtestsupport.applib.classrules;
+
+import com.tngtech.archunit.base.DescribedPredicate;
+import com.tngtech.archunit.core.domain.JavaAnnotation;
+import com.tngtech.archunit.lang.ArchRule;
+
+import org.apache.isis.applib.annotation.DomainObject;
+import org.apache.isis.applib.annotation.DomainObjectLayout;
+import org.apache.isis.applib.annotation.DomainService;
+
+import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;
+import lombok.experimental.UtilityClass;
+import lombok.val;
+
+/**
+ * A library of architecture tests to ensure coding conventions are followed for classes annotated with
+ * {@link DomainService}.
+ *
+ * @since 2.0 {@index}
+ */
+@UtilityClass
+public class ArchitectureDomainServiceRules {
+
+  /**
+   * This rule requires that classes annotated with the {@link DomainService} annotation must specify their
+   * {@link DomainService#logicalTypeName() logicalTypeName}.
+   */
+  public static ArchRule classes_annotated_with_DomainService_must_specify_logicalTypeName() {
+    return classes()
+            .that().areAnnotatedWith(DomainService.class)
+            .should().beAnnotatedWith(DomainService_logicalTypeName());
+  }
+
+  static DescribedPredicate<JavaAnnotation<?>> DomainService_logicalTypeName() {
+    return new DescribedPredicate<JavaAnnotation<?>>("@DomainService(logicalTypeName=...)") {
+      @Override public boolean apply(final JavaAnnotation<?> javaAnnotation) {
+        if (javaAnnotation.getRawType().isEquivalentTo(DomainService.class)) {
+          return false;
+        }
+        val properties = javaAnnotation.getProperties();
+        val value = properties.get("logicalTypeName");
+        return value instanceof String && ((String) value).length() > 0;
+      }
+    };
+  }
+
+  /**
+   * This rule requires that classes annotated with the {@link DomainObject} annotation must also be
+   * annotated with the {@link DomainObjectLayout} annotation.
+   */
+  public static ArchRule classes_annotated_with_DomainObject_must_also_be_annotated_with_DomainObjectLayout() {
+    return classes()
+            .that().areAnnotatedWith(DomainObject.class)
+            .should().beAnnotatedWith(DomainObjectLayout.class);
+  }
+
+
+}
diff --git a/testing/archtestsupport/applib/src/main/java/org/apache/isis/testing/archtestsupport/applib/classrules/ArchitectureJdoRules.java b/testing/archtestsupport/applib/src/main/java/org/apache/isis/testing/archtestsupport/applib/classrules/ArchitectureJdoRules.java
new file mode 100644
index 0000000..662da9b
--- /dev/null
+++ b/testing/archtestsupport/applib/src/main/java/org/apache/isis/testing/archtestsupport/applib/classrules/ArchitectureJdoRules.java
@@ -0,0 +1,77 @@
+package org.apache.isis.testing.archtestsupport.applib.classrules;
+
+import com.tngtech.archunit.lang.ArchRule;
+
+import org.apache.isis.applib.annotation.DomainObject;
+
+import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;
+import lombok.experimental.UtilityClass;
+
+/**
+ * A library of architecture tests to ensure coding conventions are followed for classes annotated with
+ * the JDO {@link javax.jdo.annotations.PersistenceCapable} annotation.
+ *
+ * @since 2.0 {@index}
+ */
+@UtilityClass
+public class ArchitectureJdoRules {
+
+  /**
+   * This rule requires that classes annotated with the JDO {@link javax.jdo.annotations.PersistenceCapable} annotation
+   * must also be annotated with the Apache Isis {@link DomainObject} annotation specifying that its
+   * {@link DomainObject#nature() nature} is an {@link org.apache.isis.applib.annotation.Nature#ENTITY entity}.
+   */
+  public static ArchRule classes_annotated_with_PersistenceCapable_must_also_be_annotated_with_DomainObject_nature_of_ENTITY() {
+    return classes()
+          .that().areAnnotatedWith(javax.jdo.annotations.PersistenceCapable.class)
+          .should().beAnnotatedWith(CommonDescribedPredicates.DomainObject_nature_ENTITY());
+  }
+
+  /**
+   * This rule requires that classes annotated with the JDO {@link javax.jdo.annotations.PersistenceCapable} annotation
+   * must also be annotated with the Apache Isis {@link javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter} annotation
+   * with a value of {@link org.apache.isis.applib.jaxb.PersistentEntityAdapter}<code>.class</code>.
+   *
+   * <p>
+   *     Tnis is so that entities can be transparently referenced from XML-style view models.
+   * </p>
+   */
+  public static ArchRule classes_annotated_with_PersistenceCapable_must_also_be_annotated_with_XmlJavaAdapter() {
+    return classes()
+      .that().areAnnotatedWith(javax.jdo.annotations.PersistenceCapable.class)
+            .should().beAnnotatedWith(CommonDescribedPredicates.XmlJavaTypeAdapter_value_PersistentEntityAdapter());
+  }
+
+  /**
+   * This rule requires that classes annotated with the JDO {@link javax.jdo.annotations.PersistenceCapable} annotation
+   * must also implement {@link Comparable}.
+   *
+   * <p>
+   *     This is so that entities have a natural ordering and can safely be added to parented collections of type
+   *     {@link java.util.SortedSet}.
+   * </p>
+   */
+  public static ArchRule classes_annotated_with_PersistenceCapable_must_also_implement_Comparable() {
+    return classes()
+      .that().areAnnotatedWith(javax.jdo.annotations.PersistenceCapable.class)
+      .should().implement(Comparable.class);
+  }
+
+  /**
+   * This rule requires that classes annotated with the JDO {@link javax.jdo.annotations.PersistenceCapable} annotation
+   * must also be annotated with the JDO {@link javax.jdo.annotations.Uniques} or {@link javax.jdo.annotations.Unique}
+   * constraints.
+   *
+   * <p>
+   *     This is so that entities will have an alternative business key in addition to the system-defined surrogate
+   *     key.
+   * </p>
+   */
+  public static ArchRule classes_annotated_with_PersistenceCapable_must_also_be_annotated_with_Uniques_or_Unique() {
+    return classes()
+      .that().areAnnotatedWith(javax.jdo.annotations.PersistenceCapable.class)
+      .should().beAnnotatedWith(javax.jdo.annotations.Uniques.class)
+      .orShould().beAnnotatedWith(javax.jdo.annotations.Unique.class);
+  }
+
+}
diff --git a/testing/archtestsupport/applib/src/main/java/org/apache/isis/testing/archtestsupport/applib/classrules/ArchitectureJpaRules.java b/testing/archtestsupport/applib/src/main/java/org/apache/isis/testing/archtestsupport/applib/classrules/ArchitectureJpaRules.java
new file mode 100644
index 0000000..367f5ab
--- /dev/null
+++ b/testing/archtestsupport/applib/src/main/java/org/apache/isis/testing/archtestsupport/applib/classrules/ArchitectureJpaRules.java
@@ -0,0 +1,95 @@
+package org.apache.isis.testing.archtestsupport.applib.classrules;
+
+import javax.persistence.Entity;
+import javax.persistence.Table;
+
+import com.tngtech.archunit.base.DescribedPredicate;
+import com.tngtech.archunit.core.domain.JavaAnnotation;
+import com.tngtech.archunit.lang.ArchRule;
+
+import org.apache.isis.applib.annotation.DomainObject;
+
+import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;
+import lombok.experimental.UtilityClass;
+import lombok.val;
+
+/**
+ * A library of architecture tests to ensure coding conventions are followed for classes annotated with
+ * the JPA {@link Entity} annotation.
+ *
+ * @since 2.0 {@index}
+ */
+@UtilityClass
+public class ArchitectureJpaRules {
+
+  /**
+   * This rule requires that classes annotated with the JPA {@link Entity} annotation must also be
+   * annotated with the Apache Isis {@link DomainObject} annotation specifying that its
+   * {@link DomainObject#nature() nature} is an {@link org.apache.isis.applib.annotation.Nature#ENTITY entity}.
+   */
+  public static ArchRule classes_annotated_with_Entity_must_also_be_annotated_with_DomainObject_nature_of_ENTITY() {
+    return classes()
+            .that().areAnnotatedWith(Entity.class)
+            .should().beAnnotatedWith(CommonDescribedPredicates.DomainObject_nature_ENTITY());
+  }
+
+  /**
+   * This rule requires that classes annotated with the JPA {@link Entity} annotation must also be
+   * annotated with the Apache Isis {@link javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter} annotation
+   * with a value of {@link org.apache.isis.applib.jaxb.PersistentEntityAdapter}<code>.class</code>.
+   *
+   * <p>
+   *     Tnis is so that entities can be transparently referenced from XML-style view models.
+   * </p>
+   */
+  public static ArchRule classes_annotated_with_Entity_must_also_be_annotated_with_XmlJavaAdapter_PersistentEntityAdapter() {
+    return classes()
+            .that().areAnnotatedWith(Entity.class)
+            .should().beAnnotatedWith(CommonDescribedPredicates.XmlJavaTypeAdapter_value_PersistentEntityAdapter());
+  }
+
+  /**
+   * This rule requires that classes annotated with the JPA {@link Entity} annotation must also be
+   * implement {@link Comparable}.
+   *
+   * <p>
+   *     This is so that entities have a natural ordering and can safely be added to parented collections of type
+   *     {@link java.util.SortedSet}.
+   * </p>
+   */
+  public static ArchRule classes_annotated_with_Entity_must_also_implement_Comparable() {
+    return classes()
+            .that().areAnnotatedWith(Entity.class)
+            .should().implement(Comparable.class);
+  }
+
+  /**
+   * This rule requires that classes annotated with the JPA {@link Entity} annotation must also be annotated with the
+   * JPA {@link Table} annotation which includes {@link Table#uniqueConstraints() uniqueConstraints}.
+   *
+   * <p>
+   *     This is so that entities will have an alternative business key in addition to the system-defined surrogate
+   *     key.
+   * </p>
+   */
+  public static ArchRule classes_annotated_with_Entity_must_also_be_annotated_with_Table_with_uniqueConstraints() {
+    return classes()
+            .that().areAnnotatedWith(Entity.class)
+            .should().beAnnotatedWith(Table_uniqueConstraints());
+  }
+
+  private static DescribedPredicate<JavaAnnotation<?>> Table_uniqueConstraints() {
+    return new DescribedPredicate<JavaAnnotation<?>>("@Table(uniqueConstraints=...)") {
+      @Override public boolean apply(final JavaAnnotation<?> javaAnnotation) {
+        if (javaAnnotation.getRawType().isEquivalentTo(Table.class)) {
+          return false;
+        }
+        val properties = javaAnnotation.getProperties();
+        val uniqueConstraints = properties.get("uniqueConstraints");
+        return uniqueConstraints != null;
+      }
+    };
+  }
+
+
+}
diff --git a/testing/archtestsupport/applib/src/main/java/org/apache/isis/testing/archtestsupport/applib/classrules/CommonDescribedPredicates.java b/testing/archtestsupport/applib/src/main/java/org/apache/isis/testing/archtestsupport/applib/classrules/CommonDescribedPredicates.java
new file mode 100644
index 0000000..b57c556
--- /dev/null
+++ b/testing/archtestsupport/applib/src/main/java/org/apache/isis/testing/archtestsupport/applib/classrules/CommonDescribedPredicates.java
@@ -0,0 +1,45 @@
+package org.apache.isis.testing.archtestsupport.applib.classrules;
+
+import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
+
+import com.tngtech.archunit.base.DescribedPredicate;
+import com.tngtech.archunit.core.domain.JavaAnnotation;
+
+import org.apache.isis.applib.annotation.DomainObject;
+import org.apache.isis.applib.annotation.Nature;
+import org.apache.isis.applib.jaxb.PersistentEntityAdapter;
+
+import lombok.experimental.UtilityClass;
+import lombok.val;
+
+@UtilityClass
+public class CommonDescribedPredicates {
+
+  static DescribedPredicate<JavaAnnotation<?>> DomainObject_nature_ENTITY() {
+    return new DescribedPredicate<JavaAnnotation<?>>("@DomainObject(nature=ENTITY)") {
+      @Override public boolean apply(final JavaAnnotation<?> javaAnnotation) {
+        if (javaAnnotation.getRawType().isEquivalentTo(DomainObject.class)) {
+          return false;
+        }
+        val properties = javaAnnotation.getProperties();
+        val nature = properties.get("nature");
+        return nature == Nature.ENTITY;
+      }
+    };
+  }
+
+  static DescribedPredicate<JavaAnnotation<?>> XmlJavaTypeAdapter_value_PersistentEntityAdapter() {
+    return new DescribedPredicate<JavaAnnotation<?>>("@XmlJavaTypeAdapter(PersistentEntityAdapter.class)") {
+      @Override public boolean apply(final JavaAnnotation<?> javaAnnotation) {
+        if (javaAnnotation.getRawType().isEquivalentTo(XmlJavaTypeAdapter.class)) {
+          return false;
+        }
+        val properties = javaAnnotation.getProperties();
+        val value = properties.get("value");
+        return value == PersistentEntityAdapter.class;
+      }
+    };
+  }
+
+
+}
diff --git a/testing/archtestsupport/applib/src/main/java/org/apache/isis/testing/archtestsupport/applib/modulerules/ArchitectureModuleRules.java b/testing/archtestsupport/applib/src/main/java/org/apache/isis/testing/archtestsupport/applib/modulerules/ArchitectureModuleRules.java
index 76520ea..d6ff0b8 100644
--- a/testing/archtestsupport/applib/src/main/java/org/apache/isis/testing/archtestsupport/applib/modulerules/ArchitectureModuleRules.java
+++ b/testing/archtestsupport/applib/src/main/java/org/apache/isis/testing/archtestsupport/applib/modulerules/ArchitectureModuleRules.java
@@ -1,5 +1,6 @@
 package org.apache.isis.testing.archtestsupport.applib.modulerules;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
@@ -9,6 +10,9 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import javax.annotation.Nullable;
 
 import com.tngtech.archunit.junit.AnalyzeClasses;
 import com.tngtech.archunit.library.Architectures;
@@ -16,18 +20,28 @@ import com.tngtech.archunit.library.Architectures;
 import org.springframework.context.annotation.Import;
 import org.springframework.core.annotation.AnnotationUtils;
 
-import lombok.val;
 import lombok.experimental.UtilityClass;
+import lombok.val;
 
-
+/**
+ * A library of architecture tests to ensure correct layering and usage of packages.
+ *
+ * @since 2.0 {@index}
+ */
 @UtilityClass
 public class ArchitectureModuleRules {
 
   /**
-   * Utility method.
+   * Utility method to aggregate the module classes (named &quot;XxxModule&quot;) that are extracted from the
+   * {@link AnalyzeClasses} annotation on the provided class.
    *
-   * @param clazz
-   * @return
+   * <p>
+   *     The result is intended to be passed into {@link #code_dependencies_follow_module_Imports(List)} and
+   *     {@link #code_dependencies_follow_module_Imports_and_subpackage_rules(List, List)}.
+   * </p>
+   *
+   * @see #code_dependencies_follow_module_Imports(List)
+   * @see #code_dependencies_follow_module_Imports_and_subpackage_rules(List, List)
    */
   public static List<Class<?>> analyzeClasses_packagesOf(Class<?> clazz) {
     val analyzeClassesAnnot = AnnotationUtils.findAnnotation(clazz, AnalyzeClasses.class);
@@ -39,38 +53,67 @@ public class ArchitectureModuleRules {
   /**
    * Ensures that the actual dependencies between classes within modules honour the module dependency graph inferred
    * from the {@link Import} statements of each module.
+   * 
+   * @see #code_dependencies_follow_module_Imports_and_subpackage_rules(List, List) 
+   */
+  public static Architectures.LayeredArchitecture code_dependencies_follow_module_Imports(
+      List<Class<?>> moduleClasses) {
+
+    return code_dependencies_follow_module_Imports_and_subpackage_rules(moduleClasses, Collections.emptyList());
+  }
+
+  /**
+   * Ensures that the actual dependencies between classes within modules honour the module dependency graph inferred
+   * from the {@link Import} statements of each module AND also ensures that the subpackages within those rules only
+   * access the allowed subpackages of both their own &local;local&local; and the subpackages of other modules that
+   * they might reference.
+   * 
+   * @see #code_dependencies_follow_module_Imports(List) 
    */
-  public static Architectures.LayeredArchitecture code_dependencies_follow_module_Imports(List<Class<?>> moduleClasses) {
+  public static Architectures.LayeredArchitecture code_dependencies_follow_module_Imports_and_subpackage_rules(
+      List<Class<?>> moduleClasses, List<Subpackage> subpackages) {
     val layeredArchitecture = Architectures.layeredArchitecture();
 
-    defineLayers(moduleClasses, layeredArchitecture);
+    defineLayers(moduleClasses, layeredArchitecture, subpackages);
 
     val directDependenciesByImported = new HashMap<Class<?>, Set<Class<?>>>();
     val directDependenciesByImporting = new HashMap<Class<?>, Set<Class<?>>>();
-    computeDirectDependencies(moduleClasses, directDependenciesByImported, directDependenciesByImporting);
+    computeDirectDependencies(moduleClasses, directDependenciesByImported,
+        directDependenciesByImporting);
 
     val transitiveDependenciesByImporting = new HashMap<Class<?>, Set<Class<?>>>();
-    computeTransitiveDependencies(moduleClasses, directDependenciesByImporting, transitiveDependenciesByImporting);
+    computeTransitiveDependencies(moduleClasses, directDependenciesByImporting,
+        transitiveDependenciesByImporting);
 
     val transitiveDependenciesByImported = invert(transitiveDependenciesByImporting);
-    checkLayerAccess(layeredArchitecture, transitiveDependenciesByImported);
+    checkLayerAccess(layeredArchitecture, transitiveDependenciesByImported, subpackages);
 
-    val importingClassesNotImported = new LinkedHashSet<Class<?>>(transitiveDependenciesByImporting.keySet());
+    val importingClassesNotImported = new LinkedHashSet<>(
+        transitiveDependenciesByImporting.keySet());
     importingClassesNotImported.removeAll(transitiveDependenciesByImported.keySet());
 
     checkNoAccessToTopmostLayers(layeredArchitecture, importingClassesNotImported);
     return layeredArchitecture;
   }
 
-  private static void defineLayers(List<Class<?>> moduleClasses, Architectures.LayeredArchitecture layeredArchitecture) {
-    moduleClasses.forEach(x -> {
-      final String moduleName = nameOf(x);
-      final String s = packageIdentifierFor(x);
-      layeredArchitecture.layer(moduleName).definedBy(s);
+  private static void defineLayers(List<Class<?>> moduleClasses,
+                                   Architectures.LayeredArchitecture layeredArchitecture,
+                                   List<Subpackage> subpackages) {
+    moduleClasses.forEach(moduleClass -> {
+      layeredArchitecture.layer(nameOf(moduleClass)).definedBy(packageIdentifierFor(moduleClass));
+
+      subpackages
+        .stream()
+        .map(Subpackage::getName)
+      .forEach(subpackageName ->
+          layeredArchitecture.optionalLayer(nameOf(moduleClass, subpackageName))
+            .definedBy(packageIdentifierFor(moduleClass, subpackageName)));
     });
   }
 
-  private static void computeDirectDependencies(List<Class<?>> moduleClasses, Map<Class<?>, Set<Class<?>>> directDependenciesByImported, Map<Class<?>, Set<Class<?>>> directDependenciesByImporting) {
+  private static void computeDirectDependencies(List<Class<?>> moduleClasses,
+      Map<Class<?>, Set<Class<?>>> directDependenciesByImported,
+      Map<Class<?>, Set<Class<?>>> directDependenciesByImporting) {
     moduleClasses.forEach(
       moduleClass -> {
         final Import importAnnotation = AnnotationUtils.findAnnotation(moduleClass, Import.class);
@@ -80,7 +123,8 @@ public class ArchitectureModuleRules {
           directDependenciesByImporting.put(moduleClass, importedClasses);
           importedClasses.forEach(
             importedClass -> {
-              val importingClasses = directDependenciesByImported.computeIfAbsent(importedClass, k -> new LinkedHashSet<>());
+                  val importingClasses = directDependenciesByImported
+                      .computeIfAbsent(importedClass, k -> new LinkedHashSet<>());
               importingClasses.add(moduleClass);
             }
           );
@@ -89,25 +133,52 @@ public class ArchitectureModuleRules {
     );
   }
 
-  private static void computeTransitiveDependencies(List<Class<?>> moduleClasses, Map<Class<?>, Set<Class<?>>> directDependenciesByImporting, Map<Class<?>, Set<Class<?>>> transitiveDependenciesByImporting) {
+  private static void computeTransitiveDependencies(List<Class<?>> moduleClasses,
+      Map<Class<?>, Set<Class<?>>> directDependenciesByImporting,
+      Map<Class<?>, Set<Class<?>>> transitiveDependenciesByImporting) {
     moduleClasses.forEach((moduleClass) -> {
       val transitiveDependencies = new LinkedHashSet<Class<?>>();
-      accumulateTransitiveDependencies(moduleClass, directDependenciesByImporting, transitiveDependencies);
+      accumulateTransitiveDependencies(moduleClass, directDependenciesByImporting,
+          transitiveDependencies);
       transitiveDependenciesByImporting.put(moduleClass, transitiveDependencies);
     });
   }
 
-  private static void checkLayerAccess(Architectures.LayeredArchitecture layeredArchitecture, Map<Class<?>, Set<Class<?>>> transitiveDependenciesByImported) {
-    transitiveDependenciesByImported.forEach((importedClass, importingClasses) -> {
-      final String importedModuleName = nameOf(importedClass);
-      final String[] importingModuleNames = namesOf(importingClasses);
+  private static void checkLayerAccess(
+      Architectures.LayeredArchitecture layeredArchitecture,
+      Map<Class<?>, Set<Class<?>>> transitiveDependenciesByImported,
+      List<Subpackage> subpackages) {
+    transitiveDependenciesByImported.forEach((importedModule, importingModules) -> {
+
       layeredArchitecture
-        .whereLayer(importedModuleName)
-        .mayOnlyBeAccessedByLayers(importingModuleNames);
+        // layering at the module level
+        .whereLayer(
+            nameOf(importedModule))
+        .mayOnlyBeAccessedByLayers(
+            namesOf(importingModules)
+        );
+
+      // in particular, access to subpackages
+      subpackages.forEach(subpackage -> {
+
+        val localModule = subpackage.mayBeAccessedBySubpackagesInSameModule().toArray(new String[]{});
+        val otherModules = subpackage.mayBeAccessedBySubpackagesInReferencingModules().toArray(new String[]{});
+        layeredArchitecture
+            .whereLayer(nameOf(importedModule, subpackage.getName()))
+            .mayOnlyBeAccessedByLayers(
+                both(
+                    namesOf(importedModule, localModule),
+                    namesOf(importingModules, otherModules)
+                )
+            );
+      });
+
     });
   }
 
-  private static void checkNoAccessToTopmostLayers(Architectures.LayeredArchitecture layeredArchitecture, Set<Class<?>> importingClassesNotImported) {
+  private static void checkNoAccessToTopmostLayers(
+      Architectures.LayeredArchitecture layeredArchitecture,
+      Set<Class<?>> importingClassesNotImported) {
     importingClassesNotImported.forEach(importingClass -> {
       final String importingModuleName = nameOf(importingClass);
       layeredArchitecture
@@ -117,18 +188,64 @@ public class ArchitectureModuleRules {
   }
 
   static String nameOf(Class<?> moduleClass) {
-    return moduleClass.getSimpleName();
+    return nameOf(moduleClass, null);
+  }
+
+  static String nameOf(Class<?> moduleClass, @Nullable final String subpackageName) {
+    val simpleName = moduleClass.getSimpleName();
+    val moduleName = simpleName.replace("Module", "");
+    return moduleName + (subpackageName != null ? (" " + subpackageName) : "");
+  }
+
+  static String[] namesOf(Class<?> moduleClass, String... subpackageNames) {
+    val names = new ArrayList<String>();
+    if (subpackageNames == null) {
+      return null;
+    }
+    if (subpackageNames.length == 0 ||
+        subpackageNames.length == 1 && subpackageNames[0].equals("*")) {
+      names.add(nameOf(moduleClass));
+    } else {
+      Arrays.stream(subpackageNames).forEach(subpackageName ->
+          names.add(nameOf(moduleClass, subpackageName)));
+    }
+    return names.toArray(new String[] {});
+  }
+
+  static String[] namesOf(Set<Class<?>> importingClasses, @Nullable String... subpackageNames) {
+    val names = new ArrayList<String>();
+    importingClasses.forEach(importingClass -> {
+      if (subpackageNames == null || subpackageNames.length == 0) {
+        names.add(nameOf(importingClass));
+      } else {
+        Stream.of(subpackageNames).forEach(subpackageName ->
+            names.add(nameOf(importingClass, subpackageName)));
+      }
+    });
+    return names.toArray(new String[] {});
   }
 
   static String packageIdentifierFor(Class<?> moduleClass) {
-    return moduleClass.getPackage().getName() + "..";
+    return packageIdentifierFor(moduleClass, null);
+  }
+
+  static String packageIdentifierFor(Class<?> moduleClass, @Nullable String subpackage) {
+    return moduleClass.getPackage().getName() + (subpackage != null ? ("." + subpackage) : "")
+        + "..";
+  }
+
+  static String[] both(String str, String[] arr) {
+    val strings = new ArrayList<String>();
+    strings.add(str);
+    strings.addAll(Arrays.asList(arr));
+    return strings.toArray(new String[] {});
   }
 
-  static String[] namesOf(Set<Class<?>> importingClasses) {
-    return importingClasses.stream()
-      .map(ArchitectureModuleRules::nameOf)
-      .collect(Collectors.toList())
-      .toArray(new String[]{});
+  static String[] both(String[] arr1, String[] arr2) {
+    val strings = new ArrayList<String>();
+    strings.addAll(Arrays.asList(arr1));
+    strings.addAll(Arrays.asList(arr2));
+    return strings.toArray(new String[] {});
   }
 
   static <T> Map<T, Set<T>> invert(Map<T, Set<T>> valueSetByKey) {
@@ -142,13 +259,15 @@ public class ArchitectureModuleRules {
   }
 
   static void accumulateTransitiveDependencies(
-    final Class<?> referringClass
-    , final Map<Class<?>, Set<Class<?>>> directDependenciesByReferringClass
-    , final Set<Class<?>> transitiveDependenciesOfReferringClass) {
-    val directDependencies = directDependenciesByReferringClass.getOrDefault(referringClass, Collections.emptySet());
+      final Class<?> referringClass,
+      final Map<Class<?>, Set<Class<?>>> directDependenciesByReferringClass,
+      final Set<Class<?>> transitiveDependenciesOfReferringClass) {
+    val directDependencies = directDependenciesByReferringClass
+        .getOrDefault(referringClass, Collections.emptySet());
     transitiveDependenciesOfReferringClass.addAll(directDependencies);
     directDependencies.forEach(directDependency ->
-      accumulateTransitiveDependencies(directDependency, directDependenciesByReferringClass, transitiveDependenciesOfReferringClass));
+        accumulateTransitiveDependencies(directDependency, directDependenciesByReferringClass,
+            transitiveDependenciesOfReferringClass));
   }
 
-}
+}
\ No newline at end of file
diff --git a/testing/archtestsupport/applib/src/main/java/org/apache/isis/testing/archtestsupport/applib/modulerules/Subpackage.java b/testing/archtestsupport/applib/src/main/java/org/apache/isis/testing/archtestsupport/applib/modulerules/Subpackage.java
new file mode 100644
index 0000000..0cd810f
--- /dev/null
+++ b/testing/archtestsupport/applib/src/main/java/org/apache/isis/testing/archtestsupport/applib/modulerules/Subpackage.java
@@ -0,0 +1,48 @@
+package org.apache.isis.testing.archtestsupport.applib.modulerules;
+
+import java.util.List;
+
+/**
+ * Defines the rules for which subpackages or a module to check and whether the classes in those subpackages can access
+ * the classes in other subpackages either for the same module, or for modules that are referenced
+ * ({@link org.springframework.context.annotation.Import}ed) directly or transitively.
+ *
+ * @since 2.0 {@index}
+ */
+public interface Subpackage {
+
+    /**
+     * The name of the subpackage, for example &quot;dom&quot;, &quot;api&quot;, &quot;spi&quot; or &quot;fixtures&quot;.
+     */
+    String getName();
+
+    /**
+     * A list of the (names of the) subpackages where classes in the same module as this package have access.
+     *
+     * <p>
+     *     For example, the &quot;dom&quot; subpackage can probably be referenced from the &quot;menu&quot; subpackage,
+     *     but not vice versa.
+     * </p>
+     *
+     * <p>
+     *     The special value of &quot;*&quot; is a wildcard meaning that all subpackages (in the same module) can access.
+     * </p>
+     */
+    List<String> mayBeAccessedBySubpackagesInSameModule();
+
+    /**
+     * A list of the (names of the) subpackages where classes in the packages of other referencing modules may
+     * have access.
+     *
+     * <p>
+     *     For example, in some cases the the &quot;dom&quot; subpackage may <i>not</i> be accessible from other
+     *     modules if the intention is to require all programmatic access through an &quot;api&quot; subpackage
+     *     (where the classes in <code>dom</code> implement interfaces defined in <code>api</code>).
+     * </p>
+     *
+     * <p>
+     *     The special value of &quot;*&quot; is a wildcard meaning that all subpackages (in other modules) can access.
+     * </p>
+     */
+    List<String> mayBeAccessedBySubpackagesInReferencingModules();
+}
diff --git a/testing/archtestsupport/applib/src/main/java/org/apache/isis/testing/archtestsupport/applib/modulerules/SubpackageEnum.java b/testing/archtestsupport/applib/src/main/java/org/apache/isis/testing/archtestsupport/applib/modulerules/SubpackageEnum.java
new file mode 100644
index 0000000..8e2968a
--- /dev/null
+++ b/testing/archtestsupport/applib/src/main/java/org/apache/isis/testing/archtestsupport/applib/modulerules/SubpackageEnum.java
@@ -0,0 +1,169 @@
+package org.apache.isis.testing.archtestsupport.applib.modulerules;
+
+import java.util.List;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.emptyList;
+import static java.util.Collections.singletonList;
+import lombok.RequiredArgsConstructor;
+
+/**
+ * A default implementation of {@link Subpackage} that constrains how (the classes in) subpackages across and within
+ * modules can reference either.
+ *
+ * @since 2.0 {@index}
+ */
+@RequiredArgsConstructor
+public enum SubpackageEnum implements Subpackage {
+
+    /**
+     * The <i>domain object model</i>module, containing the main business logic of the module.
+     *
+     * <p>
+     *     Typically consists of entities and view models and associated repositories and stateless services used by
+     *     those entities and view models.
+     * </p>
+     */
+    dom(
+        singletonList("*"), // wildcard means that all subpackages in this module can access 'dom'
+        singletonList("*")  // wildcard means that all subpackages in other modules can access 'dom'
+    ),
+    /**
+     * Optional, constitutes a formal API to the module.
+     *
+     * <p>
+     *     If used, then access to the <i>dom</i> module is likely to be restricted to only the subpackages of its
+     *     own &quot;local&quot; module with no access granted to subpackages of other &quot;external&quot;
+     *     referencing modules.
+     * </p>
+     */
+    api(
+        singletonList("*"), // wildcard means that all subpackages in this module can access 'api'
+        singletonList("*")  // wildcard means that all subpackages in other modules can access 'api'
+    ),
+    /**
+     * Optional, but if used then will hold view model that implement some sort of assisted business process, for
+     * example dashboards, or filtering.
+     */
+    app(
+        asList("fixtures", "integtests"), // only tests should call
+        asList("fixtures", "integtests")  // only tests should call
+    ),
+    /**
+     * Holds the menus that are visible in the UI.
+     *
+     * <p>
+     *     These should only be called by the framework, not programmatically (except for tests).
+     * </p>
+     */
+    menu(
+        asList("fixtures", "integtests"), // only tests should call
+        asList("fixtures", "integtests")  // only tests should call
+    ),
+    /**
+     * Holds mixins that contribute functionality to OTHER modules.
+     *
+     * <p>
+     *  Mixins are one of the main techniques for decoupling dependencies between modules.
+     * </p>
+     *
+     * <p>
+     *     Note that mixins to THIS module normally would just live in the <i>dom</i> subpackage, eg <i>dom.mixins</i>.
+     * </p>
+     */
+    contributions(
+        asList("subscribers",               // may subscribe to events fired by mixins
+                "fixtures", "integtests"),  // tests can also call
+            asList("subscribers",           // may subscribe to events fired by mixins
+                "fixtures", "integtests")   // tests can also call
+    ),
+    /**
+     * Holds domain services that subscribe to events fired from OTHER modules.
+     *
+     * <p>
+     *   Subscriptions is the other main technique for decoupling dependencies between modules.
+     * </p>
+     */
+    subscribers(
+        asList("fixtures", "integtests"), // only tests should call
+        asList("fixtures", "integtests")  // only tests should call
+    ),
+    /**
+     * Holds the menus that are visible as the REST endpoints.
+     *
+     * <p>
+     *     These should only be called by the framework, not programmatically (except for tests).
+     * </p>
+     */
+    restapi(
+        asList("fixtures", "integtests"), // only tests should call
+        asList("fixtures", "integtests")  // only tests should call
+    ),
+    /**
+     * Define an interface for OTHER modules to implement; this is therefore an alternative and
+     * more structured way to decoupled modules.
+     *
+     * <p>
+     * With events, the module that emits the event doesn't know much about what the action to be
+     * performed in the other module might be.  But if we use an SPI, then this module will call
+     * all implementations of the SPI at certain well-defined points; so it kind of is like a
+     * lifecycle sort of thing.
+     * </p>
+     *
+     * <p>
+     * Also, the SPI's interface could be more exotic, ie "fatter" than the simple event.
+     * </p>
+     */
+    spi(
+        asList("dom", "contributions", "subscribers"),  // callers of a module's own SPI
+        singletonList("spiimpl")                        // other modules should only implement the SPI
+    ),
+    /**
+     * These are this module's implementations of OTHER modules' SPI services.
+     */
+    spiimpl(
+        asList("fixtures", "integtests"), // only tests should call
+        asList("fixtures", "integtests")  // only tests should call
+    ),
+    /**
+     * Fixture scripts used to setup the systen when prototyping and for integ tests.
+     */
+    fixtures(
+        asList("seed", "integtests"),   // no restrictions
+        asList("seed", "integtests")    // no restrictions
+    ),
+    /**
+     * Seed scripts used to setup the systen, for example reference data).
+     */
+    seed(
+        emptyList(), // should not be called directly
+        emptyList()  // should not be called directly
+    ),
+    integtests(
+        emptyList(), // should not be called directly
+        emptyList()  // should not be called directly
+    ),
+    ;
+
+    final List<String> local;
+    final List<String> referencing;
+
+    public String getName() {
+        return name();
+    }
+
+    @Override
+    public List<String> mayBeAccessedBySubpackagesInSameModule() {
+        return local;
+    }
+
+    @Override
+    public List<String> mayBeAccessedBySubpackagesInReferencingModules() {
+        return referencing;
+    }
+
+    private static String[] asArray(List<String> list) {
+        return list != null ?
+                list.toArray(new String[] {}) : null;
+    }
+}
diff --git a/testing/archtestsupport/applib/src/main/java/org/apache/isis/testing/archtestsupport/applib/packagerules/ArchitecturePackageRules.java b/testing/archtestsupport/applib/src/main/java/org/apache/isis/testing/archtestsupport/applib/packagerules/ArchitecturePackageRules.java
deleted file mode 100644
index bdd6091..0000000
--- a/testing/archtestsupport/applib/src/main/java/org/apache/isis/testing/archtestsupport/applib/packagerules/ArchitecturePackageRules.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package org.apache.isis.testing.archtestsupport.applib.packagerules;
-
-import java.util.List;
-import java.util.stream.Collectors;
-
-import com.tngtech.archunit.lang.ArchRule;
-import com.tngtech.archunit.library.Architectures;
-
-import lombok.val;
-import lombok.experimental.UtilityClass;
-
-@UtilityClass
-public class ArchitecturePackageRules {
-
-    public static ArchRule code_dependencies_follow_module_subpackages(Class<?> moduleClass, List<Subpackage> subpackages) {
-        val layeredArchitecture = Architectures.layeredArchitecture();
-        defineAndCheckSubpackageDependencies(moduleClass, layeredArchitecture, subpackages);
-        return layeredArchitecture;
-    }
-
-    void defineAndCheckSubpackageDependencies(Class<?> moduleClass, Architectures.LayeredArchitecture layeredArchitecture, List<Subpackage> subpackages) {
-        subpackages.forEach(subpackage -> subpackage.defineLayer(layeredArchitecture, moduleClass));
-
-        subpackages.forEach(referenced -> {
-            final String[] referencingSubpackageNames =
-                    subpackages.stream().filter(subpackage -> subpackage.canReference(referenced))
-                            .map(Subpackage::getName)
-                            .collect(Collectors.toList()).toArray(new String[]{});
-            layeredArchitecture.whereLayer(referenced.getName()).mayOnlyBeAccessedByLayers(referencingSubpackageNames);
-        });
-    }
-
-}
diff --git a/testing/archtestsupport/applib/src/main/java/org/apache/isis/testing/archtestsupport/applib/packagerules/Subpackage.java b/testing/archtestsupport/applib/src/main/java/org/apache/isis/testing/archtestsupport/applib/packagerules/Subpackage.java
deleted file mode 100644
index b609683..0000000
--- a/testing/archtestsupport/applib/src/main/java/org/apache/isis/testing/archtestsupport/applib/packagerules/Subpackage.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package org.apache.isis.testing.archtestsupport.applib.packagerules;
-
-import com.tngtech.archunit.library.Architectures;
-
-import lombok.val;
-
-public interface Subpackage {
-    String getName();
-
-    SubpackageType getSubpackageType();
-
-    String packageIdentifierWithin(Class<?> moduleClass);
-
-    default void defineLayer(Architectures.LayeredArchitecture layeredArchitecture, Class<?> moduleClass) {
-        val layerDefinition = getSubpackageType().defineLayer(this, moduleClass, layeredArchitecture);
-        layerDefinition.definedBy(packageIdentifierWithin(moduleClass));
-    }
-
-    boolean canReference(Subpackage referenced);
-}
diff --git a/testing/archtestsupport/applib/src/main/java/org/apache/isis/testing/archtestsupport/applib/packagerules/SubpackageEnum.java b/testing/archtestsupport/applib/src/main/java/org/apache/isis/testing/archtestsupport/applib/packagerules/SubpackageEnum.java
deleted file mode 100644
index cd2c614..0000000
--- a/testing/archtestsupport/applib/src/main/java/org/apache/isis/testing/archtestsupport/applib/packagerules/SubpackageEnum.java
+++ /dev/null
@@ -1,69 +0,0 @@
-package org.apache.isis.testing.archtestsupport.applib.packagerules;
-
-import java.util.List;
-
-import static java.util.Arrays.asList;
-import static java.util.Collections.emptyList;
-import static java.util.Collections.singletonList;
-
-import static org.apache.isis.testing.archtestsupport.applib.packagerules.SubpackageType.MANDATORY;
-import static org.apache.isis.testing.archtestsupport.applib.packagerules.SubpackageType.OPTIONAL;
-
-import lombok.Getter;
-
-
-public enum SubpackageEnum implements Subpackage {
-
-    parent(MANDATORY, emptyList(), "fixtures") {
-        @Override
-        public String packageIdentifierWithin(Class<?> moduleClass) {
-            return moduleClass.getPackage().getName() + "..";
-        }
-    },
-
-    dom(OPTIONAL, emptyList(), "fixtures"), // allow access to personas
-
-    app(OPTIONAL, singletonList(dom)),
-
-    menu(OPTIONAL, singletonList(dom)),
-
-    contributions(OPTIONAL, singletonList(dom)),
-
-    subscriptions(OPTIONAL, singletonList(dom)),
-
-    restapi(OPTIONAL, singletonList(dom)),
-
-    spi(OPTIONAL, singletonList(dom)),
-
-    spiimpl(OPTIONAL, singletonList(dom)),
-
-    fixtures(OPTIONAL, asList(dom, menu, contributions)),
-
-    seed(OPTIONAL, asList(dom, fixtures)),
-
-    integtests(OPTIONAL, asList(dom, fixtures, app, menu, contributions, subscriptions, restapi, spi, spiimpl, seed)),
-    ;
-
-    @Getter
-    final SubpackageType subpackageType;
-    final List<Subpackage> references;
-    final List<String> softReferences;
-
-    SubpackageEnum(SubpackageType subpackageType, List<Subpackage> references, String... softReferences) {
-        this.subpackageType = subpackageType;
-        this.references = references;
-        this.softReferences = asList(softReferences);
-    }
-
-    public String getName() {
-        return name();
-    }
-
-    public String packageIdentifierWithin(Class<?> moduleClass) {
-        return moduleClass.getPackage().getName() + "." + name() + "..";
-    }
-
-    public boolean canReference(Subpackage subpackage) {
-        return references.contains(subpackage) || softReferences.contains(subpackage.getName());
-    }
-}
diff --git a/testing/archtestsupport/applib/src/main/java/org/apache/isis/testing/archtestsupport/applib/packagerules/SubpackageType.java b/testing/archtestsupport/applib/src/main/java/org/apache/isis/testing/archtestsupport/applib/packagerules/SubpackageType.java
deleted file mode 100644
index cfc7a90..0000000
--- a/testing/archtestsupport/applib/src/main/java/org/apache/isis/testing/archtestsupport/applib/packagerules/SubpackageType.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package org.apache.isis.testing.archtestsupport.applib.packagerules;
-
-import com.tngtech.archunit.library.Architectures;
-
-public enum SubpackageType {
-    MANDATORY {
-        @Override
-        Architectures.LayeredArchitecture.LayerDefinition defineLayer(
-                Subpackage subpackage, Class<?> moduleClass, Architectures.LayeredArchitecture layeredArchitecture) {
-            return layeredArchitecture.layer(subpackage.getName());
-        }
-    },
-    OPTIONAL {
-        @Override
-        Architectures.LayeredArchitecture.LayerDefinition defineLayer(
-                Subpackage subpackage, Class<?> moduleClass, Architectures.LayeredArchitecture layeredArchitecture) {
-            return layeredArchitecture.optionalLayer(subpackage.getName());
-        }
-    };
-
-    abstract Architectures.LayeredArchitecture.LayerDefinition defineLayer(Subpackage subpackage, Class<?> moduleClass, Architectures.LayeredArchitecture layeredArchitecture);
-}