乐趣区

关于java:新来个技术总监给公司项目引入了全新的业务架构堪称最佳实践

本文开始前,问大家一个问题,你感觉一份业务代码,尤其是互联网业务代码,都有哪些特点?

我能想到的有这几点:

  • 互联网业务 迭代快 ,工期紧,导致 代码构造凌乱,简直没有代码正文和文档
  • 互联网 人员变动频繁 ,很容易接手他人的老我的项目, 新人基本没工夫吃透代码构造,紧迫的工期又只能让屎山越堆越大。
  • 多人一起开发,每个人的编码习惯不同,工具类代码各用个的,业务命名也常常抵触,影响效率。
  • 大部分团队简直 没有工夫做代码重构,任由代码腐烂。

每当咱们新启动一个代码仓库,都是信念满满,构造整洁。然而工夫越往后,代码就变得糜烂不堪,技术债权越来越宏大。

这种状况有解决方案吗?也是有的:

  1. 小组内定期做代码重构,解决技术债权。
  2. 组内设计欠缺的利用架构,让代码的腐烂来得慢一些。(当然很难做到齐全不腐烂)
  3. 设计尽量简略,让不同层级的开发都能疾速看懂并上手开发,而不是在一堆简单的没人看懂的代码上堆更多的屎山。

而 COLA,咱们明天的配角,就是为了提供 一个可落地的业务代码构造标准,让你的代码腐烂的尽可能慢一些,让团队的开发效率尽可能快一些。

COLA 是什么

COLA 是由阿里大佬张建飞所提出的一种 业务代码架构的最佳实际,并且曾经在阿里云脚手架代码生成器中作为一个可选项,可见其曾经领有了肯定影响力。

COLA 是 Clean Object-Oriented and Layered Architecture 的缩写,代表“整洁面向对象分层架构”。

在 COLA 4.0,也就是目前最新的版本中,作者将 COLA 拆分为 COLA 架构(Archetype)和 COLA 组件(Components)两个局部:

  • COLA 架构:COLA 利用的代码模板。
  • COLA 组件:提供一些十分有用的通用组件,这些组件能够帮忙咱们晋升研发效率。

两者互不烦扰,能够独立应用。

COLA 整体架构

首先次要谈谈 COLA 架构,COLA 的官网博文中是这么介绍的:

在平时咱们的业务开发中,大部分的零碎都须要:

接管 request,响应 response;

做业务逻辑解决,像校验参数,状态流转,业务计算等等;

和内部零碎有联动,像数据库,微服务,搜索引擎等;

正是有这样的共性存在,才会有很多普适的架构思维呈现,比方分层架构、六边形架构、洋葱圈架构、整洁架构(Clean Architecture)、DDD 架构等等。

这些利用架构思维尽管很好,但咱们很多同学还是“不讲 Co 德,明确了很多情理,可还是过不好这毕生”。问题就在于不足实际和领导。COLA 的意义就在于,他不仅是思维,还提供了可落地的实际。应该是为数不多的利用架构层面的开源软件。

COLA 提供了一整套代码架构,拿来即用。 其中蕴含了很多架构设计思维,包含探讨度很高的畛域驱动设计 DDD 等。

留神:每个人对于架构设计都有着本人的了解。所以对于 COLA 的架构,本篇文章也仅仅只是我本人对于 COLA 的浅显了解,大家能够批评对待。

COLA 分层架构

先来看两张官网介绍图

其次,还有一个官网的表格,介绍了 COLA 中每个层的命名和含意:

档次包名性能必选Adapter 层 web 解决页面申请的 Controller 否 Adapter 层 wireless 解决无线端的适配否 Adapter 层 wap 解决 wap 端的适配否 App 层 executor 解决 request,包含 command 和 query 是 App 层 consumer 解决内部 message 否 App 层 scheduler 解决定时工作否 Domain 层 model 畛域模型否 Domain 层 ability 畛域能力

包含 DomainService 否 Domain 层 gateway 畛域网关,解耦利器是 Infra 层 gatewayimpl 网关实现是 Infra 层 mapperibatis 数据库映射否 Infra 层 config 配置信息否 Client SDKapi 服务对外透出的 API 是 Client SDKdto 服务对外的 DTO 是

这两张图和一个表格曾经把整个 COLA 架构的绝大部分内容展示给了大家,然而一下子这么多信息量可能很难消化。

既然整个示例架构我的项目是一个 Maven 父子构造,那咱们就从父模块一个个好好过一遍。

首先父模块的 pom.xml 蕴含了如下子模块:

<modules>
  <module>demo-web-client</module>
  <module>demo-web-adapter</module>
  <module>demo-web-app</module>
  <module>demo-web-domain</module>
  <module>demo-web-infrastructure</module>
  <module>start</module>
