chore: Initial import of FLEX training material

This commit is contained in:
Alexander Kobjolke 2024-11-07 21:02:53 +01:00
parent c01246d4f7
commit 12235acc42
1020 changed files with 53940 additions and 0 deletions

View file

@ -0,0 +1,157 @@
<?xml version="1.0" encoding="UTF-8"?>
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>de.accso</groupId>
<artifactId>flexinale-distributed</artifactId>
<version>2024.3.0</version>
<relativePath>../pom.xml</relativePath> <!-- lookup parent from repository -->
</parent>
<artifactId>flexinale-distributed-test-architecture</artifactId>
<version>2024.3.0</version>
<name>Flexinale Distributed Test - architecture tests</name>
<description>Flexinale - FLEX case-study &quot;film festival&quot;, distributed services - architecture tests</description>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>${apache-poi.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>${apache-poi.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>de.accso</groupId>
<artifactId>flexinale-distributed-besucherportal</artifactId>
<version>2024.3.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>de.accso</groupId>
<artifactId>flexinale-distributed-common</artifactId>
<version>2024.3.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>de.accso</groupId>
<artifactId>flexinale-distributed-common</artifactId>
<version>2024.3.0</version>
<type>test-jar</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>de.accso</groupId>
<artifactId>flexinale-distributed-security</artifactId>
<version>2024.3.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>de.accso</groupId>
<artifactId>flexinale-distributed-security_api_contract</artifactId>
<version>2024.3.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>de.accso</groupId>
<artifactId>flexinale-distributed-backoffice</artifactId>
<version>2024.3.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>de.accso</groupId>
<artifactId>flexinale-distributed-backoffice_api_contract</artifactId>
<version>2024.3.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>de.accso</groupId>
<artifactId>flexinale-distributed-ticketing</artifactId>
<version>2024.3.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>de.accso</groupId>
<artifactId>flexinale-distributed-ticketing_api_contract</artifactId>
<version>2024.3.0</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>${maven-jar-plugin.version}</version>
<executions>
<execution>
<goals>
<goal>test-jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs-maven-plugin</artifactId>
<version>${spotbugs-maven-plugin.version}</version>
<configuration>
<plugins>
<plugin>
<groupId>com.h3xstream.findsecbugs</groupId>
<artifactId>findsecbugs-plugin</artifactId>
<version>${findsecbugs-maven-plugin.version}</version>
</plugin>
</plugins>
</configuration>
<dependencies>
<!-- overwrite dependency on spotbugs if you want to specify the version of spotbugs -->
<dependency>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs</artifactId>
<version>${spotbugs.version}</version>
</dependency>
</dependencies>
<executions>
<execution>
<phase>verify</phase>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View file

@ -0,0 +1,18 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Distributed - Component Dependencies Test" type="JUnit" factoryName="JUnit" folderName="ArchitectureTests Distributed">
<module name="flexinale-distributed-test-architecture" />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="architecturetests.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
<option name="PACKAGE_NAME" value="architecturetests" />
<option name="MAIN_CLASS_NAME" value="architecturetests.ComponentDependenciesTest" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="class" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>

View file

@ -0,0 +1,18 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Distributed - Domain Classes Use Correct Interfaces And Internal Structure Test" type="JUnit" factoryName="JUnit" folderName="ArchitectureTests Distributed">
<module name="flexinale-distributed-test-architecture" />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="architecturetests.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
<option name="PACKAGE_NAME" value="architecturetests" />
<option name="MAIN_CLASS_NAME" value="architecturetests.InternalStructureOfDomainClassesUsingCorrectInterfacesAndAttributeTypesTest" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="class" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>

View file

@ -0,0 +1,18 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Distributed - Entities Use Correct Interfaces And Internal Structure Test" type="JUnit" factoryName="JUnit" folderName="ArchitectureTests Distributed">
<module name="flexinale-distributed-test-architecture" />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="architecturetests.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
<option name="PACKAGE_NAME" value="architecturetests" />
<option name="MAIN_CLASS_NAME" value="architecturetests.InternalStructureOfEntityClassesUsingCorrectInterfacesAndAttributeTypesTest" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="class" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>

View file

@ -0,0 +1,18 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Distributed - Event Classes Use Correct Interfaces And Internal Structure Test" type="JUnit" factoryName="JUnit" folderName="ArchitectureTests Distributed">
<module name="flexinale-distributed-test-architecture" />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="architecturetests.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
<option name="PACKAGE_NAME" value="architecturetests" />
<option name="MAIN_CLASS_NAME" value="architecturetests.InternalStructureOfEventClassesUsingCorrectInterfacesAndAttributeTypesTest" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="class" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>

View file

@ -0,0 +1,18 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Distributed - No Dependencies To Spring Test" type="JUnit" factoryName="JUnit" folderName="ArchitectureTests Distributed">
<module name="flexinale-distributed-test-architecture" />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="architecturetests.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
<option name="PACKAGE_NAME" value="architecturetests" />
<option name="MAIN_CLASS_NAME" value="architecturetests.NoDependenciesToSpringTest" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="class" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>

View file

@ -0,0 +1,18 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Distributed - Onion Dependencies Test" type="JUnit" factoryName="JUnit" folderName="ArchitectureTests Distributed">
<module name="flexinale-distributed-test-architecture" />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="architecturetests.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
<option name="PACKAGE_NAME" value="architecturetests" />
<option name="MAIN_CLASS_NAME" value="architecturetests.OnionDependenciesTest" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="class" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>

View file

@ -0,0 +1,18 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Distributed - TO Classes Use Correct Interfaces And Internal Structure Test" type="JUnit" factoryName="JUnit" folderName="ArchitectureTests Distributed">
<module name="flexinale-distributed-test-architecture" />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="architecturetests.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
<option name="PACKAGE_NAME" value="architecturetests" />
<option name="MAIN_CLASS_NAME" value="architecturetests.InternalStructureOfTOClassesUsingCorrectInterfacesAndAttributeTypesTest" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="class" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>

View file

@ -0,0 +1,339 @@
package architecturetests;
import com.tngtech.archunit.base.DescribedPredicate;
import com.tngtech.archunit.core.domain.JavaAnnotation;
import com.tngtech.archunit.core.domain.JavaClass;
import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.domain.JavaField;
import com.tngtech.archunit.core.domain.JavaModifier;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.core.importer.ImportOption;
import com.tngtech.archunit.lang.ArchCondition;
import com.tngtech.archunit.lang.ConditionEvents;
import com.tngtech.archunit.lang.SimpleConditionEvent;
import de.accso.flexinale.FlexinaleDistributedApplicationBesucherportal;
import de.accso.flexinale.FlexinaleDistributedApplicationBackoffice;
import de.accso.flexinale.FlexinaleDistributedApplicationTicketing;
import de.accso.flexinale.common.shared_kernel.DoNotCheckInArchitectureTests;
import org.springframework.boot.test.context.SpringBootTest;
import java.lang.annotation.Annotation;
import java.util.List;
import java.util.Optional;
import java.util.Set;
@SuppressWarnings("unused")
public final class ArchUnitHelper {
static final String PACKAGE_PREFIX = "de.accso.flexinale";
static final String PACKAGE_PREFIX_TOCHECK = "de.accso.flexinale";
static final JavaClasses allJavaClassesToCheck =
new ClassFileImporter()
.withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)
.importPackages(PACKAGE_PREFIX_TOCHECK);
static DescribedPredicate<JavaClass> isFlexinaleClass =
new DescribedPredicate<>("any Flexinale class") {
@Override
public boolean test(JavaClass clazz) {
return clazz.getPackageName().startsWith(PACKAGE_PREFIX);
}
};
static final DescribedPredicate<JavaClass> isFlexinaleTestClassAnnotatedWithDoNotCheckAnnotation =
new DescribedPredicate<>("any Flexinale class annotated with DoNotCheckInArchitectureTests") {
@Override
public boolean test(JavaClass clazz) {
return clazz.isAnnotatedWith(DoNotCheckInArchitectureTests.class);
}
};
static final DescribedPredicate<JavaClass> isAnyClass =
new DescribedPredicate<>("any class (to be ignored always)") {
@Override
public boolean test(JavaClass clazz) {
return true;
}
};
static final DescribedPredicate<JavaClass> isSpringBootTestClass =
new DescribedPredicate<>("a Flexinale Spring Boot test class") {
@Override
public boolean test(JavaClass clazz) {
return clazz.isAnnotatedWith(SpringBootTest.class);
}
};
static final DescribedPredicate<JavaClass> isSpringBootApplicationMainClass =
new DescribedPredicate<>("the Flexinale Spring Boot application main class") {
@Override
public boolean test(JavaClass clazz) {
return
clazz.getFullName().equals(FlexinaleDistributedApplicationBesucherportal.class.getName())
|| clazz.getFullName().equals(FlexinaleDistributedApplicationBackoffice.class.getName())
|| clazz.getFullName().equals(FlexinaleDistributedApplicationTicketing.class.getName())
;
}
};
static final DescribedPredicate<JavaClass> isSpringClass =
new DescribedPredicate<>("a Spring class") {
@Override
public boolean test(JavaClass clazz) {
return clazz.getPackageName().contains("spring");
}
};
@SuppressWarnings("rawtypes")
static final DescribedPredicate<JavaAnnotation> isSpringAnnotation =
new DescribedPredicate<>("a Spring annotation") {
@Override
public boolean test(final JavaAnnotation anno) {
return anno.getType().getName().contains("spring");
}
};
@SuppressWarnings("rawtypes")
static final DescribedPredicate<JavaAnnotation> isEntityAnnotation =
new DescribedPredicate<>("a Entity annotation") {
@Override
public boolean test(final JavaAnnotation anno) {
return anno.getType().getName().contains("jakarta.persistence.Entity");
}
};
static ArchCondition<JavaClass> hasFieldOfType(String fieldName, Class<?> fieldType) {
return hasFieldOfType(fieldName, fieldType, true);
}
static ArchCondition<JavaClass> hasFieldOfType(String fieldName, Class<?> fieldType,
boolean includeDerivedFieldsFromSuperClasses) {
return new ArchCondition<>("a field type check") {
@Override
public void check(JavaClass clazz, ConditionEvents events) {
// check all fields (or all field including the derived onces, from superclass / interfaces)
Optional<JavaField> optionalField =
(includeDerivedFieldsFromSuperClasses ? clazz.getAllFields() : clazz.getFields())
.stream()
// and retrieve the field with the given name to check
.filter(field -> fieldName.equals(field.getName()))
.findFirst();
// if there is not such a field with the given name ...
if (optionalField.isEmpty()) {
// ... then add message to ArchConditions result
String message = String.format("Class %s does not have a field %s", clazz.getName(), fieldName);
events.add(SimpleConditionEvent.violated(clazz, message));
}
else
// if there is a field with the given name, but it is not of the given type ...
if (!(optionalField.get().getRawType().isEquivalentTo(fieldType))) {
// ... then add message to ArchConditions result
String message = String.format("Class %s does not have a field %s of type %s",
clazz.getName(), fieldName, fieldType.getName());
events.add(SimpleConditionEvent.violated(clazz, message));
}
}
};
}
static ArchCondition<JavaClass> hasFieldsWithSuffixOfType(String fieldNameSuffix, Class<?> fieldType) {
return hasFieldsWithSuffixOfType(fieldNameSuffix, fieldType, true);
}
static ArchCondition<JavaClass> hasFieldsWithSuffixOfType(String fieldNameSuffix, Class<?> fieldType,
boolean includeDerivedFieldsFromSuperClasses) {
return new ArchCondition<>("a field type check") {
@Override
public void check(JavaClass clazz, ConditionEvents events) {
// check all fields (or all field including the derived onces, from superclass / interfaces)
List<JavaField> fields =
(includeDerivedFieldsFromSuperClasses ? clazz.getAllFields() : clazz.getFields())
.stream()
// and retrieve the field with the given name to check
.filter(field -> field.getName().endsWith(fieldNameSuffix))
.toList();
// if there are fields with the given name suffix, but it is not of the given type ...
for (JavaField field: fields) {
if (!field.getRawType().isEquivalentTo(fieldType)) {
// ... then add message to ArchConditions result
String message = String.format("Class %s has a field %s but it is not of type %s",
clazz.getName(), field.getName(), fieldType.getName());
events.add(SimpleConditionEvent.violated(clazz, message));
}
}
}
};
}
static ArchCondition<JavaClass> hasFieldPrivateStatic(String fieldName) {
return hasFieldPrivateStatic(fieldName, true);
}
static ArchCondition<JavaClass> hasFieldPrivateStatic(String fieldName,
boolean includeDerivedFieldsFromSuperClasses) {
return hasFieldModifiers(fieldName, includeDerivedFieldsFromSuperClasses, Set.of(JavaModifier.PRIVATE, JavaModifier.STATIC));
}
static ArchCondition<JavaClass> hasFieldPublicFinal(String fieldName) {
return hasFieldPublicFinal(fieldName, true);
}
static ArchCondition<JavaClass> hasFieldPublicFinal(String fieldName,
boolean includeDerivedFieldsFromSuperClasses) {
return hasFieldModifiers(fieldName, includeDerivedFieldsFromSuperClasses, Set.of(JavaModifier.PUBLIC, JavaModifier.FINAL));
}
static ArchCondition<JavaClass> hasFieldPrivateFinal(String fieldName) {
return hasFieldPrivateFinal(fieldName, true);
}
static ArchCondition<JavaClass> hasFieldPrivateFinal(String fieldName,
boolean includeDerivedFieldsFromSuperClasses) {
return hasFieldModifiers(fieldName, includeDerivedFieldsFromSuperClasses, Set.of(JavaModifier.PRIVATE, JavaModifier.FINAL));
}
private static ArchCondition<JavaClass> hasFieldModifiers(String fieldName,
boolean includeDerivedFieldsFromSuperClasses,
Set<JavaModifier> modifiers) {
return new ArchCondition<>("a field check for public final") {
@Override
public void check(JavaClass clazz, ConditionEvents events) {
// check all fields (or all field including the derived onces, from superclass / interfaces)
Optional<JavaField> optionalField =
(includeDerivedFieldsFromSuperClasses ? clazz.getAllFields() : clazz.getFields())
.stream()
// and retrieve the field with the given name to check
.filter(field -> fieldName.equals(field.getName()))
.findFirst();
// if there is not such a field with the given name ...
if (optionalField.isEmpty()) {
// ... then add message to ArchConditions result
String message = String.format("Class %s does not have a field %s", clazz.getName(), fieldName);
events.add(SimpleConditionEvent.violated(clazz, message));
}
else
// if there is a field with the given name, but it does not have the given modifiers
if (!(optionalField.get().getModifiers().containsAll(modifiers))) {
// ... then add message to ArchConditions result
String message = String.format("Class %s does not have a final field %s with modifiers %s",
clazz.getName(), fieldName, modifiers);
events.add(SimpleConditionEvent.violated(clazz, message));
}
}
};
}
static ArchCondition<JavaClass> hasFieldAnnotatedWithType(String fieldName, Class<? extends Annotation> annotationType) {
return hasFieldAnnotatedWithType(fieldName, annotationType, true);
}
static ArchCondition<JavaClass> hasFieldAnnotatedWithType(String fieldName, Class<? extends Annotation> annotationType,
boolean includeDerivedFieldsFromSuperClasses) {
return new ArchCondition<>("a field annotation check") {
@Override
public void check(JavaClass clazz, ConditionEvents events) {
// check all fields (or all field including the derived onces, from superclass / interfaces)
Optional<JavaField> optionalField =
(includeDerivedFieldsFromSuperClasses ? clazz.getAllFields() : clazz.getFields())
.stream()
// and retrieve the field with the given name to check
.filter(field -> fieldName.equals(field.getName()))
.findFirst();
// if there is not such a field with the given name ...
if (optionalField.isEmpty()) {
// ... then add message to ArchConditions result
String message = String.format("Class %s does not have a field %s", clazz.getName(), fieldName);
events.add(SimpleConditionEvent.violated(clazz, message));
}
else
// if there is a field with the given name, but it is not annotated with the annotation type ...
if (!(optionalField.get().isAnnotatedWith(annotationType))) {
// ... then add message to ArchConditions result
String message = String.format("Class %s does not have a field %s annotated with type %s",
clazz.getName(), fieldName, annotationType.getName());
events.add(SimpleConditionEvent.violated(clazz, message));
}
}
};
}
static ArchCondition<JavaClass> hasNoFieldsOfAnyType(Set<Class<?>> disallowedFieldTypes, Set<String> fieldsToBeFilteredOut) {
return hasNoFieldsOfAnyType(disallowedFieldTypes, fieldsToBeFilteredOut, true);
}
static ArchCondition<JavaClass> hasNoFieldsOfAnyType(Set<Class<?>> disallowedFieldTypes, Set<String> fieldsToBeFilteredOut,
boolean includeDerivedFieldsFromSuperClasses) {
return new ArchCondition<>("a field type check") {
@Override
public void check(JavaClass clazz, ConditionEvents events) {
// check all fields (or all field including the derived onces, from superclass / interfaces)
Set<JavaField> fields = includeDerivedFieldsFromSuperClasses ? clazz.getAllFields() : clazz.getFields();
// check all retrieved fields of clazz
for (JavaField field: fields) {
// but do not check the fields explicitely filtered out or annotated
if (! ( (field.isAnnotatedWith(DoNotCheckInArchitectureTests.class))
|| (fieldsToBeFilteredOut.contains(field.getName()))))
{
// for any given disallowed field type
for (Class<?> fieldType : disallowedFieldTypes) {
// check if the field from the clazz is of this disallowed type ...
if (field.getRawType().isEquivalentTo(fieldType)) {
// ... and if not, add message to ArchConditions result
String message = String.format("Class %s has a field %s of type %s",
clazz.getName(), field.getName(), fieldType.getSimpleName());
events.add(SimpleConditionEvent.violated(clazz, message));
}
}
}
}
}
};
}
static ArchCondition<JavaClass> hasOnlyFieldsImplementing(Set<Class<?>> interfaceTypes,
Set<String> fieldsToBeFilteredOut, String fieldSuffixToBeFilteredOut) {
return hasOnlyFieldsImplementing(interfaceTypes, fieldsToBeFilteredOut, fieldSuffixToBeFilteredOut, true);
}
static ArchCondition<JavaClass> hasOnlyFieldsImplementing(Set<Class<?>> interfaceTypes,
Set<String> fieldsToBeFilteredOut, String fieldSuffixToBeFilteredOut,
boolean includeDerivedFieldsFromSuperClasses) {
return new ArchCondition<>("a field implementing interfaces check") {
@Override
public void check(JavaClass clazz, ConditionEvents events) {
// check all fields (or all field including the derived onces, from superclass / interfaces)
Set<JavaField> fields = includeDerivedFieldsFromSuperClasses ? clazz.getAllFields() : clazz.getFields();
// check all retrieved fields of clazz
for (JavaField field: fields) {
// but do not check the fields explicitely filtered out or annotated
if (! ( (field.isAnnotatedWith(DoNotCheckInArchitectureTests.class))
|| (fieldsToBeFilteredOut.contains(field.getName()))
|| (field.getName().endsWith(fieldSuffixToBeFilteredOut))))
{
// for any given interface type
for (Class<?> interfaceType : interfaceTypes) {
// check if the field from the clazz does implement the interface type ...
if (!field.getRawType().getAllRawInterfaces()
.stream()
.map(interfaceClazz -> interfaceClazz.reflect())
.toList()
.contains(interfaceType))
{
// ... and if not, add message to ArchConditions result
String message = String.format("Class %s has a field %s not implementing type %s but has type %s",
clazz.getName(), field.getName(), interfaceType.getSimpleName(), field.getRawType().reflect().getSimpleName());
events.add(SimpleConditionEvent.violated(clazz, message));
}
}
}
}
}
};
}
}

View file

@ -0,0 +1,290 @@
package architecturetests;
import com.tngtech.archunit.core.domain.JavaClass;
import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.domain.PackageMatcher;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.library.dependencies.SliceAssignment;
import com.tngtech.archunit.library.dependencies.SliceIdentifier;
import com.tngtech.archunit.library.dependencies.SlicesRuleDefinition;
import de.accso.flexinale.common.shared_kernel.DeveloperMistakeException;
import de.accso.flexinale.common.shared_kernel.DoNotCheckInArchitectureTests;
import org.junit.jupiter.api.Test;
import java.util.*;
import java.util.stream.Collectors;
import static architecturetests.ArchUnitHelper.PACKAGE_PREFIX;
import static architecturetests.ArchUnitHelper.allJavaClassesToCheck;
import static com.tngtech.archunit.core.domain.PackageMatcher.TO_GROUPS;
import static java.util.Collections.emptyList;
import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;
import static org.junit.jupiter.api.Assertions.fail;
public class ComponentDependenciesTest {
@Test
void test_component_dependencies_are_free_of_cycles() {
PackageMatchingSliceIdentifierIgnoringDependenciesToApiContract sliceAssignment =
new PackageMatchingSliceIdentifierIgnoringDependenciesToApiContract(PACKAGE_PREFIX + ".(*)..");
// arrange, act, assert
SlicesRuleDefinition.slices()
.assignedFrom(sliceAssignment)
.should()
.beFreeOfCycles()
.because("no cycles between components allowed")
.check(allJavaClassesToCheck);
}
// ------------------------------------------------------------------------------------------------------------
private record IncorrectDependencyFromTo(String fromClazzName, String toClazzName) {
@Override
public String toString() {
return "%s -> %s".formatted(fromClazzName, toClazzName);
}
}
@Test
void test_besucherportal_has_only_allowed_dependencies_to_other_flexinale_components() {
// arrange
FlexinaleComponent cut = FlexinaleComponent.besucherportal;
// act
Set<IncorrectDependencyFromTo> incorrectDependencies =
getIncorrectDependenciesFromComponentClazzesOtherThanAllowed(cut,
FlexinaleComponent.security_api_contract,
FlexinaleComponent.backoffice_api_contract,
FlexinaleComponent.ticketing_api_contract,
FlexinaleComponent.common);
// assert
assertThat(incorrectDependencies)
.withFailMessage("incorrect dependencies found for component %s: %s".formatted(cut.name(), incorrectDependencies))
.isEmpty();
}
@Test
void test_besucherportal_api_contract_has_only_allowed_dependencies_to_other_flexinale_components() {
// arrange
FlexinaleComponent cut = FlexinaleComponent.besucherportal_api_contract;
// act
Set<IncorrectDependencyFromTo> incorrectDependencies =
getIncorrectDependenciesFromComponentClazzesOtherThanAllowed(cut, FlexinaleComponent.common);
// assert
assertThat(incorrectDependencies)
.withFailMessage("incorrect dependencies found for component %s: %s".formatted(cut.name(), incorrectDependencies))
.isEmpty();
}
@Test
void test_ticketing_has_only_allowed_dependencies_to_other_flexinale_components() {
// arrange
FlexinaleComponent cut = FlexinaleComponent.ticketing;
// act
Set<IncorrectDependencyFromTo> incorrectDependencies =
getIncorrectDependenciesFromComponentClazzesOtherThanAllowed(cut,
FlexinaleComponent.besucherportal_api_contract,
FlexinaleComponent.security_api_contract,
FlexinaleComponent.backoffice_api_contract,
FlexinaleComponent.common);
// assert
assertThat(incorrectDependencies)
.withFailMessage("incorrect dependencies found for component %s: %s".formatted(cut.name(), incorrectDependencies))
.isEmpty();
}
@Test
void test_ticketing_api_contract_has_only_allowed_dependencies_to_other_flexinale_components() {
// arrange
FlexinaleComponent cut = FlexinaleComponent.ticketing_api_contract;
// act
Set<IncorrectDependencyFromTo> incorrectDependencies =
getIncorrectDependenciesFromComponentClazzesOtherThanAllowed(cut, FlexinaleComponent.common);
// assert
assertThat(incorrectDependencies)
.withFailMessage("incorrect dependencies found for component %s: %s".formatted(cut.name(), incorrectDependencies))
.isEmpty();
}
@Test
void test_backoffice_has_only_allowed_dependencies_to_other_flexinale_components() {
// arrange
FlexinaleComponent cut = FlexinaleComponent.backoffice;
// act
Set<IncorrectDependencyFromTo> incorrectDependencies =
getIncorrectDependenciesFromComponentClazzesOtherThanAllowed(cut,
FlexinaleComponent.security_api_contract,
FlexinaleComponent.backoffice_api_contract,
FlexinaleComponent.ticketing_api_contract,
FlexinaleComponent.common);
// assert
assertThat(incorrectDependencies)
.withFailMessage("incorrect dependencies found for component %s: %s".formatted(cut.name(), incorrectDependencies))
.isEmpty();
}
@Test
void test_security_has_only_allowed_dependencies_to_other_flexinale_components() {
// arrange
FlexinaleComponent cut = FlexinaleComponent.security;
// act
Set<IncorrectDependencyFromTo> incorrectDependencies =
getIncorrectDependenciesFromComponentClazzesOtherThanAllowed(cut,
FlexinaleComponent.security_api_contract,
FlexinaleComponent.common);
// assert
assertThat(incorrectDependencies)
.withFailMessage("incorrect dependencies found for component %s: %s".formatted(cut.name(), incorrectDependencies))
.isEmpty();
}
@Test
void test_backoffice_api_contract_has_only_allowed_dependencies_to_other_flexinale_components() {
// arrange
FlexinaleComponent cut = FlexinaleComponent.backoffice_api_contract;
// act
Set<IncorrectDependencyFromTo> incorrectDependencies =
getIncorrectDependenciesFromComponentClazzesOtherThanAllowed(cut, FlexinaleComponent.common);
// assert
assertThat(incorrectDependencies)
.withFailMessage("incorrect dependencies found for component %s: %s".formatted(cut.name(), incorrectDependencies))
.isEmpty();
}
@Test
void test_common_has_only_allowed_dependencies_to_self() {
// arrange
FlexinaleComponent cut = FlexinaleComponent.common;
// act
Set<IncorrectDependencyFromTo> incorrectDependencies =
getIncorrectDependenciesFromComponentClazzesOtherThanSelf(cut);
// assert
if (!incorrectDependencies.isEmpty()) {
fail("incorrect dependencies found for component %s: %s".formatted(cut.name(), incorrectDependencies));
}
}
@Test
void test_security_api_contract_has_only_allowed_dependencies_to_other_flexinale_components() {
// arrange
FlexinaleComponent cut = FlexinaleComponent.security_api_contract;
// act
Set<IncorrectDependencyFromTo> incorrectDependencies =
getIncorrectDependenciesFromComponentClazzesOtherThanAllowed(cut, FlexinaleComponent.common);
// assert
if (!incorrectDependencies.isEmpty()) {
fail("incorrect dependencies found for component %s: %s".formatted(cut.name(), incorrectDependencies));
}
}
private Set<IncorrectDependencyFromTo>
getIncorrectDependenciesFromComponentClazzesOtherThanSelf(FlexinaleComponent fromComponent)
{
return getIncorrectDependenciesFromComponentClazzesOtherThanAllowed(fromComponent);
}
private Set<IncorrectDependencyFromTo>
getIncorrectDependenciesFromComponentClazzesOtherThanAllowed(FlexinaleComponent fromComponent,
FlexinaleComponent... toAllowedComponents)
{
Set<String> toAllowedPackages = Arrays.stream(toAllowedComponents)
.map(component -> component.pkg)
.collect(Collectors.toSet());
// add self as allowed dependency
toAllowedPackages.add(fromComponent.pkg);
Set<IncorrectDependencyFromTo> allIncorrectDependenciesForComponent = new HashSet<>();
// check all clazzes in the fromComponent
JavaClasses fromComponentClazzes = new ClassFileImporter().importPackages(fromComponent.pkg);
fromComponentClazzes.forEach(clazz -> {
if (!clazz.getFullName().startsWith(fromComponent.pkg))
throw new DeveloperMistakeException("wrong clazz filter implemented");
// for any clazz of the fromComponent: check its outgoing dependencies and keep the wrong ones
HashSet<IncorrectDependencyFromTo> incorrectDependenciesForClazz =
clazz.getDirectDependenciesFromSelf().stream()
.filter(dependency -> {
JavaClass targetClazz = dependency.getTargetClass();
// filter out all clazzes outside of our component scope (like dependencies to java... or org... etc)
if (! targetClazz.getPackageName().startsWith(PACKAGE_PREFIX))
return false;
// filter out DoNotCheckInArchitectureTests
if (targetClazz.getFullName().equals(DoNotCheckInArchitectureTests.class.getSimpleName()))
return false;
// filter out annotation usages of DoNotCheckInArchitectureTests
if (dependency.getOriginClass().isAnnotatedWith(DoNotCheckInArchitectureTests.class))
return false;
// filter out all allowed dependencies
for (String toAllowedPackage : toAllowedPackages) {
if (targetClazz.getPackageName().startsWith(toAllowedPackage)) {
return false;
}
}
// finally now keep all, where the dependency target is not explicitely allowed
return true;
})
.map(it -> new IncorrectDependencyFromTo(it.getOriginClass().getFullName(),
it.getTargetClass().getFullName()))
.collect(Collectors.toCollection(HashSet::new)); // do not use .toSet() as this results in an UnsupportedOperation exception
allIncorrectDependenciesForComponent.addAll(incorrectDependenciesForClazz);
});
return allIncorrectDependenciesForComponent;
}
}
// copied from ArchUnit's internal class PackageMatchingSliceIdentifier
class PackageMatchingSliceIdentifierIgnoringDependenciesToApiContract implements SliceAssignment {
private final String packageIdentifier;
PackageMatchingSliceIdentifierIgnoringDependenciesToApiContract(String packageIdentifier) {
this.packageIdentifier = packageIdentifier;
}
@Override
public SliceIdentifier getIdentifierOf(JavaClass javaClass) {
// ignore dependencies to ..api_contract..
if (javaClass.getPackageName().contains("api_contract")) {
return SliceIdentifier.ignore();
}
PackageMatcher matcher = PackageMatcher.of(packageIdentifier);
Optional<List<String>> result = matcher.match(javaClass.getPackageName()).map(TO_GROUPS);
List<String> parts = result.orElse(emptyList());
return parts.isEmpty() ? SliceIdentifier.ignore() : SliceIdentifier.of(parts);
}
@Override
public String getDescription() {
return slicesMatchingDescription(packageIdentifier);
}
private static String slicesMatchingDescription(String packageIdentifier) {
return "'" + packageIdentifier + "'";
}
}

View file

@ -0,0 +1,23 @@
package architecturetests;
import static architecturetests.ArchUnitHelper.PACKAGE_PREFIX;
enum FlexinaleComponent {
besucherportal ("besucherportal", PACKAGE_PREFIX + ".besucherportal"),
besucherportal_api_contract("besucherportal_api_contract", PACKAGE_PREFIX + ".besucherportal.api_contract"),
security ("security", PACKAGE_PREFIX + ".security"),
security_api_contract ("security_api_contract", PACKAGE_PREFIX + ".security.api_contract"),
backoffice ("backoffice", PACKAGE_PREFIX + ".backoffice"),
backoffice_api_contract ("backoffice_api_contract", PACKAGE_PREFIX + ".backoffice.api_contract"),
ticketing ("ticketing", PACKAGE_PREFIX + ".ticketing"),
ticketing_api_contract ("ticketing_api_contract", PACKAGE_PREFIX + ".ticketing.api_contract"),
common ("common", PACKAGE_PREFIX + ".common");
final String name;
final String pkg;
FlexinaleComponent(final String name, String pkg) {
this.name = name;
this.pkg = pkg;
}
}

View file

@ -0,0 +1,143 @@
package architecturetests;
import com.tngtech.archunit.lang.ArchRule;
import com.tngtech.archunit.lang.EvaluationResult;
import com.tngtech.archunit.lang.syntax.ArchRuleDefinition;
import com.tngtech.archunit.lang.syntax.elements.GivenClassesConjunction;
import de.accso.flexinale.common.shared_kernel.*;
import org.junit.jupiter.api.Test;
import java.util.Set;
import static architecturetests.ArchUnitHelper.*;
import static org.assertj.core.api.Assertions.assertThat;
public class InternalStructureOfDomainClassesUsingCorrectInterfacesAndAttributeTypesTest {
static final String PACKAGE_BACKOFFICE_DOMAIN_MODEL = ArchUnitHelper.PACKAGE_PREFIX + ".backoffice.domain.model..";
static final String PACKAGE_TICKETING_DOMAIN_MODEL = ArchUnitHelper.PACKAGE_PREFIX + ".ticketing.domain.model..";
static final GivenClassesConjunction domainClasses = ArchRuleDefinition.classes()
.that()
.resideInAnyPackage(PACKAGE_BACKOFFICE_DOMAIN_MODEL,
PACKAGE_TICKETING_DOMAIN_MODEL)
.and()
.areNotEnums()
.and()
.areNotInnerClasses()
.and()
.areNotNestedClasses()
.and()
.areNotAnnotatedWith(DoNotCheckInArchitectureTests.class);
@Test
void test_DomainClassesAreImplementingIdentifiableAndVersionableAndEqualsByContent() {
// arrange
ArchRule archRule = domainClasses
.should()
.implement(Identifiable.class)
.andShould()
.implement(Versionable.class)
.andShould()
.implement(EqualsByContent.class);
// act
EvaluationResult evaluationResult = archRule.evaluate(ArchUnitHelper.allJavaClassesToCheck);
// assert
assertThat(evaluationResult.getFailureReport().getDetails()).isEmpty();
}
@Test
void test_DomainClassesHaveIdFieldUsingStrongType() {
// arrange
String idField = "id";
ArchRule archRule = domainClasses
.should(hasFieldOfType(idField, Identifiable.Id.class));
// act
EvaluationResult evaluationResult = archRule.evaluate(ArchUnitHelper.allJavaClassesToCheck);
// assert
assertThat(evaluationResult.getFailureReport().getDetails()).isEmpty();
}
@Test
void test_DomainClassesHaveVersionFieldUsingStrongType() {
// arrange
String versionField = "version";
ArchRule archRule = domainClasses
.should(hasFieldOfType(versionField, Versionable.Version.class));
// act
EvaluationResult evaluationResult = archRule.evaluate(ArchUnitHelper.allJavaClassesToCheck);
// assert
assertThat(evaluationResult.getFailureReport().getDetails()).isEmpty();
}
@Test
void test_DomainClassesHaveIdFieldWhichIsPublicFinal() {
// arrange
String idField = "id";
ArchRule archRule = domainClasses
.should(hasFieldPublicFinal(idField));
// act
EvaluationResult evaluationResult = archRule.evaluate(ArchUnitHelper.allJavaClassesToCheck);
// assert
assertThat(evaluationResult.getFailureReport().getDetails()).isEmpty();
}
@Test
void test_DomainClassesHaveVersionFieldWhichIsPublicFinal() {
// arrange
String versionField = "version";
ArchRule archRule = domainClasses
.should(hasFieldPublicFinal(versionField));
// act
EvaluationResult evaluationResult = archRule.evaluate(ArchUnitHelper.allJavaClassesToCheck);
// assert
assertThat(evaluationResult.getFailureReport().getDetails()).isEmpty();
}
@Test
void test_DomainClassesHaveOnlyFieldsImplementingRawWrapper() {
// arrange
Set<Class<?>> interfaceTypes = Set.of(RawWrapper.class);
Set<String> fieldsToBeFilteredOut = Set.of();
String fieldSuffixToBeFilteredOut = "Id";
ArchRule archRule = domainClasses
.should(hasOnlyFieldsImplementing(interfaceTypes, fieldsToBeFilteredOut, fieldSuffixToBeFilteredOut));
// act
EvaluationResult evaluationResult = archRule.evaluate(ArchUnitHelper.allJavaClassesToCheck);
// assert
assertThat(evaluationResult.getFailureReport().getDetails()).isEmpty();
}
@Test
void test_DomainClassesHaveExternalIdFieldsUsingStrongType() {
// arrange
String idFieldSuffix = "Id";
ArchRule archRule = domainClasses
.should(hasFieldsWithSuffixOfType(idFieldSuffix, Identifiable.Id.class));
// act
EvaluationResult evaluationResult = archRule.evaluate(ArchUnitHelper.allJavaClassesToCheck);
// assert
assertThat(evaluationResult.getFailureReport().getDetails()).isEmpty();
}
}

View file

@ -0,0 +1,108 @@
package architecturetests;
import com.tngtech.archunit.lang.ArchRule;
import com.tngtech.archunit.lang.EvaluationResult;
import com.tngtech.archunit.lang.syntax.ArchRuleDefinition;
import com.tngtech.archunit.lang.syntax.elements.GivenClassesConjunction;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import de.accso.flexinale.common.shared_kernel.Versionable;
import org.junit.jupiter.api.Test;
import java.io.Serializable;
import static architecturetests.ArchUnitHelper.hasFieldAnnotatedWithType;
import static architecturetests.ArchUnitHelper.hasFieldOfType;
import static org.assertj.core.api.Assertions.assertThat;
public class InternalStructureOfEntityClassesUsingCorrectInterfacesAndAttributeTypesTest {
static final GivenClassesConjunction entities = ArchRuleDefinition.classes()
.that()
.areAnnotatedWith(ArchUnitHelper.isEntityAnnotation);
@Test
void test_NoEntitiesAreOutsidePersistencePackages() {
// arrange
final String PACKAGE_BACKOFFICE_INFRASTRUCTURE_PERSISTENCE = ArchUnitHelper.PACKAGE_PREFIX + ".backoffice.infrastructure.persistence..";
final String PACKAGE_SECURITY_INFRASTRUCTURE_PERSISTENCE = ArchUnitHelper.PACKAGE_PREFIX + ".security.infrastructure.persistence..";
final String PACKAGE_TICKETING_INFRASTRUCTURE_PERSISTENCE = ArchUnitHelper.PACKAGE_PREFIX + ".ticketing.infrastructure.persistence..";
ArchRule archRule = ArchRuleDefinition.noClasses()
.that()
.resideOutsideOfPackages(PACKAGE_BACKOFFICE_INFRASTRUCTURE_PERSISTENCE,
PACKAGE_SECURITY_INFRASTRUCTURE_PERSISTENCE,
PACKAGE_TICKETING_INFRASTRUCTURE_PERSISTENCE)
.should()
.beAnnotatedWith(ArchUnitHelper.isEntityAnnotation);
// act
EvaluationResult evaluationResult = archRule.evaluate(ArchUnitHelper.allJavaClassesToCheck);
// assert
assertThat(evaluationResult.getFailureReport().getDetails()).isEmpty();
}
@Test
void test_EntitiesAreImplementingIdentifiableAndSerializableAndVersionable() {
// arrange
ArchRule archRule = entities
.should()
.implement(Identifiable.class)
.andShould()
.implement(Serializable.class)
.andShould()
.implement(Versionable.class);
// act
EvaluationResult evaluationResult = archRule.evaluate(ArchUnitHelper.allJavaClassesToCheck);
// assert
assertThat(evaluationResult.getFailureReport().getDetails()).isEmpty();
}
@Test
void test_EntitiesHaveIdFieldUsingRawType() {
// arrange
String idField = "id";
ArchRule archRule = entities
.should(hasFieldOfType(idField, String.class)) // Entities should use String for Id (not the Id class)
.andShould(hasFieldAnnotatedWithType(idField, jakarta.persistence.Id.class));
// act
EvaluationResult evaluationResult = archRule.evaluate(ArchUnitHelper.allJavaClassesToCheck);
// assert
assertThat(evaluationResult.getFailureReport().getDetails()).isEmpty();
}
@Test
void test_EntitiesHaveVersionFieldUsingRawType() {
// arrange
String idField = "version";
ArchRule archRule = entities
.should(hasFieldOfType(idField, Integer.class)) // Entities should use Integer for version (not the Version class)
.andShould(hasFieldAnnotatedWithType(idField, jakarta.persistence.Version.class));
// act
EvaluationResult evaluationResult = archRule.evaluate(ArchUnitHelper.allJavaClassesToCheck);
// assert
assertThat(evaluationResult.getFailureReport().getDetails()).isEmpty();
}
@Test
void test_EntitiesHaveEntityAsSuffixClassName() {
// arrange
String clazzNameSuffix = "Entity";
ArchRule archRule = entities
.should()
.haveSimpleNameEndingWith(clazzNameSuffix);
// act
EvaluationResult evaluationResult = archRule.evaluate(ArchUnitHelper.allJavaClassesToCheck);
// assert
assertThat(evaluationResult.getFailureReport().getDetails()).isEmpty();
}
}

View file

@ -0,0 +1,217 @@
package architecturetests;
import com.tngtech.archunit.core.domain.JavaModifier;
import com.tngtech.archunit.lang.ArchRule;
import com.tngtech.archunit.lang.EvaluationResult;
import com.tngtech.archunit.lang.syntax.ArchRuleDefinition;
import com.tngtech.archunit.lang.syntax.elements.GivenClassesConjunction;
import de.accso.flexinale.common.api.event.Event;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import de.accso.flexinale.common.shared_kernel.RawWrapper;
import de.accso.flexinale.common.shared_kernel.Versionable;
import org.junit.jupiter.api.Test;
import java.io.Serializable;
import java.util.Set;
import static architecturetests.ArchUnitHelper.*;
import static org.assertj.core.api.Assertions.assertThat;
public class InternalStructureOfEventClassesUsingCorrectInterfacesAndAttributeTypesTest {
static final String PACKAGE_BESUCHERPORTAL_API_EVENTS = ArchUnitHelper.PACKAGE_PREFIX + ".besucherportal.api_contract.event";
static final String PACKAGE_BACKOFFICE_API_EVENTS = ArchUnitHelper.PACKAGE_PREFIX + ".backoffice.api_contract.event";
static final String PACKAGE_SECURITY_API_EVENTS = ArchUnitHelper.PACKAGE_PREFIX + ".security.api_contract.event";
static final String PACKAGE_TICKETING_API_EVENTS = ArchUnitHelper.PACKAGE_PREFIX + ".ticketing.api_contract.event";
static final GivenClassesConjunction eventClasses = ArchRuleDefinition.classes()
.that()
.resideInAnyPackage(PACKAGE_BESUCHERPORTAL_API_EVENTS,
PACKAGE_BACKOFFICE_API_EVENTS,
PACKAGE_SECURITY_API_EVENTS,
PACKAGE_TICKETING_API_EVENTS)
.and()
.areNotEnums()
.and()
.areNotInnerClasses()
.and()
.areNotNestedClasses()
.and()
.doNotHaveModifier(JavaModifier.ABSTRACT) // ignore AbstractEvent
.and()
.haveSimpleNameNotEndingWith("Events"); // ignore the ...Events classes, as they simply collect the ...Event classes
@Test
void test_EventClassesAreImplementingEvent() {
// arrange
ArchRule archRule = eventClasses
.should()
.implement(Event.class);
// act
EvaluationResult evaluationResult = archRule.evaluate(ArchUnitHelper.allJavaClassesToCheck);
// assert
assertThat(evaluationResult.getFailureReport().getDetails()).isEmpty();
}
@Test
void test_EventClassesAreImplementingIdentifiableAndVersionableAndSerializable() {
// arrange
ArchRule archRule = eventClasses
.should()
.implement(Identifiable.class)
.andShould()
.implement(Versionable.class)
.andShould()
.implement(Serializable.class);
// act
EvaluationResult evaluationResult = archRule.evaluate(ArchUnitHelper.allJavaClassesToCheck);
// assert
assertThat(evaluationResult.getFailureReport().getDetails()).isEmpty();
}
@Test
void test_EventClassesHaveIdFieldUsingStrongType() {
// arrange
String idField = "id";
ArchRule archRule = eventClasses
.should(hasFieldOfType(idField, Identifiable.Id.class));
// act
EvaluationResult evaluationResult = archRule.evaluate(ArchUnitHelper.allJavaClassesToCheck);
// assert
assertThat(evaluationResult.getFailureReport().getDetails()).isEmpty();
}
@Test
void test_EventClassesHaveCorrelationIdFieldUsingStrongType() {
// arrange
String correlationIdField = "correlationId";
ArchRule archRule = eventClasses
.should(hasFieldOfType(correlationIdField, Identifiable.Id.class));
// act
EvaluationResult evaluationResult = archRule.evaluate(ArchUnitHelper.allJavaClassesToCheck);
// assert
assertThat(evaluationResult.getFailureReport().getDetails()).isEmpty();
}
@Test
void test_EventClassesHaveVersionFieldUsingStrongType() {
// arrange
String versionField = "version";
ArchRule archRule = eventClasses
.should(hasFieldOfType(versionField, Versionable.Version.class));
// act
EvaluationResult evaluationResult = archRule.evaluate(ArchUnitHelper.allJavaClassesToCheck);
// assert
assertThat(evaluationResult.getFailureReport().getDetails()).isEmpty();
}
@Test
void test_EventClassesHaveIdFieldWhichIsPublicFinal() {
// arrange
String idField = "id";
ArchRule archRule = eventClasses
.should(hasFieldPublicFinal(idField));
// act
EvaluationResult evaluationResult = archRule.evaluate(ArchUnitHelper.allJavaClassesToCheck);
// assert
assertThat(evaluationResult.getFailureReport().getDetails()).isEmpty();
}
@Test
void test_EventClassesHaveCorrelationIdFieldWhichIsPublicFinal() {
// arrange
String correlationIdField = "correlationId";
ArchRule archRule = eventClasses
.should(hasFieldPublicFinal(correlationIdField));
// act
EvaluationResult evaluationResult = archRule.evaluate(ArchUnitHelper.allJavaClassesToCheck);
// assert
assertThat(evaluationResult.getFailureReport().getDetails()).isEmpty();
}
@Test
void test_EventClassesHaveVersionFieldWhichIsPrivateStatic() {
// arrange
String versionField = "version";
ArchRule archRule = eventClasses
.should(hasFieldPrivateStatic(versionField, false));
// act
EvaluationResult evaluationResult = archRule.evaluate(ArchUnitHelper.allJavaClassesToCheck);
// assert
assertThat(evaluationResult.getFailureReport().getDetails()).isEmpty();
}
@Test
void test_EventClassesHaveEventAsSuffixClassName() {
// arrange
String clazzNameSuffix = "Event";
ArchRule archRule = eventClasses
.should()
.haveSimpleNameEndingWith(clazzNameSuffix);
// act
EvaluationResult evaluationResult = archRule.evaluate(ArchUnitHelper.allJavaClassesToCheck);
// assert
assertThat(evaluationResult.getFailureReport().getDetails()).isEmpty();
}
@Test
void test_EventClassesHaveOnlyFieldsImplementingRawWrapper() {
// arrange
Set<Class<?>> interfaceTypes = Set.of(RawWrapper.class);
Set<String> fieldsToBeFilteredOut = Set.of();
String fieldSuffixToBeFilteredOut = "Id";
ArchRule archRule = eventClasses
.should(hasOnlyFieldsImplementing(interfaceTypes, fieldsToBeFilteredOut, fieldSuffixToBeFilteredOut));
// act
EvaluationResult evaluationResult = archRule.evaluate(ArchUnitHelper.allJavaClassesToCheck);
// assert
assertThat(evaluationResult.getFailureReport().getDetails()).isEmpty();
}
@Test
void test_EventClassesHaveExternalIdFieldsUsingStrongType() {
//We check here that any attribute with suffix "...Id" in an event class is of type Identifiable.Id.
// Actually it would be better to check that events do not 'carry' data by themselves but only in
// TO classes wrapping this data. But this is not so easy to do.
// arrange
String idFieldSuffix = "Id";
ArchRule archRule = eventClasses
.should(hasFieldsWithSuffixOfType(idFieldSuffix, Identifiable.Id.class));
// act
EvaluationResult evaluationResult = archRule.evaluate(ArchUnitHelper.allJavaClassesToCheck);
// assert
assertThat(evaluationResult.getFailureReport().getDetails()).isEmpty();
}
}

View file

@ -0,0 +1,184 @@
package architecturetests;
import com.tngtech.archunit.lang.ArchRule;
import com.tngtech.archunit.lang.EvaluationResult;
import com.tngtech.archunit.lang.syntax.ArchRuleDefinition;
import com.tngtech.archunit.lang.syntax.elements.GivenClassesConjunction;
import de.accso.flexinale.common.shared_kernel.EqualsByContent;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import de.accso.flexinale.common.shared_kernel.RawWrapper;
import de.accso.flexinale.common.shared_kernel.Versionable;
import org.junit.jupiter.api.Test;
import java.io.Serializable;
import java.util.Set;
import static architecturetests.ArchUnitHelper.*;
import static org.assertj.core.api.Assertions.assertThat;
public class InternalStructureOfTOClassesUsingCorrectInterfacesAndAttributeTypesTest {
static final String PACKAGE_BACKOFFICE_API_MODEL = ArchUnitHelper.PACKAGE_PREFIX + ".backoffice.api_contract.event.model..";
static final String PACKAGE_SECURITY_API_MODEL = ArchUnitHelper.PACKAGE_PREFIX + ".security.api_contract.event.model..";
static final String PACKAGE_TICKETING_API_MODEL = ArchUnitHelper.PACKAGE_PREFIX + ".ticketing.api_contract.event.model..";
static final GivenClassesConjunction toClasses = ArchRuleDefinition.classes()
.that()
.resideInAnyPackage(PACKAGE_BACKOFFICE_API_MODEL,
PACKAGE_SECURITY_API_MODEL,
PACKAGE_TICKETING_API_MODEL)
.and()
.areNotEnums()
.and()
.areNotInnerClasses()
.and()
.areNotNestedClasses();
@Test
void test_TOClassesAreImplementingIdentifiableAndVersionableAndEqualsByContentAndSerializable() {
// arrange
ArchRule archRule = toClasses
.should()
.implement(Identifiable.class)
.andShould()
.implement(Versionable.class)
.andShould()
.implement(EqualsByContent.class)
.andShould()
.implement(Serializable.class);
// act
EvaluationResult evaluationResult = archRule.evaluate(ArchUnitHelper.allJavaClassesToCheck);
// assert
assertThat(evaluationResult.getFailureReport().getDetails()).isEmpty();
}
@Test
void test_TOClassesAreRecords() {
// arrange
ArchRule archRule = toClasses
.should()
.beRecords();
// act
EvaluationResult evaluationResult = archRule.evaluate(ArchUnitHelper.allJavaClassesToCheck);
// assert
assertThat(evaluationResult.getFailureReport().getDetails()).isEmpty();
}
@Test
void test_TOClassesHaveIdFieldUsingStrongType() {
// arrange
String idField = "id";
ArchRule archRule = toClasses
.should(hasFieldOfType(idField, Identifiable.Id.class));
// act
EvaluationResult evaluationResult = archRule.evaluate(ArchUnitHelper.allJavaClassesToCheck);
// assert
assertThat(evaluationResult.getFailureReport().getDetails()).isEmpty();
}
@Test
void test_TOClassesHaveVersionFieldUsingStrongType() {
// arrange
String versionField = "version";
ArchRule archRule = toClasses
.should(hasFieldOfType(versionField, Versionable.Version.class));
// act
EvaluationResult evaluationResult = archRule.evaluate(ArchUnitHelper.allJavaClassesToCheck);
// assert
assertThat(evaluationResult.getFailureReport().getDetails()).isEmpty();
}
//@Test
void test_TOClassesHaveIdFieldWhichIsPrivateFinal() {
// Actually this test is not needed as we test that all TO classes are records, see test_TOClassesAreRecords()
// In a record, all its attributes are private final
// arrange
String idField = "id";
ArchRule archRule = toClasses
.should(hasFieldPrivateFinal(idField));
// act
EvaluationResult evaluationResult = archRule.evaluate(ArchUnitHelper.allJavaClassesToCheck);
// assert
assertThat(evaluationResult.getFailureReport().getDetails()).isEmpty();
}
//@Test
void test_TOClassesHaveVersionFieldWhichIsPrivateFinal() {
// Actually this test is not needed as we test that all TO classes are records, see test_TOClassesAreRecords()
// In a record, all its attributes are private final
// arrange
String versionField = "version";
ArchRule archRule = toClasses
.should(hasFieldPrivateFinal(versionField));
// act
EvaluationResult evaluationResult = archRule.evaluate(ArchUnitHelper.allJavaClassesToCheck);
// assert
assertThat(evaluationResult.getFailureReport().getDetails()).isEmpty();
}
@Test
void test_TOClassesHaveTOAsSuffixClassName() {
// arrange
String clazzNameSuffix = "TO";
ArchRule archRule = toClasses
.should()
.haveSimpleNameEndingWith(clazzNameSuffix);
// act
EvaluationResult evaluationResult = archRule.evaluate(ArchUnitHelper.allJavaClassesToCheck);
// assert
assertThat(evaluationResult.getFailureReport().getDetails()).isEmpty();
}
@Test
void test_TOClassesHaveOnlyFieldsImplementingRawWrapper() {
// arrange
Set<Class<?>> interfaceTypes = Set.of(RawWrapper.class);
Set<String> fieldsToBeFilteredOut = Set.of();
String fieldSuffixToBeFilteredOut = "Id";
ArchRule archRule = toClasses
.should(hasOnlyFieldsImplementing(interfaceTypes, fieldsToBeFilteredOut, fieldSuffixToBeFilteredOut));
// act
EvaluationResult evaluationResult = archRule.evaluate(ArchUnitHelper.allJavaClassesToCheck);
// assert
assertThat(evaluationResult.getFailureReport().getDetails()).isEmpty();
}
@Test
void test_TOClassesHaveExternalIdFieldsUsingStrongType() {
// arrange
String idFieldSuffix = "Id";
ArchRule archRule = toClasses
.should(hasFieldsWithSuffixOfType(idFieldSuffix, Identifiable.Id.class));
// act
EvaluationResult evaluationResult = archRule.evaluate(ArchUnitHelper.allJavaClassesToCheck);
// assert
assertThat(evaluationResult.getFailureReport().getDetails()).isEmpty();
}
}

View file

@ -0,0 +1,76 @@
package architecturetests;
import com.tngtech.archunit.lang.ArchRule;
import com.tngtech.archunit.lang.EvaluationResult;
import com.tngtech.archunit.lang.syntax.ArchRuleDefinition;
import de.accso.flexinale.common.shared_kernel.DoNotCheckInArchitectureTests;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import java.util.stream.Stream;
import static architecturetests.FlexinaleComponent.*;
import static org.assertj.core.api.Assertions.assertThat;
public class NoDependenciesToSpringTest {
private static Stream<Arguments> allFlexinaleComponents() {
return Stream.of(
Arguments.of(besucherportal.name)
, Arguments.of(common.name)
, Arguments.of(security.name)
, Arguments.of(security_api_contract.name)
, Arguments.of(backoffice.name)
, Arguments.of(backoffice_api_contract.name)
, Arguments.of(ticketing.name)
, Arguments.of(ticketing_api_contract.name)
);
}
@ParameterizedTest
@MethodSource("allFlexinaleComponents")
void test_NoDependenciesToSpringInCore(final String component) {
// arrange
final String componentPackagePrefix = ArchUnitHelper.PACKAGE_PREFIX + "." + component;
final String applicationPackage = componentPackagePrefix + ".application..";
final String domainPackage = componentPackagePrefix + ".domain..";
ArchRule archRule = ArchRuleDefinition.noClasses()
.that()
.areNotAnnotatedWith(DoNotCheckInArchitectureTests.class)
.and()
.resideInAnyPackage(applicationPackage, domainPackage)
.should()
.beAnnotatedWith(ArchUnitHelper.isSpringAnnotation)
.orShould()
.dependOnClassesThat(ArchUnitHelper.isSpringClass);
// act
EvaluationResult evaluationResult = archRule.evaluate(ArchUnitHelper.allJavaClassesToCheck);
// assert
assertThat(evaluationResult.getFailureReport().getDetails()).isEmpty();
}
@Test
void test_NoDependenciesToSpringInSharedKernel() {
// arrange
final String PACKAGE_SHARED_KERNEL = ArchUnitHelper.PACKAGE_PREFIX + ".common.shared_kernel..";
ArchRule archRule = ArchRuleDefinition.noClasses()
.that()
.resideInAnyPackage(PACKAGE_SHARED_KERNEL)
.should()
.beAnnotatedWith(ArchUnitHelper.isSpringAnnotation)
.orShould()
.dependOnClassesThat(ArchUnitHelper.isSpringClass);
// act
EvaluationResult evaluationResult = archRule.evaluate(ArchUnitHelper.allJavaClassesToCheck);
// assert
assertThat(evaluationResult.getFailureReport().getDetails()).isEmpty();
}
}

View file

@ -0,0 +1,162 @@
package architecturetests;
import com.tngtech.archunit.lang.syntax.ArchRuleDefinition;
import com.tngtech.archunit.library.Architectures;
import de.accso.flexinale.common.shared_kernel.DoNotCheckInArchitectureTests;
import jakarta.transaction.Transactional;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import java.util.stream.Stream;
import static architecturetests.ArchUnitHelper.*;
import static architecturetests.FlexinaleComponent.*;
public class OnionDependenciesTest {
record Layer(String layer, String... packages) {}
private static Stream<Arguments> allFlexinaleComponents() {
return Stream.of(
Arguments.of(besucherportal.name)
, Arguments.of(backoffice.name)
, Arguments.of(ticketing.name)
//, Arguments.of(common.name) // does not have the onion structure
//, Arguments.of(security.name) // does not have the onion structure
//, Arguments.of(ticketing_api_contract.name) // does not have the onion structure
//, Arguments.of(security_api_contract.name) // does not have the onion structure
//, Arguments.of(backoffice_api_contract.name ) // does not have the onion structure
);
}
private static Stream<Arguments> allFlexinaleComponentsWithOutBesucherportal() {
return Stream.of(
Arguments.of(backoffice.name)
, Arguments.of(ticketing.name)
//, Arguments.of(common.name) // does not have the onion structure
//, Arguments.of(security.name) // does not have the onion structure
//, Arguments.of(ticketing_api_contract.name) // does not have the onion structure
//, Arguments.of(security_api_contract.name) // does not have the onion structure
//, Arguments.of(backoffice_api_contract.name ) // does not have the onion structure
);
}
@ParameterizedTest
@MethodSource("allFlexinaleComponents")
void test_onion_architecture_for_each_component(final String component) {
String componentPackagePrefix = PACKAGE_PREFIX + "." + component;
final Layer apiRest = new Layer("API Rest", componentPackagePrefix + ".api_in.rest..");
final Layer apiWeb = new Layer("API Web", componentPackagePrefix + ".api_in.web..");
final Layer apiEventIn = new Layer("API Event In", componentPackagePrefix + ".api_in.event..");
final Layer apiEventOut = new Layer("API Event Out", componentPackagePrefix + ".api_out.event..");
final Layer apiContract = new Layer("API Contract", componentPackagePrefix + ".api_contract..");
final Layer application = new Layer("Application", componentPackagePrefix + ".application..");
final Layer domainServices = new Layer("DomainServices", componentPackagePrefix + ".domain.services..", componentPackagePrefix + ".domain.dao..");
final Layer domainModel = new Layer("DomainModel", componentPackagePrefix + ".domain.model..");
final Layer infrastructure = new Layer("Infra", componentPackagePrefix + ".infrastructure");
final Layer infraPersistence = new Layer("Infra Persistence", componentPackagePrefix + ".infrastructure.persistence..");
final Layer sharedKernel = new Layer("SharedKernel", componentPackagePrefix + ".shared_kernel..");
Architectures.LayeredArchitecture layeredArchitecture = Architectures.layeredArchitecture()
.consideringOnlyDependenciesInAnyPackage(componentPackagePrefix + "..")
.withOptionalLayers(true) // needed in modulith-2 and distributed (while commented out in modulith-1)
.layer(apiRest.layer) .definedBy(apiRest.packages)
.layer(apiWeb.layer) .definedBy(apiWeb.packages)
.layer(apiEventIn.layer) .definedBy(apiEventIn.packages)
.layer(apiEventOut.layer) .definedBy(apiEventOut.packages)
.layer(apiContract.layer) .definedBy(apiContract.packages)
.layer(application.layer) .definedBy(application.packages)
.layer(domainServices.layer) .definedBy(domainServices.packages)
.layer(domainModel.layer) .definedBy(domainModel.packages)
.layer(infrastructure.layer) .definedBy(infrastructure.packages)
.layer(infraPersistence.layer).definedBy(infraPersistence.packages)
.layer(sharedKernel.layer) .definedBy(sharedKernel.packages);
// shared kernel
Architectures.LayeredArchitecture onionArchitecture = layeredArchitecture
.whereLayer(sharedKernel.layer).mayNotAccessAnyLayer()
// outer layer ring: no ingoing dependencies allowed
.whereLayer( apiWeb.layer).mayNotBeAccessedByAnyLayer()
.whereLayer(apiRest.layer).mayNotBeAccessedByAnyLayer()
.whereLayer(infrastructure.layer).mayNotBeAccessedByAnyLayer()
// web and rest - define outgoing dependencies
.whereLayer(apiWeb.layer)
.mayOnlyAccessLayers(application.layer, domainModel.layer, sharedKernel.layer)
.whereLayer(apiRest.layer)
.mayOnlyAccessLayers(apiEventOut.layer, application.layer, domainModel.layer, sharedKernel.layer)
.whereLayer(apiEventIn.layer)
.mayOnlyAccessLayers(apiContract.layer, application.layer, domainModel.layer, sharedKernel.layer)
.whereLayer(apiEventOut.layer)
.mayOnlyAccessLayers(apiContract.layer, application.layer, domainModel.layer, sharedKernel.layer)
// infrastructure - define outgoing dependencies
.whereLayer(infrastructure.layer)
.mayOnlyAccessLayers(apiEventIn.layer, apiEventOut.layer, application.layer, domainServices.layer, infraPersistence.layer, sharedKernel.layer)
.whereLayer(infraPersistence.layer)
.mayOnlyAccessLayers(domainServices.layer, domainModel.layer, sharedKernel.layer)
// application - define outgoing dependencies
.whereLayer(application.layer)
.mayOnlyAccessLayers(domainServices.layer, domainModel.layer, sharedKernel.layer)
// domain services - define outgoing dependencies
.whereLayer(domainServices.layer)
.mayOnlyAccessLayers(sharedKernel.layer, domainModel.layer)
// domain model - define outgoing dependencies
.whereLayer(domainModel.layer)
.mayOnlyAccessLayers(sharedKernel.layer);
// check the onion architecture
onionArchitecture
.ignoreDependency(isSpringBootTestClass, isSpringBootApplicationMainClass)
.ignoreDependency(isFlexinaleTestClassAnnotatedWithDoNotCheckAnnotation, isAnyClass)
.because("we want to enforce the onion architecture inside each component")
.check(allJavaClassesToCheck);
}
@ParameterizedTest
@MethodSource("allFlexinaleComponents")
void test_that_only_onion_application_services_are_annotated_with_transactional(final String component) {
// arrange
String componentPackagePrefix = PACKAGE_PREFIX + "." + component;
String applicationServicesPackage = componentPackagePrefix + ".application.services..";
ArchRuleDefinition.classes().that()
.resideInAnyPackage(componentPackagePrefix + "..") // only check classes in Component under consideration
.and()
.areAnnotatedWith(Transactional.class)
.should().resideInAnyPackage(applicationServicesPackage)
.check(allJavaClassesToCheck);
}
@ParameterizedTest
@MethodSource("allFlexinaleComponentsWithOutBesucherportal")
void test_that_all_onion_application_services_are_annotated_with_transactional(final String component) {
// arrange
String componentPackagePrefix = PACKAGE_PREFIX + "." + component;
String applicationServicesPackage = componentPackagePrefix + ".application.services..";
ArchRuleDefinition.classes().that()
.resideInAnyPackage(applicationServicesPackage)
.and()
.haveSimpleNameEndingWith("Service")
.and()
.areNotAnnotatedWith(DoNotCheckInArchitectureTests.class)
.and()
.areNotInterfaces()
.should().beAnnotatedWith(Transactional.class)
.check(allJavaClassesToCheck);
}
}

View file

@ -0,0 +1,39 @@
application.title=FLEXinale as Distributed Services, Test
application.version=test
spring.banner.location=classpath:/flexinale-banner.txt
#########################################################################
# For endpoint /version
#########################################################################
build.version=@pom.version@
build.date=@maven.build.timestamp@
#########################################################################
# Persistence
#########################################################################
spring.datasource.url=jdbc:postgresql://127.0.0.1:5432/distributed-test
spring.datasource.name=flexinale
spring.datasource.username=flexinale
spring.datasource.password=flexinale
spring.datasource.driver-class-name=org.postgresql.Driver
spring.jpa.database=postgresql
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
spring.jpa.generate-ddl=true
# spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=create
server.port=9093
#########################################################################
# Web
#########################################################################
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
server.error.path=/error
#########################################################################
# flexinale properties
#########################################################################
# Quote for online kontingent in percent
de.accso.flexinale.kontingent.quote.online=33
# time in minutes
de.accso.flexinale.vorfuehrung.min-zeit-zwischen-vorfuehrungen-in-minuten=30

View file

@ -0,0 +1,11 @@
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{YYYYMMdd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<logger name="architecturetests" level="INFO"/>
<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
</configuration>