在以前的文章中介绍了通过 [《实战PMD》](https://zhuanlan.zhihu.com/p/105585075)、[《实战Checkstyle》](https://zhuanlan.zhihu.com/p/105583516)在代码级守护我们的代码,比通过[《实战Jacoco》](https://zhuanlan.zhihu.com/p/105581725)来了解当前项目的测试覆盖情况。通过得到数据了解我们的项目质量,进行定向的改进。

使用这些简单方面的自动化工具比凭空猜想或者全靠人力来接发现代码上的问题,效率高多了。

这篇文章将聚焦在`Arch Unit`上,`Arch Unit`能通过为我们提供架构的守护。

1. 开发前的准备

2. 项目分层检测

3. 循环依赖检测(同一个package下,不同package下的循环依赖)

4. Package依赖检测

5. Package和Class的包含关系检测

6. 忽略某些违规行为的三种凡是

7. 如何组织Arch Unit的测试

先来看一下Arch Unit的相关功能介绍。

这些功能很好,但是要是面面俱到,那么维护、查看规则也是一件麻烦事,所以针对项目情况,有选择定制,才能更好的展现器价值。

通过自己坐在项目的情况,可以通过金字塔来罗列:哪些行为做了价值大,哪些事情做了价值小。

---

### 1,开发前的准备

`Arch Unit`集成了`Junit4`和`Junit5`,在它的模块中包含:`archunit`、`archunit-junit4`、`archunit-junit5-api`、`archunit-junit5-engine`、`archunit-junit5-engine-api`。

在项目中只需要引入测试相关的JUnit5相关的依赖。

dependencies {
 testCompile 'com.tngtech.archunit:archunit-junit5:0.13.1'}  

test {
 useJUnitPlatform()}  

实践过程中有可能遇到的情况:

`Tips 01`: 当 @Analysis 中配置的 package 目录写错时,并不会报错package不存在,而是会让全部测试通过。

`Tips 02`: *在 layer 验证的时候,定义 layer的时候,package 名称需要根据需要在包名后添加 "..",例如:

layeredArchecture()
.layer("Representation").definedBy("com.page.practice.representation..")
.layer("Domain").definedBy("com.page.practice.domain..")
...  

.whereLayer("Domain").mayOnlyAccessedByLayers("Representation")
...

其中的 `com.page.practice.representation..` 结尾使用 ..,原因是 `representation` 中如果包含了其他两个package 例如:`request` 和 `response`,那么当 `request` 中调用到了 `domain` 中类后,上面的代码是可以检测通过。

如果去掉 `com.page.practice.representation..` 结尾的 ..,那么当`request` 中调用到了 `domain` 时,检测是不过的。

---

### 2, 项目分层检测

在做`DDD`的一些落地项目中我们会使用四层架构,即`Representation`、`Application`、`Domain`、`Infrastructure`四层,这四层的调用关系如下图所示。

 下面通过一个例子我们来约束这几层的调动关系。


package com.page.practice.archunit;  

import com.tngtech.archunit.junit.AnalyzeClasses;
import com.tngtech.archunit.junit.ArchTest;
import com.tngtech.archunit.lang.ArchRule;
import org.assertj.core.presentation.Representation;  

import static com.tngtech.archunit.library.Architectures.layeredArchitecture;  

@AnalyzeClasses(packages = "com.page.practice")
public class LayeredArchitectureTest {  

 @ArchTest static ArchRule layer\_dependencies\_are_respected = layeredArchitecture()
 .layer("Representation").definedBy("com.page.practice.representation..")
 .layer("Application").definedBy("com.page.practice.application")
 .layer("Domain").definedBy("com.page.practice.domain..")
 .layer("Infrastructure").definedBy("com.page.practice.infrastructure")  

 .whereLayer("Representation").mayNotBeAccessedByAnyLayer()
 .whereLayer("Application").mayOnlyBeAccessedByLayers("Representation")
 .whereLayer("Domain").mayOnlyBeAccessedByLayers("Application", "Representation");
}  

如上代码,定义了四层 layer,然后定义了:
1. `Representation` 不能被其他 Layer 调用,
2. `Application` 能够被 `Represenatation` 调用
3. `Domain` 能够被 `Representation`、`Application` 调用。

`TIPS:`

其中的 `com.page.practice.representation..` 结尾使用 ..,原因是 `representation` 中如果包含了其他两个package 例如:`request` 和 `response`,那么当 `request` 中调用到了 `domain` 中类后,上面的代码是可以检测通过。

如果去掉 `com.page.practice.representation..` 结尾的 ..,那么当`request` 中调用到了 `domain` 时,检测是不过的。

---

### 3, 循环依赖检测

对于一些项目仍旧在使用`MVC`三层的结构,当项目进行一段时间后,经常会遇到的循环依赖的问题。

而解决这类问题除了参考`DDD`等实践还有可能会根据团队对项目的理解,而添加团队规范,形成约束,如果只是口头约束,那么接下来开发过程中还会遇到多个上下的循环依赖问题,而如何通过测试代码约束住循环依赖,并在运行测试时第一时间得到反馈,这是效率较高的做法(`Tips`:使用 自动化测试 比完全启动项目 人肉点击测试 效率要高很多)。

在日常工作中经常出现两种循环依赖,一种是同一个`package`下出现循环依赖,另一种情况是不同`package`下的类出现了循环依赖。`Arch Unit`对这两的场景约束验证略有不同。

(1)场景一:相同`package`下的类出现了循环依赖

package com.page.practice.application;  

import com.tngtech.archunit.core.domain.JavaClass;
import com.tngtech.archunit.junit.AnalyzeClasses;
import com.tngtech.archunit.junit.ArchTest;
import com.tngtech.archunit.lang.ArchRule;
import com.tngtech.archunit.library.dependencies.SliceAssignment;
import com.tngtech.archunit.library.dependencies.SliceIdentifier;  

import static com.tngtech.archunit.library.dependencies.SlicesRuleDefinition.slices;  

@AnalyzeClasses(packages = "com.page.practice")
public class CycleDependencyRulesTest {  

 private static SliceAssignment legacyPackageStructure = new SliceAssignment() {
 [@Override](https://my.oschina.net/u/1162528) public SliceIdentifier getIdentifierOf(JavaClass javaClass) {
 if (javaClass.getPackageName().startsWith("com.page.practice.application")) {
 return SliceIdentifier.of(javaClass.getName());
  }  

 return SliceIdentifier.ignore();
  }  

 @Override public String getDescription() {
 return "legacy package structure";  }
 };  

 @ArchTest static final ArchRule no\_cycles\_by\_method\_calls\_in\_same_slice = slices()
 .assignedFrom(legacyPackageStructure)
 .should()
 .beFreeOfCycles();
}

上面的代码关键部分在 `getIdentifierOf()`中,如果`package`名字是以`javaClass.getPackageName().startsWith("com.page.practice.application")`则`return SliceIdentifier.of(javaClass.getName());` 认为是不同slice,如果不是目标`package`则忽略。

**(2)不同`package`下的类出现循环依赖

使用Arch Unit对不同`package`下的循环依赖验证条件要相对简单,只需要在`matching()`方法中,指定匹配的`package`规则就可以了。

package com.page.practice.application;  

import com.tngtech.archunit.junit.AnalyzeClasses;
import com.tngtech.archunit.junit.ArchTest;
import com.tngtech.archunit.lang.ArchRule;  

import static com.tngtech.archunit.library.dependencies.SlicesRuleDefinition.slices;  

@AnalyzeClasses(packages = "com.page.practice")
public class CycleDependencyRulesTest {  

 @ArchTest static final ArchRule no\_cycles\_by\_method\_calls\_between\_slices = slices()
 .matching("application.(*).."www.yasenyulee.cn)
 .should()
 .beFreeOfCycles();
}

即:在包 `com.page.practice.infrastructure` 中的内类不能够调用 `com.page.practices` 中的类。

---

### 4, Package依赖检测

在实现`DDD`的四层架构中,`application`可以依赖`infrastructure`,但是`infrastructure` 并不能依赖 `application`,所以通过`Arch Unit`建立规则

模拟场景如图,下图中的红线部分这个场景中出现 `infrastructure` 逆向调用了 `application` 层的代码

通过如下代码我们建立约束。

package com.page.practice.archunit;  

import com.tngtech.archunit.junit.AnalyzeClasses;
import com.tngtech.archunit.junit.ArchTest;
import com.tngtech.archunit.lang.ArchRule;  

import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;  

@AnalyzeClasses(packages =www.sangyulpt.com "com.page.practice"www.yixingylzc.cn  )
public class PackageDependencyTest {  

 @ArchTest static ArchRule infrastructure\_should\_no\_dependecny\_on_application = noClasses()
 .that()
 .resideInAPackage("www.henxingyule.com ..infrastructure" www.yuanmenyul.cn)
 .should()
 .dependOnClassesThat(www.shentuylgw.cn)
 .resideInAPackage(www.wangffzc.cn"..application");  

}

即:在包 `com.page.practice.infrastructure` 中的内类不能够调用 `com.page.practices` 中的类。

注意这里使用的是 `noClasses()` 静态方法,表达的不能依赖。

---

### 5, Package和Class的包含关系

约束某个`package`下的类的命名规则也是非常重要的,例如之前有的项目在进行过程中,由于没有进行自动化约束,而是人为的传授约束,结果一段时间过去后,命名五花八门。

例如在微服务中使用到了 `Feign`,结果出现了如下命名方式:`xxFeign`,`xxFeignClient`、`xxClient`、`xxService`。

预期较劲脑汁的沟通,不如建议一套自动化约束。了解自卸自动化约束是修改、添加代码的前提之一。

下面的例子是在**.application 包下的类都需要以Service结尾。


 @ArchTest static ArchRule = classes() .that() .resideInAPackage("..application") .should() .haveSimpleNameEndingWith("Service");

### 6, 忽略某些违规行为

如果你的项目已经开始,且代码质量不高,直接添加这些`Arch Unit`约束,很可能会遇到进退两难的问题,所以临时忽略一部分是架构向好的反向演进过程中一种需要使用的方法。

有两种行为能够忽略部分规则

(1) 使用@ArchIgnore注解


 @ArchIgnore @ArchTest static ArchRule = classes(www.jinniuyulzc.cn) .that() .resideInAPackage("..application") .should() .haveSimpleNameEndingWith("Service");

(2) 通过制定更为具体的`package`名称来做局部限定,在对自己何时的时候在扩大范围

(3) 在classpath下使用`arch_ignore_patterns.txt`,并在文件中添加需要忽略的`package`或者`class`

.\*some\\.pkg\\.ABC.\*

所有符合`some.pkg.ABC`都会被忽略。

### 7, 如何组织Arch Unit的测试
为了方便维护,可以一个类型的规放置在一个单独的Test文件,当查找是能够方便的进行相关规则的查找。

实战Arch Unit的更多相关文章

  1. [.NET领域驱动设计实战系列]专题四:前期准备之工作单元模式(Unit Of Work)

    一.前言 在前一专题中介绍了规约模式的实现,然后在仓储实现中,经常会涉及工作单元模式的实现.然而,在我的网上书店案例中也将引入工作单元模式,所以本专题将详细介绍下该模式,为后面案例的实现做一个铺垫. ...

  2. Docker虚拟化实战学习——基础篇(转)

    Docker虚拟化实战学习——基础篇 2018年05月26日 02:17:24 北纬34度停留 阅读数:773更多 个人分类: Docker   Docker虚拟化实战和企业案例演练 深入剖析虚拟化技 ...

  3. kubernetes实战(二十七):CentOS 8 二进制 高可用 安装 k8s 1.16.x

    1. 基本说明 本文章将演示CentOS 8二进制方式安装高可用k8s 1.16.x,相对于其他版本,二进制安装方式并无太大区别.CentOS 8相对于CentOS 7操作更加方便,比如一些服务的关闭 ...

  4. Linux操作系统安全-局域网私有CA(Certificate Authority)证书服务器实战篇

    Linux操作系统安全-局域网私有CA(Certificate Authority)证书服务器实战篇 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.试验架构说明 node101 ...

  5. Gerrit代码审计系统实战-Gerrit 2.15.14版本快速搭建

    Gerrit代码审计系统实战-Gerrit 2.15.14版本快速搭建  作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.Gerrit版本选择 1>.查看Gerrit官网 ...

  6. kubernetes实战(三十):CentOS 8 二进制 高可用 安装 k8s 1.17.x

    1. 基本说明 本文章将演示CentOS 8二进制方式安装高可用k8s 1.17.x,相对于其他版本,二进制安装方式并无太大区别. 2. 基本环境配置 主机信息 192.168.1.19 k8s-ma ...

  7. [.NET领域驱动设计实战系列]专题二:结合领域驱动设计的面向服务架构来搭建网上书店

    一.前言 在前面专题一中,我已经介绍了我写这系列文章的初衷了.由于dax.net中的DDD框架和Byteart Retail案例并没有对其形成过程做一步步分析,而是把整个DDD的实现案例展现给我们,这 ...

  8. 【Java并发编程实战】-----“J.U.C”:Exchanger

    前面介绍了三个同步辅助类:CyclicBarrier.Barrier.Phaser,这篇博客介绍最后一个:Exchanger.JDK API是这样介绍的:可以在对中对元素进行配对和交换的线程的同步点. ...

  9. 【Java并发编程实战】-----“J.U.C”:CountDownlatch

    上篇博文([Java并发编程实战]-----"J.U.C":CyclicBarrier)LZ介绍了CyclicBarrier.CyclicBarrier所描述的是"允许一 ...

随机推荐

  1. 019、Java中定义字符

    01.代码如下: package TIANPAN; /** * 此处为文档注释 * * @author 田攀 微信382477247 */ public class TestDemo { public ...

  2. kali下的截图工具scrot、flameshot和deepin-scrot

    对于这几个截图工具,精简好用的应该是deepin-scrot了,这是个和QQ截图有类似功能的Linux截图工具.flameshot的功能是最多的,也很好用,虽然有的功能用不上. 1.scrot安装和使 ...

  3. Django(七)模型:字段属性、字段选项(参数)

    一.模型类属性命名限制 参考:https://docs.djangoproject.com/zh-hans/3.0/topics/db/models/ 1)不能是python的保留关键字. 2)不允许 ...

  4. java 循环节长度

    循环节长度 两个整数做除法,有时会产生循环小数,其循环部分称为:循环节. 比如,11/13=6=>0.846153846153- 其循环节为[846153] 共有6位. 下面的方法,可以求出循环 ...

  5. 文件上传报错java.io.FileNotFoundException拒绝访问

    局部代码如下: File tempFile = new File("G:/tempfileDir"+"/"+fileName); if(!tempFile.ex ...

  6. php.laravel.log

    看代码,原厂代码写的比较清楚,版本(laravel5.7) 简单使用,方便调试,详细使用需要详细查看文档. 在config/app.php 添加 'log' => env('APP_LOG', ...

  7. mysql union 和union all

    http://www.w3school.com.cn/sql/sql_union.asp

  8. Spark 集群 任务提交模式

    Spark 集群的模式及提交任务的方式 本文大致的内容图 Spark 集群的两种模式: Standalone 模式 Standalone-client 任务提交方式 提交命令 ./spark-subm ...

  9. NumPy 排序、查找、计数

    章节 Numpy 介绍 Numpy 安装 NumPy ndarray NumPy 数据类型 NumPy 数组创建 NumPy 基于已有数据创建数组 NumPy 基于数值区间创建数组 NumPy 数组切 ...

  10. Docker Python 例子

    版权所有,未经许可,禁止转载 章节 Docker 介绍 Docker 和虚拟机的区别 Docker 安装 Docker Hub Docker 镜像(image) Docker 容器(container ...