</modules>

start 层

该模块作为整个利用的启动模块(通常是一个 SpringBoot 利用),只承当启动我的项目和全局相干配置项的寄存职责。代码目录如下:

将启动独立进去,益处是清晰简洁,也能让新人一眼就看出如何运行我的项目,以及我的项目的一些根底依赖。

adapter 层

接下来咱们依照之前架构图从上到下的程序,一个个看。

首先是 demo-web-adapter 模块,这名字是不是很陈腐?但其实,能够了解为平时咱们用的 controller 层(对于 Web 利用来说),换汤不换药。

在 COLA 官网博客中,也能找到如下的形容:

Controller 这个名字次要是来自于 MVC,因为是 MVC,所以自带了 Web 利用的烙印。然而,随着 mobile 的衰亡,当初很少有利用仅仅只反对 Web 端,通常的标配是 Web,Mobile,WAP 三端都要反对。

cilent 层

有了咱们说的“controller”层,接下来有的小伙伴必定就会想,是不是 service 层啦。

是,也不是。

传统的 Web 利用中,齐全能够只有一个 service 层给 controller 层调用,然而作为一个业务利用,除非你真的只是个前端页面的有情吐数据机器,否则很大可能性你的利用会有很多其余上下游调用方,并且你须要提供接口给他们。

这时候你给他们的不应该是一个 Web 接口,应该是 RPC 调用的服务层接口,至于起因不是本文的重点,具体就不开展了。

所以在 COLA 中,你的 adapter 层,调用了 client 层,client 层中就是你服务接口的定义。

从上图中能够看到,client 包里有:

  • api 文件夹:寄存服务接口定义
  • dto 文件夹:寄存传输实体

留神,这里只是服务接口定义,而不是服务层的具体实现,所以在 adapter 层中,调用的其实是 client 层的接口:

@RestController
public class CustomerController {

    @Autowired
    private CustomerServiceI customerService;

    @GetMapping(value = "/customer")
    public MultiResponse<CustomerDTO> listCustomerByName(@RequestParam(required = false) String name){CustomerListByNameQry customerListByNameQry = new CustomerListByNameQry();
        customerListByNameQry.setName(name);
        return customerService.listByName(customerListByNameQry);
    }

而最终接口的具体实现逻辑放到了 app 层。

@Service
@CatchAndLog
public class CustomerServiceImpl implements CustomerServiceI {

    @Resource
    private CustomerListByNameQryExe customerListByNameQryExe;

    @Override
    public MultiResponse<CustomerDTO> listByName(CustomerListByNameQry customerListByNameQry) {return customerListByNameQryExe.execute(customerListByNameQry);
    }
}

app 层

接着下面说的,咱们的 app 模块作为服务的实现,寄存了各个业务的实现类,并且严格依照业务分包 ,这里划重点, 是先依照业务分包,再依照性能分包的,为何要这么做,文章前面还会多说两句,先看图:

customer 和 order 别离对应了生产着和订单两个业务子畛域。外面是 COLA 定义 app 层上面三种性能:

App 层 executor 解决 request,包含 command 和 query 是 App 层 consumer 解决内部 message 否 App 层 scheduler 解决定时工作否

能够看到,音讯队列的消费者和定时工作,这类平时咱们业务开发常常会遇到的场景,也放在 app 层。

domain 层

接下来便是 domain,也就是畛域层,先看一下畛域层整体构造:

能够看到,首先是依照不同的畛域(customer 和 order)分包,外面则是三种次要的文件类型:

  1. 畛域实体:实体模型能够是充血模型(请自行理解),例如官网示例里的 Customer.java 如下:
@Data
@Entity
public class Customer{

    private String customerId;
    private String memberId;
    private String globalId;
    private long registeredCapital;
    private String companyName;
    private SourceType sourceType;
    private CompanyType companyType;

    public Customer() {}

