Skip to content

Latest commit

 

History

History
116 lines (86 loc) · 5.35 KB

从事务脚本到领域驱动1.md

File metadata and controls

116 lines (86 loc) · 5.35 KB

从事务脚本到领域驱动(1)——分层和自治

事务脚本大多采用传统三层架构(表示层 UI->业务逻辑层 BLL->数据访问层 DAL)。分层架构可以在一定程度上达到“高内聚/低耦合”。但仅有分层是不够的,很快我们就会产生一些疑问:

  • 业务逻辑层的一个Service是否可以依赖其他Service?

  • 数据访问层的一个 Dao 是否可以依赖其他 Dao?

  • Service是否可以依赖多个 Dao?

如果没有一个清晰的规则对这些依赖加以限制,系统最终将会不可避免地变成一个大泥球。除了纵向的分层,我们还需要做横向的切割。

将系统横向切割成自治的小块

  1. 如何确定自治的边界?粒度多大?

    自治以聚合根为单位。

    • 每个聚合根都有独立的应用服务、领域服务(如果需要)、资源库、模型。

    • 聚合根实体不允许引用其他聚合根实体,只允许持有其他聚合根的Id。

    • 事务在应用服务层。应用服务不允许依赖其他应用服务,且一个事务只允许更新一个聚合根。对于需要级联更新的情况,应使用领域事件

    • 应用服务不允许直接依赖其他聚合根的资源库。需要关联查询的时候,应依赖六边形架构的adaptor接口。

    • 资源库不允许依赖其他资源库。

  2. 如何在技术层面约束依赖?

    由于自治的粒度较小,且既有纵向的分层又有横向的切割,子项目或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), ".");
         }
        };
     }

    实际就是通过反射得到所有依赖包,再通过一定的规则进行校验。