package de.fete; import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes; import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; import static com.tngtech.archunit.library.Architectures.onionArchitecture; import com.tngtech.archunit.core.domain.JavaClass; import com.tngtech.archunit.core.importer.ImportOption; import com.tngtech.archunit.junit.AnalyzeClasses; import com.tngtech.archunit.junit.ArchTest; import com.tngtech.archunit.lang.ArchCondition; import com.tngtech.archunit.lang.ArchRule; import com.tngtech.archunit.lang.ConditionEvents; import com.tngtech.archunit.lang.SimpleConditionEvent; @AnalyzeClasses(packages = "de.fete", importOptions = ImportOption.DoNotIncludeTests.class) class HexagonalArchitectureTest { @ArchTest static final ArchRule onionArchitectureIsRespected = onionArchitecture() .domainModels("de.fete.domain.model..") .domainServices("de.fete.domain.port.in..", "de.fete.domain.port.out..") .applicationServices("de.fete.application.service..") .adapter("web", "de.fete.adapter.in.web..") .adapter("persistence", "de.fete.adapter.out.persistence..") .adapter("config", "de.fete.config.."); @ArchTest static final ArchRule domainMustNotDependOnAdapters = noClasses() .that().resideInAPackage("de.fete.domain..") .should().dependOnClassesThat().resideInAPackage("de.fete.adapter.."); @ArchTest static final ArchRule domainMustNotDependOnApplication = noClasses() .that().resideInAPackage("de.fete.domain..") .should().dependOnClassesThat().resideInAPackage("de.fete.application.."); @ArchTest static final ArchRule domainMustNotDependOnConfig = noClasses() .that().resideInAPackage("de.fete.domain..") .should().dependOnClassesThat().resideInAPackage("de.fete.config.."); @ArchTest static final ArchRule inboundPortsMustBeInterfaces = classes() .that().resideInAPackage("de.fete.domain.port.in..") .should().beInterfaces(); @ArchTest static final ArchRule outboundPortsMustBeInterfaces = classes() .that().resideInAPackage("de.fete.domain.port.out..") .should().beInterfaces(); @ArchTest static final ArchRule domainMustNotUseSpring = noClasses() .that().resideInAPackage("de.fete.domain..") .should().dependOnClassesThat().resideInAPackage("org.springframework.."); @ArchTest static final ArchRule webMustNotDependOnPersistence = noClasses() .that().resideInAPackage("de.fete.adapter.in.web..") .should().dependOnClassesThat().resideInAPackage("de.fete.adapter.out.persistence.."); @ArchTest static final ArchRule persistenceMustNotDependOnWeb = noClasses() .that().resideInAPackage("de.fete.adapter.out.persistence..") .should().dependOnClassesThat().resideInAPackage("de.fete.adapter.in.web.."); @ArchTest static final ArchRule webAdapterMustNotDependOnOutboundPorts = noClasses() .that().resideInAPackage("de.fete.adapter.in.web..") .should().dependOnClassesThat().resideInAPackage("de.fete.domain.port.out.."); @ArchTest static final ArchRule domainModelsMustBeRecords = classes() .that().resideInAPackage("de.fete.domain.model..") .and().doNotHaveSimpleName("package-info") .should(beRecords()); private static ArchCondition beRecords() { return new ArchCondition<>("be records") { @Override public void check(JavaClass javaClass, ConditionEvents events) { boolean isRecord = javaClass.reflect().isRecord(); if (!isRecord) { events.add(SimpleConditionEvent.violated(javaClass, javaClass.getFullName() + " is not a record")); } } }; } }