package com.example.tmpl.architecture; import com.tngtech.archunit.core.domain.JavaClasses; import com.tngtech.archunit.core.importer.ClassFileImporter; import com.tngtech.archunit.lang.ArchRule; import org.junit.jupiter.api.Test; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.*; import static com.tngtech.archunit.library.Architectures.layeredArchitecture; /** * 【架构测试】架构合规性测试。 * *

验证分层架构、依赖方向、注解使用等是否符合架构规范。 */ class ArchitectureTest { private final JavaClasses importedClasses = new ClassFileImporter().importPackages("com.example.tmpl"); @Test void domainLayerShouldNotDependOnOtherLayers() { ArchRule rule = classes() .that().resideInAPackage("..domain..") .should().onlyDependOnClassesThat().resideInAnyPackage( "java..", "jakarta.validation..", "com.example.tmpl.domain.." ); rule.check(importedClasses); } @Test void applicationLayerShouldOnlyDependOnDomain() { ArchRule rule = classes() .that().resideInAPackage("..application..") .should().onlyDependOnClassesThat().resideInAnyPackage( "java..", "org.springframework.transaction..", "org.springframework.context..", "org.springframework.data.commons..", "com.example.tmpl.domain..", "com.example.tmpl.application.." ); rule.check(importedClasses); } @Test void infrastructureLayerShouldOnlyDependOnDomainAndApplication() { ArchRule rule = classes() .that().resideInAPackage("..infrastructure..") .should().onlyDependOnClassesThat().resideInAnyPackage( "java..", "org.springframework..", "org.postgresql..", "org.flywaydb..", "org.mapstruct..", "com.example.tmpl.domain..", "com.example.tmpl.application..", "com.example.tmpl.infrastructure.." ); rule.check(importedClasses); } @Test void apiLayerShouldOnlyDependOnApplicationAndInfrastructure() { ArchRule rule = classes() .that().resideInAPackage("..api..") .should().onlyDependOnClassesThat().resideInAnyPackage( "java..", "org.springframework..", "org.springdoc..", "io.micrometer..", "jakarta.validation..", "com.example.tmpl.domain..", "com.example.tmpl.application..", "com.example.tmpl.infrastructure..", "com.example.tmpl.api.." ); rule.check(importedClasses); } @Test void controllersShouldNotDependOnInfrastructureDirectly() { ArchRule rule = classes() .that().resideInAPackage("..api.controller..") .should().onlyDependOnClassesThat().resideInAnyPackage( "java..", "org.springframework.web..", "org.springframework.http..", "jakarta.validation..", "com.example.tmpl.domain..", "com.example.tmpl.application..", "com.example.tmpl.api.dto..", "com.example.tmpl.api.mapper..", "com.example.tmpl.api.." ); rule.check(importedClasses); } @Test void transactionalAnnotationShouldOnlyBeInApplicationLayer() { ArchRule rule = noClasses() .that().resideOutsideOfPackage("..application..") .should().beAnnotatedWith(Transactional.class); rule.check(importedClasses); } @Test void domainClassesShouldNotUseSpringAnnotations() { ArchRule rule = noClasses() .that().resideInAPackage("..domain..") .should().beAnnotatedWith(Service.class); rule.check(importedClasses); } @Test void layeredArchitectureTest() { ArchRule rule = layeredArchitecture() .layer("Domain").definedBy("..domain..") .layer("Application").definedBy("..application..") .layer("Infrastructure").definedBy("..infrastructure..") .layer("API").definedBy("..api..") .whereLayer("Application").mayNotBeAccessedByAnyLayer() .whereLayer("Domain").mayOnlyBeAccessedByLayers("Application", "Infrastructure") .whereLayer("Infrastructure").mayOnlyBeAccessedByLayers("API", "Application") .whereLayer("API").mayNotBeAccessedByAnyLayer(); rule.check(importedClasses); } }