在以前的文章中介绍了通过 [《实战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. 009.CI4框架CodeIgniter, 网页访问GET的URL参数获取,分段输出URL参数

    01.代码如下,我们给在PHP CI4框架中定义了一个show函数,并给了3个参数,代码如下: <?php namespace App\Controllers\System; use App\C ...

  2. CharacterEncodingFilter详解及源码解析

    字符编码过滤器  (Spring框架对字符编码的处理) 基于函数回调,对所有请求起作用,只在容器初始化时调用一次,依赖于servlet容器. web.xml配置文件 <filter> &l ...

  3. 树莓派—raspbian软件源

    零.一键换源 2018.05.18更新:新的默认源为raspbian.raspberrypi.org 因此一键换源相应改为 sudo sed -i 's#://raspbian.raspberrypi ...

  4. vue-i18n多语言文件归类的两种方法

    1.按语言类型归类 流行的做法是按照语言对文件进行归类,目录结构类似于: --lang ----en ------test.json --------"abc": "ab ...

  5. 吴裕雄 Bootstrap 前端框架开发——Bootstrap 字体图标(Glyphicons):glyphicon glyphicon-minus

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name ...

  6. mvn 编译报错mavn sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested targ

    mavn 编译报错: mavn sun.security.validator.ValidatorException: PKIX path building failed: sun.security.p ...

  7. C++编程学习(八)new&delete动态内存分配

    前段时间楼主忙着期末大作业,停更了一段,今天刚好在做机器人课程的大作业时,和同组的小伙伴利用python做了工业机器人的在线编程,突然想起来很久没有阅读大型工程了,马上补上- 接下来的几篇博客主要是博 ...

  8. VM虚拟机安装 常用Linux命令 网卡配置 (第二天)

    VM虚拟机安装:(昨天已经安装好了VM了,按照提示安装就好,很简单) 1.安装centos7虚拟机,现在磁盘里面新建文件夹作为安装文件夹 2.找到centos7的iso文件,打开vm-新建虚拟机-按照 ...

  9. UVA - 11892 ENimEN(博弈)

    题意:有n堆石子,两个人拿,拿走最后的石子的人赢,poopi先拿,条件是,每个人必须从另外一个人最后拿过的石子堆中取石子,若那堆石子被拿没了,才可以自由地拿其他堆.要求每次拿的石子数不能为0.问谁赢. ...

  10. HDU - 1114 Piggy-Bank(完全背包讲解)

    题意:背包重量为F-E,有N种硬币,价值为Pi,重量为Wi,硬币个数enough(无穷多个),问若要将背包完全塞满,最少需要多少钱,若塞不满输出“This is impossible.”. 分析:完全 ...