[#architecture-test-support]
= Architecture Test Support

:Notice: Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at. http://www.apache.org/licenses/LICENSE-2.0 . Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR  CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Apache Isis provides a library of link:https://www.archunit.org/[ArchUnit] tests to verify the structure of your domain applications.


== Maven Configuration

include::docs:mavendeps:partial$setup-and-configure-dependencyManagement.adoc[leveloffset=+2]

In addition, add a section for the BOM of all the testing support libraries:

[source,xml,subs="attributes+"]
.pom.xml
----
<dependencyManagement>
    <dependencies>
        <dependency>
        	<groupId>org.apache.isis.testing</groupId>
	        <artifactId>isis-testing</artifactId>
            <scope>import</scope>
            <type>pom</type>
            <version>{page-isisrel}</version>
        </dependency>
    </dependencies>
</dependencyManagement>
----

[#dependencies]
=== Dependencies

In the webapp module(s) of your application, add the following dependency:

[source,xml]
.pom.xml
----
<dependencies>
    <dependency>
        <groupId>org.apache.isis.testing</groupId>
        <artifactId>isis-testing-archtestsupport-applib</artifactId>
        <scope>test</scope>     <!--.-->
    </dependency>
</dependencies>
----

<.> this assumes that the architecture tests reside in `src/test/java` (but see notes on excluding tests, in the <<architecture_test-boilerplate,next section>>).


[#architecture_test-boilerplate]
== Architecture_Test Boilerplate

It's generally sufficient to have a single `Architecture_Test` class that runs all architecture tests against all classes.
This should look something like:

[source,java]
.ArchitectureTests.java
----
import com.tngtech.archunit.junit.ArchTest;
import com.tngtech.archunit.lang.ArchRule;

import static org.apache.isis.testing.archtestsupport.applib.domainrules.ArchitectureDomainRules.*;

@AnalyzeClasses(
    packagesOf = {
        CustomerModule.class                                                // <.>
        , OrderModule.class                                                 // <1>
        , ProductModule.class                                               // <1>
    },
    importOptions = { ImportOption.DoNotIncludeTests.class }                // <.>
)
public class Architecture_Test {                                            // <.>

  @ArchTest                                                                 // <.>
  public static ArchRule every_DomainObject_must_specify_logicalTypeName =  // <.>
      every_DomainObject_must_specify_logicalTypeName();                    // <.>

  ...
}
----

<.> the modules of the application to be scanned
<.> excludes running against the tests themselves.
But see caveat, below.
<.> Recommended name.
Architecture tests run quickly, so it generally makes sense to name them as xref:testing:unittestsupport:about.adoc[unit tests] (with a `Test` suffix, to be picked up by surefire).
<.> indicates this is an architecture test.
There can be many such tests in a single file; all are expressed as `static` fields.
<.> the field name is unimportant (but must be unique, of course).
<.> the architecture test itself, imported from `ArchitectureXxxRules`

[WARNING]
.Don't architecture check your tests
====
In theory tests can be skipped from checking using the `ImportOption.DoNotIncludeTests` annotation shown above.

If this does not work for you, consider creating a separate Maven module as a peer of your webapp, and place the architecture tests in `src/main/java`.
Also remember to change the `<scope>test</scope>` to `<scope>compile</scope>`.
====


== Domain rules

Checks for rules defined against domain classes reside in the `ArchitectureDomainRules` library.

* Strongly Recommended

** logical type name
+
This ensures that persisted/serialized bookmarks of domain objects are stable even if the domain class' physical canonical name changes over time due to refactoring etc.:

*** `every_DomainObject_must_specify_logicalTypeName()`
*** `every_DomainService_must_specify_logicalTypeName()`

** unique logical type name
+
This ensures that the logical type name is also unique across ``@DomainObject``s and ``@DomainService``s.
+
Note: this rule ignores any abstract classes because strictly the framework only requires that instantiatable classes have unique logical type names.

** JAXB view models are annotated with `@DomainObject(nature=VIEW_MODEL)`
+
This ensures that the framework is able to correctly detect the view models in the metamodel:

*** `every_jaxb_view_model_must_also_be_annotated_with_DomainObject_nature_VIEWMODEL()`

** allow injected fields in view models (strongly recommended)
+
Ensures that view models safely ignore injected services.

*** `every_injected_field_of_jaxb_view_model_must_be_annotated_with_XmlTransient()`
*** `every_injected_field_of_serializable_view_model_must_be_transient()`

* Recommended

** consistency with repository finders
+
Requires that the caller of a repository finder handle the case that there may be no such object in the database:

*** `every_finder_method_in_Repository_must_return_either_Collection_or_Optional()`

** mixin naming and coding conventions
+
For teams that rely on the `Mixee_memberName` convention to find mixins of domain objects:

*** `every_Action_mixin_must_follow_naming_convention()`
*** `every_Property_mixin_must_follow_naming_convention()`
*** `every_Collection_mixin_must_follow_naming_convention()`

* Optional

** layout annotations
+
For teams that prefer to use annotations instead of layout files:

*** `every_DomainObject_must_also_be_annotated_with_DomainObjectLayout()`
*** `every_DomainService_must_also_be_annotated_with_DomainServiceLayout()`

** repository class naming/annotation conventions (optional)
+
For teams that prefer that repositories are easily identifiable by their name `XxxRepository`, (and are annotated accordingly):

*** `every_Repository_must_follow_naming_conventions()`
*** `every_class_named_Repository_must_be_annotated_correctly()`

** controller class naming/annotation conventions (optional)
+
For teams that prefer that custom controllers are easily identifiable by their name `XxxController`, (and are annotated accordingly):

*** `every_Controller_must_be_follow_naming_conventions()`
*** `every_class_named_Controller_must_be_annotated_correctly()`


None of these rules take any arguments, so simply include (as a static field) those that you wish to enforce.

== Entity Checks

Entity rule checks are provided for both xref:pjpa::about.adoc[] and xref:pjdo::about.adoc[], in `ArchitectureJpaRules` and `ArchitectureJdoRules` respectively:

* Strongly Recommended

** ensure JPA entities can support auditing and dependency injection:
+
Required in almost all cases:

*** `every_jpa_Entity_must_be_annotated_as_an_IsisEntityListener()`

** ensure entities can be referenced in JAXB view models:
+
Required to allow JAXB view models to transparently reference entities:

*** `every_jpa_Entity_must_be_annotated_with_XmlJavaAdapter_of_PersistentEntityAdapter()`
*** `every_jdo_PersistenceCapable_must_be_annotated_as_XmlJavaAdapter_PersistentEntityAdapter()`

** ensure JPA entity defines a surrogate key:
+
Enforces a very common convention for JPA entities:

*** `every_jpa_Entity_must_have_an_id_field()`

** ensure JDO entity's logicalTypeName matches its discriminator (if any)
+
Both the logical type name and the `@Discriminator` for subclasses are a unique identifier of a type; this rule says they should be the same value.

*** `every_logicalTypeName_and_jdo_discriminator_must_be_same()`


* Recommended

** encourage entities be organised into schemas:
+
for teams adopting a "(micro) service-oriented" mindset:

*** `every_jpa_Entity_must_be_annotated_as_Table_with_schema()`
*** `every_jdo_PersistenceCapable_must_have_schema()`


** ensure JPA enum fields are persisted as strings:
+
For teams that prefer to query the database with enum values persisted as strings rather than as ordinal numbers:

*** `every_enum_field_of_jpa_Entity_must_be_annotated_with_Enumerable_STRING()`


** ensure entities can be used in ``SortedSet`` parented collections:
+
For teams that like to use `SortedSet` for parented collections, and rely on the entity to render itself in a "sensible" order:

*** `every_jpa_Entity_must_implement_Comparable()`
*** `every_jdo_PersistenceCapable_must_implement_Comparable()`

** ensure entities have a unique business key:
+
For teams that insist on business keys in addition to surrogate keys

*** `every_jpa_Entity_must_be_annotated_as_Table_with_uniqueConstraints()`
*** `every_jdo_PersistenceCapable_must_be_annotated_as_Uniques_or_Unique()`

* Optional

** ensure conformance of persistence vs Isis annotations
+
For teams that like to emphasise entities vs view models:

*** `every_jpa_Entity_must_be_annotated_with_DomainObject_nature_of_ENTITY()`
*** `every_jdo_PersistenceCapable_must_be_annotated_with_DomainObject_nature_of_ENTITY`

** ensure if JDO entity has a surrogate key then its annotations are consistent:
+
For teams that like to be explicit about the use of surrogate keys:

*** `every_jdo_PersistenceCapable_with_DATASTORE_identityType_must_be_annotated_as_DataStoreIdentity()`

** enable injected fields in entities:
+
For teams that use type-safe queries (and want to ensure that services do not appear superfluously as fields in the "QXxx" classes):

*** `every_injected_field_of_jpa_Entity_must_be_annotated_with_Transient()`
*** `every_injected_field_of_jdo_PersistenceCapable_must_be_annotated_with_NotPersistent()`

** encourage optimistic locking:
+
For teams that prefer optimistic locking as a standard everywhere:

*** `every_jpa_Entity_must_have_a_version_field()`
*** `every_jdo_PersistenceCapable_must_be_annotated_with_Version()`

** encourage static factory methods for entities:
+
For teams that prefer the use of factory methods to vanilla constructors:

*** `every_jpa_Entity_must_have_protected_no_arg_constructor()`

None of these rules takes any arguments, so simply call those (from a static field) those that you wish to enforce.


== Module Rules

The module rules, residing in `ArchitectureModuleRules` class, are used to check the coarse-grained dependencies between modules footnote:[By "module" we mean a classes annotated with `@Configuration`], to ensure proper layering.
A further check checks at a more fine-grained level for the placement of classes into subpackages of those modules.

=== code_dependencies_follow_module_Imports

The `@Import` statements between modules form an acyclic dependency graph.
If there is a cycle between those modules, then Spring itself will fail to boot.

This architecture check doesn't check the the modules' graph itself, but rather ensures that the dependencies between classes that live within each module honour the direction of the module graph.

For example, if the `OrderModule` depends on (``@Import``s) the `CustomerModule`, then we allow for the `Order` entity to reference the `Customer` entity.
However, we do not allow the opposite reference.

To run the test, use:

[source,java]
----
@ArchTest
public static ArchRule code_dependencies_follow_module_Imports =
    code_dependencies_follow_module_Imports(                        // <.>
        analyzeClasses_packagesOf(Architecture_Test.class));        // <.>
}
----
<.> architecture test itself, imported from `ArchitectureModuleRules`
<.> utility that passes in for all analysis all of the module classes that are annotated on `Architecture_Test`.



==== Aside

You might well ask: if deleting a customer requires deleting its orders, then how is this done if a customer does not "know" abouts its related orders?

There are two main options:

. use a mixin.
Provide a `Customer_delete` action, but place it in the `OrderModule`.
That way the mixin code has access to delete the associated ``Order``s
+
However, this trick won't work if there are multiple referencing modules to customer, all of which need to do their work but none of which know about the other.
For example, if there is also an `AddressModule` that maintains the customer's address, and neither `OrderModule` nor `AddressModule` know about the other, then there is no one place to put the mixin that has knowledge of all of the related "stuff" of customer to be dealt with.

. use a domain event.
+
Here we keep the `Customer_delete` mixin in the `CustomerModule` (indeed, it could even be a regular action) and instead it emits a domain event:
+
[source,java]
.Customer_delete.java
----
@Action(domainEvent = Customer_delete.ActionEvent.class)
@RequiredArgsConstructor
public class Customer_delete {
    public static class ActionEvent                     // <.>
        extends ActionDomainEvent<Customer_delete>{}
    private final Customer customer;
    public void act() {
        customerRepo.delete(customer);
    }
}
----
<.> event to be fired.


=== Module Package Tests

The module package tests builds upon the module layering tests and _also_ checks that that each class is in the appropriate subpackage within each module, and that those subpackages access only the allowed subpackages of its own module or of other modules.
Different sets of subpackages can be defined for both the "local" (same) module and "foreign" (referencing) modules.

Because different teams will undoubtedly have different standards, the exact rules for subpackages are pluggable, with the `Subpackage` interface defining the information that the architecture test requires:

[source,java]
.Subpackage
----
public interface Subpackage {

    String getName();                                               // <.>
    List<String> mayBeAccessedBySubpackagesInSameModule();          // <.>
    List<String> mayBeAccessedBySubpackagesInReferencingModules();  // <.>

    default String packageIdentifier() {                            // <.>
        return "." + getName() + "..";
    }
}
----

<.> The name of the subpackage, for example "dom", "api", "spi" or "fixtures".

<.> A list of the (names of the) subpackages where classes in the same module as this package have access.
+
For example, the "dom" subpackage can probably be referenced from the "menu" subpackage, but not vice versa.
+
The special value of "*" is a wildcard meaning that all subpackages (in the same module) can access.

<.> 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 "dom" subpackage may <i>not</i> be accessible from other modules if the intention is to require all programmatic access through an "api" subpackage (where the classes in `dom` implement interfaces defined in `api`).
+
The special value of "*" is a wildcard meaning that all subpackages (in other modules) can access.

<.> allows a subpackage to more precisely control the classes that it includes.
Useful if want to declare a subpackage representing that of the module's own package (where the `XxxModule` class resides).


The `SubpackageEnum` provides an off the shelf implementation; you will probably want to copy this and adjust as necessary.

To run the test, use:

[source,java]
----
@ArchTest
public static ArchRule code_deps_follow_module_Imports_and_subpackage_rules =
      code_dependencies_follow_module_Imports_and_subpackage_rules(     // <.>
          analyzeClasses_packagesOf(ModulesArchTests.class),            // <.>
          asList(SimplifiedSubpackageEnum.values()));                   // <.>
----
<.> architecture test itself, imported from `ArchitectureModuleRules`
<.> utility that passes in for all analysis all of the module classes that are annotated on `Architecture_Test`.
<.> list of the ``Subpackage``s whose relationships are to be checked

where for example `Subpackage` is implemented using with:

[source,java]
.SimplifiedSubpackageEnum.java
----
@RequiredArgsConstructor
public enum SimplifiedSubpackageEnum implements Subpackage {

    dom(
            singletonList("*"),         // <.>
            emptyList()                 // <.>
    ),
    api(
            singletonList("*"),         // <1>
            singletonList("*")          // <.>
    ),
    spi(
            singletonList("dom"),       // <.>
            singletonList("spiimpl")    // <.>
    ),
    spiimpl(
            emptyList(),                // <.>
            emptyList()                 // <2>
    ),
    ;

    final List<String> local;
    final List<String> referencing;

    @Override
    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;
    }
}
----
<.> wildcard means that all subpackages in this module can access this module
<.> no direct access from other modules
<.> wildcard means that all subpackages in other modules can access this module
<.> callers of a module's own SPI
<.> other modules should only implement the SPI
<.> no direct access from _this_ module

