文档章节

实战Arch Unit

PageThinker
 PageThinker
发布于 02/15 22:51
字数 1901
阅读 2K
收藏 0

在以前的文章中介绍了通过 [《实战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.(*)..")  
 .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 = "com.page.practice")  
public class PackageDependencyTest {  
  
 @ArchTest static ArchRule infrastructure\_should\_no\_dependecny\_on_application = noClasses()  
 .that()  
 .resideInAPackage("..infrastructure")  
 .should()  
 .dependOnClassesThat()  
 .resideInAPackage("..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() .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文件,当查找是能够方便的进行相关规则的查找。

© 著作权归作者所有

PageThinker
粉丝 5
博文 54
码字总数 32891
作品 0
朝阳
高级程序员
私信 提问
加载中

评论(2)

柳絮飞祭奠
柳絮飞祭奠
哈不错
PageThinker
PageThinker 博主
多谢^_^
4412开发板Linux4.14和uboot设备树2017编译说明

在ubuntu下解压linux 4.14.2的压缩包(itop4412kernel4142_bsp.tar.gz),里面包含uboot、内核和system。 2.在ubutnu下,进入到步骤1解压出来的文件夹(itop4412kernel4142_bsp),拷贝里面的...

追梦人2020
03/31
20
0
iOS采集录制音视频API选择推荐

需求 对于整个iOS体系中音视频众多框架,我们不应该盲目选择,针对音视频的采集录制,需要根据自己的实际需求选择最适合项目的API,以致于最高效的完成项目任务. 以下是一些主要中低层框架的选择...

小东邪啊
2019/05/11
0
0
Scala 深入浅出实战经典 第64讲:Scala中隐式对象代码实战详解

王家林亲授《DT大数据梦工厂》大数据实战视频 Scala 深入浅出实战经典(1-87讲)完整视频、PPT、代码下载: 百度云盘:http://pan.baidu.com/s/1c0noOt6 腾讯微云:http://url.cn/TnGbdC 36...

曹振华
2015/08/24
0
0
Rust开发以太坊智能合约-Parity

Parity 声称是世界上最快速最轻便的客户端。它用Rust语言写成,可靠性、性能和代码清晰度都有所增强。Parity由Ethcore开发。Ethcore由以太坊基金会的几个会员创建。 网站: https://ethcore.i...

os2man
2018/05/02
433
0
[docker in docker]使用docker起一个Centos7.2容器 容器中再安装docker 但docker服务起不来

物理服务器上安装docker,版本如下: # docker version Client: Version: 1.8.2-el7.centos API version: 1.20 Package Version: docker-1.8.2-10.el7.centos.x86_64 Go version: go1.4.2 Gi......

zhang_gq
2016/03/30
2.8K
1

没有更多内容

加载失败,请刷新页面

加载更多

java关键字 —— new、this、static

  java关键字,也叫保留字(50个),是java有特殊意义的标识符,不能用作参数名、变量名、方法名、类名、包名等。   现在我们讲讲其中三个关键字的使用吧~~~ 一、new关键字 1. 用途:新建...

osc_s2b5kacl
23分钟前
15
0
java 集合框架的工具类Collections

sort(),max(),binarySearch(),fill() public class CollectionsDemo { public static void main(String[] args) { replaceAllDemo(); } public static void replaceAll......

osc_r9yyhhqz
23分钟前
25
0
创龙基于Xilinx Kintex-7系列高性价比FPGA开发板散热风扇接口、SATA接口

处理器 Xilinx Kintex-7系列FPGA处理器,芯片型号为XC7K325T-2FFG676I,兼容XC7K160T/410T-2FFG676I,高达326K逻辑单元,840个DSP Slice,硬件如下图: 散热风扇接口 开发板引出1个散热风扇接...

Tronlong创龙
24分钟前
27
0
【经验分享】学习Java的好书有哪些?Java书籍清单

Java书籍是程序员学习提升技能的重要学习渠道,通过书籍Java程序员可以学习当前流行、重要的相关技能。经典的书经受时间的考验,随着岁月的流逝变得越来越重要,让我们不断的学习和进步。 为...

osc_b1kaj6np
25分钟前
22
0
java Collections的reverseOrder(),SynList()

Collections的reverseOrder(比较器)返回相反的比较器,可以逆转比较器。 SynList()可以让非同步变成同步,底层实现synchronized(){}。 swap交换元素位置。 Collections.shuffle()随机重新排序...

osc_2gkfj43j
26分钟前
27
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部