我们是如何解决复杂系统扩展性问题的

春哥叨叨

共 4957字,需浏览 10分钟

 · 2022-01-23

背景

我们部门是中台部门,很多系统都具有平台化或者中台化系统的特点。就是一个系统需要承接整个集团不同事业部的业务流转。

所有系统有三个特点:

  1. 对接业务方多:大量的需求接入,对于研发团队需求吞吐能力会有一个比较大的要求;

  2. 业务多样化:个性化业务多,如何解决变化带来的成本问题,关乎到效率及稳定性;

  3. 不同业务发展阶段不同,迭代效率诉求不一样:怎么解决研发交付塞车、撞车的问题;

基于以上问题及挑战,我们交付了两套研发框架:

  • 一套基于微内核+插件化的研发框架,解决了业务逻辑隔离与数据隔离,实现了独立开发、交付,提高了稳定性及扩展性;

  • 一个基于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);
}
}

微内核架构

一个完整的微内核架构长什么样子。


基于对元数据的扩展点设计,可以灵活应对业务场景复杂多样的系统复杂度扩展问题,支持灵活的版本升级。

对于校验类、转换类需求同样可以灵活轻松扩展。

希望对你有用。

浏览 39
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报