我们是如何解决复杂系统扩展性问题的
背景
我们部门是中台部门,很多系统都具有平台化或者中台化系统的特点。就是一个系统需要承接整个集团不同事业部的业务流转。
所有系统有三个特点:
对接业务方多:大量的需求接入,对于研发团队需求吞吐能力会有一个比较大的要求;
业务多样化:个性化业务多,如何解决变化带来的成本问题,关乎到效率及稳定性;
不同业务发展阶段不同,迭代效率诉求不一样:怎么解决研发交付塞车、撞车的问题;
基于以上问题及挑战,我们交付了两套研发框架:
一套基于微内核+插件化的研发框架,解决了业务逻辑隔离与数据隔离,实现了独立开发、交付,提高了稳定性及扩展性;
一个基于Serverless的交付框架,解决了不同需求不同发布周期撞车、塞车的问题,结合本地IDE快速交付新功能上线;
低代码、可配置的方案今年可能会落地一套,主要目标是将简单、重复、低成本的需求以配置化搭建的方式由事业部的产品或者运营同学,进一步释放中台研发团队的交付压力,抽时间去做更有价值的事情。
微内核的研发框架
今天我们主要讲微内核+插件化研发框架的实现逻辑,Serverless及低代码配置化放到以后讲。
一个中台化或者平台化的架构都可以被抽象为一个微内核的架构,内核就是中台系统本应该具有的核心能力,插件就是不同业务线、不同事业部在不同阶段提出的个性化能力,也就是基础能力的扩展需求。
通过插件化架构,我们可以封装不变的逻辑,也就是我们的核心能力,不断打磨。提供可扩展抽象的接口,也就是我们的扩展能力,应对灵活变化的需求。
扩展点定义
一个个被抽象之后,用于扩展个性化需求的接口就叫做扩展点。
对接口的入参、返回值的格式进行标准化,进而实现了对扩展点定义的约束,扩展点以Ext结尾,便于研发查找,修改。
比如一个组织结构相关的扩展点长这样:
public interface OrganizationExtPt extends ExtensionPointI {
/**
* 根据corpId查询企业下所有部门
*
* @param corpId 企业编号
* @param includeDelete 是否包含删除的部门
* @return 部门
*/
List getDepartmentsByCorpId(String corpId, Boolean includeDelete);
}
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Component
public @interface Extension {
String bizId() default BizScenario.DEFAULT_BIZ_ID;
String useCase() default BizScenario.DEFAULT_USE_CASE;
String scenario() default BizScenario.DEFAULT_SCENARIO;
}
我们准备对这个扩展点进行扩展实现,比如有的企业使用钉钉作为组织信息管理,有的使用企业微信进行组织信息管理。
扩展定义上参考了AKF扩展模型,就是需要基于XYZ三个角度实现一个可以真正实现正交分解的业务定义。
比如采用:业务、用例、场景三个角度定义。
那么钉钉的扩展实现如下:
@Extension(bizId = "organize",useCase = "getByCorpId",scenario = "dingTalk")
@Slf4j
public class DingTalkOrganizationExt implements OrganizationExtPt {
@Override
public List getDepartmentsByCorpId(String corpId, Boolean includeDelete) {
log.info("在组织结构业务,通过企业编号获取部门列表的用例,在钉钉的场景下业务的实现处理方式");
log.info("通过钉钉的配置信息和API获取得到组织信息,并组装成云枢识别的部门信息");
Department department = new Department();
department.setName("dingTalk");
department.setCorpId(corpId);
return Collections.singletonList(department);
}
}
企业微信的扩展实现:
@Extension(bizId = "organize",useCase = "getByCorpId",scenario = "wechat")
@Slf4j
public class WechatOrganizationExt implements OrganizationExtPt {
@Override
public List getDepartmentsByCorpId(String corpId, Boolean includeDelete) {
log.info("业务:组织机构,用例:通过企业编号获取部门 , 场景:企业微信");
log.info("通过企业微信的API获取组织的部门信息,然后包装为需要的部门列表");
Department department = new Department();
department.setName("wechat");
department.setCorpId(corpId);
return Collections.singletonList(department);
}
}
扩展点执行
当我们定义了扩展点,实现了扩展实现,那么扩展点及扩展实现是如何被执行的呢?
@Command
public class OrgazationQueryExe implements CommandExecutorI {
private final ExtensionExecutor extensionExecutor;
public OrgazationQueryExe(ExtensionExecutor extensionExecutor) {
this.extensionExecutor = extensionExecutor;
}
@Override
public MultiResponse execute(OrgnizationQry cmd) {
String corpId = cmd.getCorpId();
boolean includeDelete = cmd.isIncludeDelete();
List departmentList = extensionExecutor.execute(OrganizationExtPt.class, cmd.getBizScenario(),
ex -> ex.getDepartmentsByCorpId(corpId, includeDelete));
return MultiResponse.ofWithoutTotal(departmentList);
}
}
在ExtensionExecutor
中,实现了根据不同的cmd
找到不同的OrganizationExtPt.class
,并进行调用。
如何通过坐标得到扩展实例:
protected Ext locateExtension(Class targetClz, BizScenario bizScenario) {
checkNull(bizScenario);
Ext extension;
String bizScenarioUniqueIdentity = bizScenario.getUniqueIdentity();
logger.debug("BizScenario in locateExtension is : " + bizScenarioUniqueIdentity);
// first try
extension = firstTry(targetClz, bizScenarioUniqueIdentity);
if (extension != null) {
return extension;
}
// loop try
extension = loopTry(targetClz, bizScenarioUniqueIdentity);
if (extension != null) {
return extension;
}
throw new ColaException("Can not find extension with ExtensionPoint: "+targetClz+" BizScenario:"+bizScenarioUniqueIdentity);
}
以一个http调用看下如何使用。
@RestController
public class OrganizationController {
private final OrganizationServiceI organizationServiceI;
public OrganizationController(OrganizationServiceI organizationServiceI) {
this.organizationServiceI = organizationServiceI;
}
@GetMapping(value = "/organization/getDepartmentsByCorpId/{corpId}/{scenario}")
public MultiResponse listCustomerByName(@PathVariable("corpId") String corpId,@PathVariable("scenario") String scenario){
OrgnizationQry qry = new OrgnizationQry();
qry.setCorpId(corpId);
qry.setIncludeDelete(true);
qry.setBizScenario(BizScenario.valueOf("organize","getByCorpId",scenario));
return organizationServiceI.getDepartmentsByCorpId(qry);
}
}
微内核架构
一个完整的微内核架构长什么样子。
基于对元数据的扩展点设计,可以灵活应对业务场景复杂多样的系统复杂度扩展问题,支持灵活的版本升级。
对于校验类、转换类需求同样可以灵活轻松扩展。
希望对你有用。