平台化建设思路浅谈
随着业务的不断发展,软件系统不可避免的走向熵增:复杂度越来越高、研发效率越来越差、稳定性逐渐降低等。这时抽象核心能力,走向平台化的道路成为很多系统的首要选择。笔者结合自己的经验,总结了平台化建设的几种思路,希望对大家建设平台化有所帮助。
平台化有以下优点
- 复用性强:复用核心逻辑,业务功能只在平台之上的业务层建设,降低建设成本;
- 研发效率高:平台服务作为通用能力基建,业务只需要关注需求,不用关心平台底层复杂能力实现;
- 降低复杂性:平台都有合理的职责边界和模块划分,对外开发的接口也都直观简洁;
- 稳定性:平台服务的稳定性是重中之重,一般有专门的团队维护,稳定性比一般的业务系统强;
平台化建设几种方式
1、嵌入式
平台提供类似容器的功能,业务方以Jar包形式嵌入到平台当中,类似于传统的多个war包部署在tomcat中。这种实现方式平台提供通用能力接口和业务扩展点,业务方实现业务扩展点来实现业务逻辑。一般有统一的入口(比如tomcat提供的域名+端口),根据租户标识来区分业务方(比如tomcat的serverPath),平台底层的存储及模型中也都有租户ID标识。
优势:
- 运维: 平台统一运维,业务方工作量降低;
- 对外接口:对外统一接口,调用者的工作量会降低;
劣势:
- 业务方功能受限:一般不能做重量级任务,平台以扩展点方式提供给业务方扩展,除此之外的能力都应该被限制;
- jar包冲突、类冲突问题:平台本身包含了很多依赖,业务方jar包也会有很多依赖,如果有冲突会导致整个平台不可用,下文会介绍几种规避方法;
- 业务隔离性差:不同业务方之间可能相互影响;
处理业务隔离的常用方案:
- 每个业务方提供一个集群;
- 使用类加载器隔离jar包,但可能依然解决不了jar包冲突的问题;
- 业务方提供fatjar,更改所有依赖包的package路径,比如MavenShadePlugin插件;
2、接口依赖式
平台也可以通过远程依赖的形式来整合业务的功能。这样能避免jar包冲突、业务功能受限等问题。此方案也会有一些限制,比如原jar包依赖的方式都是本地调用,现在都是远程调用,对性能、事务保证等都提出了新的挑战;需要保证接口的兼容性;平台与业务的交互由原来对象交互变成RPC接口,设计到编解码等;
这种方案适合平台与业务层交互较少、扩展点比较固定的场景,比如API渲染服务,平台提供渲染模板接口,业务方实现接口填充字段。
优势:
- 隔离性:平台和业务完全隔离;
- 业务方方便整合其他业务:平台扩展点只是作为业务方的一种能力,可以在已有的服务上提供;
劣势:
- 接口变更复杂:如果要变更接口,所有业务方都需要迭代;
- 交互复杂:都是通过RPC交互,一些扩展字段需要编解码成String传输;
- 平台方兜底:如果业务方服务异常,平台方需要提供限流、降级、兜底的能力;
3、中台式
上面讲到两种模式都是以平台为主,对上层来说都是感知的平台,适合交互接口比较固定的场景,对交互差异性大的业务不是很适合。中台式的思路是提供业务通用能力,业务方基于中台能力快速开发自己的业务,并独立提供服务或页面。
中台和平台的区别:
- 视角不同:平台关注的是去重、整合;中台关注的是复用;
- 价值体现:平台直接对外提供服务,是一个功能大集合;中台是其他产品的一部分,为了其他产品更好的提供服务;
优势:
- 能力聚焦:只需要提供核心能力支撑,不关心和用户交互;
- 复用性更强:平台不依赖业务的扩展点,而只是业务方到平台的单向依赖;
劣势:
- 个性化能力弱:因为没有扩展点,只提供通用能力;
平台化建设常用模式
1、DSL领域特性语言
DSL(Domain Specific Language)是针对某一领域,具有受限表达性的一种计算机程序设计语言。 DSL 具备强大的表现力,常用于聚焦指定的领域或问题。
在平台化建设中,DSL一般用来屏蔽平台复杂的业务逻辑,以DSL的形式对业务方暴露简洁能力接口。
比如非常有名的Gradle,就是一种DSL表达,具有比Maven更灵活的特性,关于如何构建DSL,请参考作者博客:使用Groovy构建DSL
2、Specification规约模式
Specification 模式用于解决「业务规则」相关的复杂性。
什么是业务规则呢?比如电商业务场景中需要判断:账户有效状态、是否是VIP、活动价有效期、账户余额等。在常规的代码开发中,有三种处理方式:
- 在业务流程代码中case by case的编写;缺点是会导致能力复用性、可维护性越来越差;
- 新建静态类,比如OrderValidator、TimeValidator等,缺点是处理组合逻辑(and、or)力不从心;
- 在模型类中校验,缺点是类中会掺杂越来越多的非领域逻辑,这种逻辑太多会掩盖业务核心的业务规则;
Specification模式认为校验逻辑都是“动作”,需要单独建模,且模型都是值对象,接口通用模式如下:
public interface Specification<T> {
boolean isMatch(T domainObject);
}
通过实现 Specification
接口,我们可以对不同的领域对象扩展不同的校验逻辑,而这些类都是可以复用的。
同时这些 Specification
可以作为基础元素进行任意的组合,组合更为复杂的校验规则与筛选逻辑。
当然Specification
不仅仅适用于过滤数据,它的核心是组装业务规则。例如 Spring Data JPA 提供了基于 JPA 的 Specification 模式的查询功能,使用起来非常方便,以下是一个示例:
public List<Student> getStudent(String studentNumber, String name) {
Specification<Student> specification = new Specification<Student>(){
@Override
public Predicate toPredicate(Root<Student> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
//用于暂时存放查询条件的集合
List<Predicate> predicatesList = new ArrayList<>();
//equal
if (!StringUtils.isEmpty(name)){
Predicate namePredicate = cb.equal(root.get("name"), name);
predicatesList.add(namePredicate);
}
//like
if (!StringUtils.isEmpty(nickName)){
Predicate nickNamePredicate = cb.like(root.get("nickName"), '%'+nickName+'%');
predicatesList.add(nickNamePredicate);
}
//最终将查询条件拼好然后return
Predicate[] predicates = new Predicate[predicatesList.size()];
return cb.and(predicatesList.toArray(predicates));
}
};
return repository.findAll(specification);
}
3、异构
平台提供的通用能力如果不能直接满足业务的需求,需要提供扩展能力以适配业务模型来达到异构的目的。支持业务扩展模型一般有以下几种方式:
- String ext
- Map<String,String> ext
- Class:一般用于嵌入jar式
还有另外一个问题需要解决,平台作为通用能力,有平台自身的模型,如何将平台模型转换为业务模型?简单的做法是作为扩展点开放给业务方实现,不过作为业务方,应该关注的是业务模型,平台模型有自己的规则且平台为了通用化,模型都会非常复杂。
一个更完善的平台应该支持更灵活的异构模型支持,常用是方案是字段配置化:
- 在平台申请一个模型的定义,一般包括类型,长度,限制规则等(比如必须是正整数);
- 业务方配置字段和平台模型的映射关系,如果需要动态能力,可提供Groovy或Aviator等脚本支持;
- 转换为业务方模型:根据用户配置,自动转换并给用户返回业务模型;
- 转化为平台模型:参数中需要传入元数据类名,平台按照配置规则进行有效性校验;如果需要自动转化,则需要在配置服务支持双向映射
4、统一存储
平台除了平台通用模型的存储支持,还需要支持不能转换为平台模型业务模型存储。有以下几种方案:
- 宽表:采用列式存储引擎,可方便的创建、修改列,缺点是常用的列式存储引擎一般不能提供的很好的事务支持;
- 列转行:数据表只有三列:id、key、value,查询及存储时在repository层进行转换,缺点是不能join、不支持修改列;适用于不太复杂的业务场景;
- 元数据:完全接管数据库操作,根据不同字段格式自动存储到不同列,完善的元数据平台还支持分库分表、扩缩容、数据迁移等能力,建设成本最高;
总结
平台化建设是一个非常复杂的工程,涉及的业务方、方案选择比较多,难点和投入成本也都差异较大,没有一套完美的方案能覆盖所有业务场景,本文提供了几种参考方案和设计模式,具体的方案还需要读者结合自己的业务场景来挑选最适合自己的方案。
作者简介:木小丰,美团Java技术专家,专注分享软件研发实践、架构思考。欢迎关注公共号:Java研发
本文链接:平台化建设思路浅谈