    public boolean isBigCompany() {return registeredCapital > 10000000; // 注册资金大于 1000 万的是大企业}

    public boolean isSME() {return registeredCapital > 10000 && registeredCapital < 1000000; // 注册资金大于 10 万小于 100 万的为中小企业}

    public void checkConfilict(){
        //Per different biz, the check policy could be different, if so, use ExtensionPoint
        if("ConflictCompanyName".equals(this.companyName)){throw new BizException(this.companyName+"has already existed, you can not add it");
        }

    }
}
  1. 畛域能力:domainservice 文件夹下,是畛域对外裸露的服务能力,如上图中的 CreditChecker
  2. 畛域网关:gateway 文件夹下的接口定义,这里的接口你能够粗略的了解成一种 SPI,也就是交给 infrastructure 层去实现的接口。

例如 CustomerGateway 里定义了接口 getByById,要求 infrastructure 的实现类必须定义如何通过消费者 Id 获取消费者实体信息,而 infrastructure 层能够实现任何数据源逻辑,比方,从 MySQL 获取,从 Redis 获取,还是从内部 API 获取等等。

public interface CustomerGateway {public Customer getByById(String customerId);
}

在示例代码的 CustomerGatewayImpl(位于 infrastructure 层)中,CustomerDO(数据库实体)通过 MyBatis 的查问,转换为了 Customer 畛域实体,进行返回。实现了依赖倒置。

@Component
public class CustomerGatewayImpl implements CustomerGateway {
    @Autowired
    private CustomerMapper customerMapper;

    public Customer getByById(String customerId){CustomerDO customerDO = customerMapper.getById(customerId);
      //Convert to Customer
      return null;
    }
}

infrastructure 层

最初是咱们的 infrastructure 也就是基础设施层,这层有咱们方才提到的 gatewayimpl 网关实现,也有 MyBatis 的 mapper 等数据源的映射和 config 配置文件。

Infra 层 gatewayimpl 网关实现是 Infra 层 mapperibatis 数据库映射否 Infra 层 config 配置信息否

所有层讲完了,COLA4.0 很简单明了,最初,在援用一段官网介绍博客原文来总结 COLA 的层级:

1)适配层(Adapter Layer):负责对前端展现(web,wireless,wap)的路由和适配,对于传统 B / S 零碎而言,adapter 就相当于 MVC 中的 controller;

2)应用层(Application Layer):次要负责获取输出,组装上下文,参数校验,调用畛域层做业务解决,如果需要的话,发送音讯告诉等。档次是凋谢的,应用层也能够绕过畛域层,间接拜访根底施行层;

3)畛域层(Domain Layer):次要是封装了外围业务逻辑,并通过畛域服务(Domain Service)和畛域对象(Domain Entity)的办法对 App 层提供业务实体和业务逻辑计算。畛域是利用的外围,不依赖任何其余档次;

4)根底施行层(Infrastructure Layer):次要负责技术细节问题的解决,比方数据库的 CRUD、搜索引擎、文件系统、分布式服务的 RPC 等。此外,畛域防腐的重任也落在这里,内部依赖须要通过 gateway 的本义解决,能力被下面的 App 层和 Domain 层应用。

COLA 架构的特色

说完了分层架构,咱们再来回顾下下面提到的 COLA 架构的几个特色的设计

畛域与性能的分包策略

也就是上面这张图的意思,先依照畛域分包,再依照性能分包,这样做的其中一点益处是能将腐烂管制在该业务域内。

比方消费者 customer 和订单 order 两个畛域是两个后端开发并行开发,两个人对于 dto,util 这些文件夹的命名习惯都不同,那么只会腐烂在各自的业务包上面,而不会将 dto,util,config 等文件夹放在一起,极容易引发文件抵触。

后面的包定义,都是性能维度的定义。为了兼顾畛域维度的内聚性,咱们有必要对包构造进行一下微调,即顶层包构造应该是依照畛域划分,让畛域内聚。

业务域和内部依赖解耦

后面提到的 domain 和 infrastructure 层的依赖倒置,是一个十分有用的设计,进一步解耦了取数逻辑的实现。

例如下图中,你的畛域实体是商品 item,通过 gateway 接口,你的商品的数据源能够是数据库,也能够是内部的服务 API。

如果是内部的商品服务,你通过 API 调用后,商品域吐出的是一个大而全的 DTO(可能蕴含几十个字段),而在下单这个阶段,订单所须要的可能只是其中几个字段而已。你拿到了内部畛域 DTO,转为本人畛域的 Item,只留下题目价格库存等必要的数据字段。

COLA 并不完满

诚然,COLA 曾经做的足够清晰简洁了,然而它依然有不完满的中央,比方每个接口的出入参都会依据业务名做定义,导致了很多构造极为类似的 DTO,DTO 的爆炸增长是个问题。参考:ISSUE-271

然而总的来说,COLA 只是给你提供了一种架构设计的思维,并不深刻到强制你应用某种标准的层面,所以对于 COLA 中你感觉简单,或者不了解的中央,很多时候须要你本人来做衡量,作取舍。取其精华,去其糟粕 的使用到你的我的项目中。

总结

COLA 架构并不简单,COLA 曾经从 1.0 版本通过逐次精简,倒退到了现在的状态。在阿里云代码脚手架生成器中作为一个可选项,足见其曾经趋于成熟。

退出移动版