事务脚本大多采用传统三层架构(表示层 UI->业务逻辑层 BLL->数据访问层 DAL)。分层架构可以在一定程度上达到“高内聚/低耦合”。但仅有分层是不够的,很快我们就会产生一些疑问:
-
业务逻辑层的一个Service是否可以依赖其他Service?
-
数据访问层的一个 Dao 是否可以依赖其他 Dao?
-
Service是否可以依赖多个 Dao?
如果没有一个清晰的规则对这些依赖加以限制,系统最终将会不可避免地变成一个大泥球。除了纵向的分层,我们还需要做横向的切割。
-
如何确定自治的边界?粒度多大?
自治以聚合根为单位。
-
如何在技术层面约束依赖?
由于自治的粒度较小,且既有纵向的分层又有横向的切割,子项目或module带来的静态隔离无法提供全部所需的依赖约束。通常子项目或module的粒度为界限上下文或更大。
我们可以使用archunit以单元测试的形式针对上述每一条约束创建一个测试用例。例如用于分层依赖检查的测试用例为:
@ArchTest public static final ArchRule layer_dependcy = layeredArchitecture() .layer("Controller") .definedBy("com.bzb.atjob.app..feed.web..") .layer("ApplicationService") .definedBy("com.bzb.atjob.app..application..") .layer("Repository") .definedBy("com.bzb.atjob.app..repository..") .layer("Model") .definedBy("com.bzb.atjob.app..model..") .layer("Mapper") .definedBy("com.bzb.atjob.app..mapper..") .whereLayer("Controller") .mayNotBeAccessedByAnyLayer() .whereLayer("ApplicationService") .mayOnlyBeAccessedByLayers("Controller") .whereLayer("Repository") .mayOnlyBeAccessedByLayers("ApplicationService") .whereLayer("Mapper") .mayOnlyBeAccessedByLayers("Repository") .as("层依赖关系");
全部测试代码请见 DependencyTest.java.
在执行
mvn clean install
编译完代码后会自动运行单元测试。archunit 提供了非常灵活的自定义规则的接口,可以根据需要自己灵活添加检查规则。例如我们需要增加一个
Application Service 不应依赖其他模块的 Repository
的自定义规则:@ArchTest public static final ArchRule application_service_should_depend_on_repository_inside_same_package = noClasses() .that() .resideInAPackage("com.bzb.atjob.app..application") .should(dependOnClassesThatResideInOtherParentPackageAndEndWith("Repository")) .as("Application Service 不应依赖其他模块的 Repository");
其中
dependOnClassesThatResideInOtherParentPackageAndEndWith
自定义规则的实现如下:private static ArchCondition<JavaClass> dependOnClassesThatResideInOtherParentPackageAndEndWith( String classEndWith) { return new ArchCondition<JavaClass>("依赖不同的上级包") { @Override public void check(JavaClass input, ConditionEvents events) { String inputParentPackageName = parentPackageName(input.getPackageName()); List<String> diffParentPackageNames = input.getFieldAccessesFromSelf().stream() .filter(t -> t.getTarget().getRawType().getSimpleName().endsWith(classEndWith)) .map(t -> parentPackageName(t.getTarget().getRawType().getPackageName())) .filter(t -> !StringUtils.equals(t, inputParentPackageName)) .collect(Collectors.toList()); boolean accessedDiffParentPackages = diffParentPackageNames.size() > 0; if (accessedDiffParentPackages) { events.add( SimpleConditionEvent.satisfied( input, input.getName() + " 依赖包: " + StringUtils.join(diffParentPackageNames, ", "))); } } private String parentPackageName(String packageName) { String[] packageNames = packageName.split("[.]"); if (packageNames.length <= 1) { return ""; } return StringUtils.join(Arrays.copyOfRange(packageNames, 0, packageNames.length - 1), "."); } }; }
实际就是通过反射得到所有依赖包,再通过一定的规则进行校验。