简介

Batrix企业能力库,是京东物流战略级项目-技术中台架构升级项目的基础底座。致力于建立企业级业务复用能力平台,依托能力复用业务框架Batrix,通过通用能力/扩展能力的定义及复用,灵活支持业务差异化场景的快速能力编排组装,从而通过技术驱动的方式助力业务整体交付吞吐率。

在四层架构(接入层、交易层、履约层、执行层)的背景下,交易平台组承接交易层的业务逻辑,负责交易场景下的可复用能力开发。当前时间,交易订单域已沉淀综合评分超100的能力13个,交易产品域已沉淀综合评分超100的能力5个。

本文重点为大家介绍交易域如何使用Batrix框架沉淀能力

准备工作

针对能力域建设,需要多方共同参与,业务、产品、研发、测试等缺一不可。一个能力域需要什么能力,能力内逻辑是什么,不同能力之间负责的业务边界是什么,都需要业务架构师和技术架构师共同商定,需要有一些前置的顶层设计。

业务架构师前期可以由产品侧出任,后期一定要有业务侧重度参与,需要根据已有的业务沉淀和未来的行业发展规划共同拟定。

技术架构师由研发出任,负责针对能力的开发进行应用架构设计,保证能力编码的合理性,需要针对能力扩展性进行把控,为后续的复用打好基础,和业务架构师共同商定领域上下文的设计。

测试同学主要负责能力质量,在能力建设过程中,难免出现代码的调整,扩展逻辑的提取和沉淀,这些都需要对已有业务逻辑做回归验证,保障能力调整过程中的线上稳定。

能力沉淀是个复杂而又长期的事情,不可能短时间见成效,需要管理者进行预期管理和支持。

能力建设最关键的两个问题:

1、哪些逻辑是能力的基础逻辑,所有业务都需要执行。如果有个性化需求逻辑怎么适配。

2、如何复用现有能力。

建设方法

Batrix框架提供标准能力和扩展能力的建设方案:

标准能力:负责能力的基础逻辑,所有使用到能力的业务都要执行。提前设计扩展插槽,为后续的能力复用扩展打好基础。

扩展能力:针对标准能力内预留的扩展插槽做具体实现,解决个性化逻辑的适配。

Batrix框架提供能力建设的工程实施方案:

BDomainFlowNode:FlowNode是外显的,能够独立提供服务的能力节点,Batrix框架流程编排使用的是以FlowNode为单位的功能编排。简单逻辑可以直接在FlowNode内开发实现。复杂逻辑由FlowNode调用DomainAbility或者DomainService,进行组合引用,实现能力业务逻辑。

BIDomainService:DomainService开发多个ability的组合业务,尽量不要包含复杂的业务处理,只有简单的逻辑代码部分。

BBaseDomainAbility:DomainAbility开发独立的功能点,功能为公共的基础功能,每个ability支持一个类型的扩展插槽。扩展插槽的实现,可以开发个性化需求的代码部分。如果没有个性化代码部分,可以不实现扩展点部分代码。多个ability,由FlowNode或者BIDomainService进行应用组合,提供服务。

BIDomainAbilityExtension:Extension实现SPI定义,开发个性化自定义代码部分。

建设标准能力,需要对本域内的业务有清晰正确的认识,需要有远期的规划。

不同的业务域因业务逻辑不同,所以能力的建设思路会有差异。业务能力的划分粒度,不同能力之间的边界很难通过一次设计就固定下来,还需要根据具体场景,持续优化。在建设能力的过程中,根据理解程度的不同,可以大致分为四个阶段:

初期:只知道能力具体负责什么功能,但不清楚哪些应该属于通用标准逻辑部分,哪些后期需要扩展,为扩展逻辑预留扩展插槽。针对扩展插槽,不清楚扩展逻辑的入参有哪些,出参有哪些。(未进行过能力域建设过的系统,多数属于该阶段。)

方案建议****:全部代码由Extension实现,使用一个FlowNode对外承接调用逻辑,最终由Extension实现业务。如果有个性化逻辑,则使用另一个Extension实现。在后期通过对多个Extension的抽象,分析出哪些是公共逻辑,哪些是个性化逻辑,并把能力进行下一步演化。

示例****:

public class IntegralOccupyFlowNode extends AbstractDomainFlowNode {

    private static final Logger LOGGER = LoggerFactory.getLogger(IntegralOccupyFlowNode.class);

    @Resource
private CreateIntegralOccupyAbility createIntegralOccupyAbility; @Override
public BOutputMessage call(InputMessage inputMessage) {
CallerInfo info = Profiler.registerInfo(this.getClass().getName() + ".call"
, UmpKeyConstants.JDL_OMS_EXPRESS_APP_CODE
, UmpKeyConstants.METHOD_ENABLE_HEART
, UmpKeyConstants.METHOD_ENABLE_TP);
try {
BOutputMessage outputMessage = new BOutputMessage();
if (inputMessage.getBody() == null) {
throw new DomainAbilityException(UnifiedErrorSpec.BasisOrder.INTEGRAL_OCCUPY_FAIL).withCustom("积分占用流程节点执行失败");
}
ExpressOrderContext expressOrderContext = (ExpressOrderContext) inputMessage.getBody();
outputMessage.setBody(expressOrderContext);
LOGGER.info("积分占用流程节点开始执行");
createIntegralOccupyAbility.execute(expressOrderContext, this);
outputMessage.setCallBackInterface(new BDomainFlowNodeCallBack() {
@Override
public void callBack() {
AbstractDomainAbility releaseAbility = getDomainAbility(expressOrderContext, createErrorIntegralReleaseAbility, modifyErrorIntegralReleaseAbility);
if (releaseAbility != null) {
releaseAbility.execute(expressOrderContext, IntegralOccupyFlowNode.this);
}
LOGGER.info("回退积分任务完成");
}
});
outputMessage.setCallBack(true);
LOGGER.info("积分占用流程节点开始结束");
return outputMessage;
} catch (DomainAbilityException e) {
LOGGER.error("积分占用流程节点执行异常: {}", e.fullMessage());
throw e;
} catch (Exception e) {
Profiler.functionError(info);
LOGGER.error("积分占用流程节点执行异常", e);
throw e;
} finally {
Profiler.registerInfoEnd(info);
}
} @Override
public String getCode() {
return FlowConstants.EXPRESS_ORDER_INTEGRAL_FLOW_CODE;
} @Override
public String getName() {
return FlowConstants.EXPRESS_ORDER_INTEGRA_FLOW_NAME;
}
}
@DomainAbility(name = "纯配接单领域能力-积分占用活动", parent = DomainConstants.EXPRESS_ORDER_DOMIAN_CODE)
public class CreateIntegralOccupyAbility extends AbstractDomainAbility<ExpressOrderContext, IIntegralExtension> { private static final Logger LOGGER = LoggerFactory.getLogger(CreateIntegralOccupyAbility.class); /**
* 积分占用
*
* @param context
* @param bDomainFlowNode
* @throws DomainAbilityException
*/
@Override
public void execute(ExpressOrderContext context, BDomainFlowNode bDomainFlowNode) {
CallerInfo callerInfo = Profiler.registerInfo(this.getClass().getName() + ".execute"
, UmpKeyConstants.JDL_OMS_EXPRESS_APP_CODE
, UmpKeyConstants.METHOD_ENABLE_HEART
, UmpKeyConstants.METHOD_ENABLE_TP);
try {
//根据Batrix配置,获取扩展点实现
IIntegralExtension extension = this.getMiddleExtensionFast(IIntegralExtension.class,
context,
SimpleReducer.listCollectOf(Objects::nonNull), bDomainFlowNode);
extension.execute(context);
} catch (DomainAbilityException e) {
LOGGER.error("纯配接单领域能力-积分占用活动执行异常: {}", e.fullMessage());
throw e;
} catch (Exception e) {
Profiler.functionError(callerInfo);
LOGGER.error("纯配接单领域能力-积分占用活动执行异常", e);
throw new DomainAbilityException(UnifiedErrorSpec.BasisOrder.INTEGRAL_OCCUPY_FAIL, e);
} finally {
Profiler.registerInfoEnd(callerInfo);
}
} @Override
public IIntegralExtension getDefaultExtension() {
return null;
}
}
/**
* 积分占用扩展点
*/
@Extension(code = ExpressOrderProduct.CODE)
public class CreateIntegralOccupyExtension implements IIntegralExtension { private static final Logger LOGGER = LoggerFactory.getLogger(CreateIntegralOccupyExtension.class); @Resource
private IntegralFacade integralFacade; /**
* 积分占用
*
* @param expressOrderContext
* @throws AbilityExtensionException
*/
@Override
public void execute(ExpressOrderContext expressOrderContext) throws AbilityExtensionException {
CallerInfo callerInfo = Profiler.registerInfo(this.getClass().getName() + ".execute"
, UmpKeyConstants.JDL_OMS_EXPRESS_APP_CODE
, UmpKeyConstants.METHOD_ENABLE_HEART
, UmpKeyConstants.METHOD_ENABLE_TP);
try {
LOGGER.info("开始执行接单积分占用扩展点");
ExpressOrderModel orderModel = expressOrderContext.getOrderModel();
if (orderModel.getFinance() == null || orderModel.getFinance().getPoints() == null) {
LOGGER.info("未使用积分,积分占用扩展点执行结束");
return;
}
IntegralFacadeRequest request = new IntegralFacadeRequest();
request.setPin(orderModel.getOperator());
request.setBusinessNo(orderModel.getRefOrderInfoDelegate().getWaybillNo());
Points points = orderModel.getFinance().getPoints();
request.setDeductNumber(points.getRedeemPointsQuantity().getValue().intValue());
request.setRelease(false);
request.setTitle(IntegralOperaterEnum.OCCUPY.getTitle());
integralFacade.operateIntegrals(request);
} catch (InfrastructureException infrastructureException) {
LOGGER.error("接单积分占用扩展点执行异常", infrastructureException);
throw infrastructureException;
} catch (Exception exception) {
Profiler.functionError(callerInfo);
LOGGER.error("接单积分占用扩展点执行异常", exception);
throw new AbilityExtensionException(UnifiedErrorSpec.BasisOrder.INTEGRAL_OCCUPY_FAIL, exception);
} finally {
Profiler.registerInfoEnd(callerInfo);
}
}
}

前期:清楚能力所负责的业务逻辑,针对业务逻辑清楚的了解哪些需要扩展,但为了适配后续业务,不清楚能不能扩展。(交易订单域处在当前状态,正在向下一阶段演化。)

方案建议****:针对业务逻辑进行合理的拆分,粗暴些的方式,可以把能力内的逻辑拆分为前中后逻辑,支持在能力核心逻辑的前后进行个性化逻辑扩展实现调用,入参传入全量领域上下文提供使用。好处在于扩展灵活,方便个性化逻辑开发。坏处在于不好管理,SPI丧失接口隔离原则。

示例****:

@Service
public class OccupyStockFlowNode extends AbstractDomainFlowNode {
private static final Logger LOGGER = LoggerFactory.getLogger(OccupyStockFlowNode.class);
/**
* 预占库存
*/
@Resource
private CreateOccupyStockAbility createOccupyStockAbility; @Resource
private CreateOccupyStockFrontAbility createOccupyStockFrontAbility; @Resource
private CreateOccupyStockPostAbility createOccupyStockPostAbility; /**
* 释放库存
*/
@Resource
private CreateOccupyStockErrorRollbackAbility createOccupyStockErrorRollbackAbility; @Resource
private AlarmUtil alarmUtil; /**
* @param
* @return
* @Description 预占库存能力流程节点
* @lastModify
*/
@Override
public BOutputMessage call(InputMessage inputMessage) {
CallerInfo info = Profiler.registerInfo(this.getClass().getName() + ".call"
, UmpKeyConstants.JDL_OMS_SUPPLYCHAIN_APP_CODE
, UmpKeyConstants.METHOD_ENABLE_HEART
, UmpKeyConstants.METHOD_ENABLE_TP); CallerInfo sectionInfo = null, buSectionInfo = null; try {
SupplyChainOrderContext context = (SupplyChainOrderContext) inputMessage.getBody();
BOutputMessage outputMessage = new BOutputMessage();
outputMessage.setBody(context); sectionInfo = Profiler.registerInfo(this.getClass().getName() + ".call.section." + ClobOrderUtils.getSectionString(context)
, UmpKeyConstants.JDL_OMS_SUPPLYCHAIN_APP_CODE
, UmpKeyConstants.METHOD_ENABLE_HEART
, UmpKeyConstants.METHOD_ENABLE_TP); buSectionInfo = Profiler.registerInfo(this.getClass().getName() + ".call.bu." + context.getBusinessIdentity().getBusinessUnit()
+ ".section." + ClobOrderUtils.getSectionString(context)
, UmpKeyConstants.JDL_OMS_SUPPLYCHAIN_APP_CODE
, UmpKeyConstants.METHOD_ENABLE_HEART
, UmpKeyConstants.METHOD_ENABLE_TP); //前置预占入参处理
createOccupyStockFrontAbility.execute(context, this); //库存预占
createOccupyStockAbility.execute(context, this); //后置预占结果处理
createOccupyStockPostAbility.execute(context, this);
outputMessage.setCallBackInterface(new BDomainFlowNodeCallBack() {
@Override
public void callBack() {
SmartPattern smartPattern = context.getSupplyChainOrderModel().getSmartPattern();
if (smartPattern.hasBeanOccupySuccess()) {
String msg = String.format("客户单号:%s,订单号:%s,接单或重新受理失败,回滚释放预占库存", context.getSupplyChainOrderModel().getChannel().getCustomerOrderNo(), context.getSupplyChainOrderModel().orderNo());
LOGGER.info(msg);
Profiler.businessAlarm(UmpKeyConstants.UMP_JDL_OMS_CREATE_PERSIST_FAIL_PDQ_RELEASE_STOCK_ALARM_MONITOR, msg);
createOccupyStockErrorRollbackAbility.execute(context, OccupyStockFlowNode.this);
} }
});
return outputMessage;
} catch (DomainAbilityException e) {
LOGGER.error("预占库存能力流程执行异常", e);
SupplyChainOrderContext context = (SupplyChainOrderContext) inputMessage.getBody();
alarmUtil.newAlarm().key(UmpKeyConstants.UMP_JDL_OMS_OCCUPY_STOCK_EXCEPTION_ALARM_MONITOR)
.model(context.getSupplyChainOrderModel())
.alarmMessage("预占库存能力流程执行异常code:%s,message:%s" , e.code(), e.getMessage())
.alarm();
throw e;
} catch (Exception e) {
Profiler.functionError(info);
if (sectionInfo != null) {
Profiler.functionError(sectionInfo);
}
if (buSectionInfo != null) {
Profiler.functionError(buSectionInfo);
}
LOGGER.error("预占库存能力流程执行异常", e);
throw e;
} finally {
Profiler.registerInfoEnd(info);
if (sectionInfo != null) {
Profiler.registerInfoEnd(sectionInfo);
}
if (buSectionInfo != null) {
Profiler.registerInfoEnd(buSectionInfo);
}
}
}
}
/**
* @Description: 接单库存预占前置活动
*/
@DomainAbility(name = "仓配领域能力-接单库存预占前置活动", parent = DomainConstants.SUPPLY_CHAIN_ORDER_DOMIAN_CODE)
@AbilityScene(businessScenes = {BusinessSceneEnum.CREATE}, isDefault = false)
public class CreateOccupyStockFrontAbility extends OccupyStockFrontAbility {
/**
* log
*/
private static final Logger LOGGER = LoggerFactory.getLogger(CreateOccupyStockFrontAbility.class); @Override
public void execute(SupplyChainOrderContext supplyChainOrderContext, BDomainFlowNode bDomainFlowNode) throws DomainAbilityException {
CallerInfo callerInfo = UmpUtils.getCallerInfo(this.getClass().getName() , "execute");
try {
//根据Batrix配置,获取扩展点实现
IOccupyFrontExtensionPlugin extension = this.getMiddleExtensionFast(IOccupyFrontExtensionPlugin.class, supplyChainOrderContext, SimpleReducer.listCollectOf(Objects::nonNull), bDomainFlowNode);
if (extension != null) {
String orderNo = StringUtils.isNotBlank(supplyChainOrderContext.getSupplyChainOrderModel().orderNo()) ? supplyChainOrderContext.getSupplyChainOrderModel().orderNo() : supplyChainOrderContext.getSupplyChainOrderModel().getChannel().getCustomerOrderNo();
IOccupyFrontRequest occupyFrontRequest = new OccupyFrontRequest();
occupyFrontRequest.createWith(supplyChainOrderContext.getSupplyChainOrderModel().getCargoDelegate());
occupyFrontRequest.getExtendProps().put("orderNo", orderNo);
if (LOGGER.isInfoEnabled()) {
LOGGER.info("单号:{},库存预占前置扩展插件请求入参:{}", orderNo, JSONUtils.beanToJSONDefault(occupyFrontRequest));
}
IOccupyFrontResponse iOccupyFrontResponse = extension.execute(supplyChainOrderContext.getSupplyChainOrderModel().requestProfile(), occupyFrontRequest);
if (LOGGER.isInfoEnabled()) {
LOGGER.info("单号:{},库存预占前置扩展插件返回结果:{}", orderNo, JSONUtils.beanToJSONDefault(iOccupyFrontResponse));
}
if (!iOccupyFrontResponse.isSuccess()) {
LOGGER.error("库存预占前置扩展插件异常", iOccupyFrontResponse.getThrowable());
throw new BusinessDomainException(UnifiedErrorSpec.BasisOrder.OCCUPY_STOCK_FAIL).withCustom(iOccupyFrontResponse.getMessage());
} IStockCargoDelegate iStockCargoDelegate = iOccupyFrontResponse.getStockCargoDelegate();
StockSupportModelCreator stockSupportModelCreator = new StockSupportModelCreator();
stockSupportModelCreator.setCargoInfos(toCargoInfoList(iStockCargoDelegate.getCargoList()));
Integer orderType = Integer.valueOf(supplyChainOrderContext.getSupplyChainOrderModel().getExtendProps().get(IsvExtendFieldNameEnum.ORDER_SELL_MODEL.getCode()));
stockSupportModelCreator.setOrderType(orderType);
stockSupportModelCreator.setBizType(BizTypeEnum.SOO.getCode());
supplyChainOrderContext.getStockSupportModel().complementStockCargoDelegate(this, stockSupportModelCreator);
supplyChainOrderContext.getStockSupportModel().complementOrderType(this, stockSupportModelCreator);
supplyChainOrderContext.getStockSupportModel().complementBizType(this, stockSupportModelCreator);
}
} catch (AbilityExtensionException e) {
LOGGER.error("仓配领域能力-库存预占前置活动能力执行异常: ", e);
throw e;
} catch (Exception e) {
Profiler.functionError(callerInfo);
LOGGER.error("仓配领域能力-库存预占前置活动能力执行异常: ", e);
throw new DomainAbilityException(UnifiedErrorSpec.BasisOrder.OCCUPY_STOCK_FAIL, e);
} finally {
Profiler.registerInfoEnd(callerInfo);
}
} @Override
public IOccupyFrontExtensionPlugin getDefaultExtension() {
return null;
}
}
/**
* @Description: 库存预占
*/
@DomainAbility(name = "仓配领域能力-接单库存预占活动", parent = DomainConstants.SUPPLY_CHAIN_ORDER_DOMIAN_CODE)
@AbilityScene(businessScenes = {BusinessSceneEnum.CREATE}, isDefault = false)
public class CreateOccupyStockAbility extends AbstractDomainAbility<SupplyChainOrderContext, IOccupyStockExtension> {
private static final Logger LOGGER = LoggerFactory.getLogger(CreateOccupyStockAbility.class); /**
* @Description 预占库存活动能力
*/
@Override
public void execute(SupplyChainOrderContext supplyChainOrderContext, BDomainFlowNode bDomainFlowNode) throws DomainAbilityException {
CallerInfo callerInfo = Profiler.registerInfo(this.getClass().getName() + ".execute"
, UmpKeyConstants.JDL_OMS_SUPPLYCHAIN_APP_CODE
, UmpKeyConstants.METHOD_ENABLE_HEART
, UmpKeyConstants.METHOD_ENABLE_TP); CallerInfo sectionInfo = Profiler.registerInfo(this.getClass().getName() + ".execute.section." + ClobOrderUtils.getSectionString(supplyChainOrderContext)
, UmpKeyConstants.JDL_OMS_SUPPLYCHAIN_APP_CODE
, UmpKeyConstants.METHOD_ENABLE_HEART
, UmpKeyConstants.METHOD_ENABLE_TP);
try {
//根据Batrix配置,获取扩展点实现
IOccupyStockExtension extension = this.getMiddleExtensionFast(IOccupyStockExtension.class,
supplyChainOrderContext,
SimpleReducer.listCollectOf(Objects::nonNull), bDomainFlowNode);
extension.execute(supplyChainOrderContext);
} catch (AbilityExtensionException e) {
LOGGER.error("仓配领域能力-接单库存预占活动能力执行异常: ", e);
throw e;
} catch (Exception e) {
Profiler.functionError(callerInfo);
Profiler.functionError(sectionInfo);
LOGGER.error("仓配领域能力-接单库存预占活动能力执行异常: ", e);
throw new DomainAbilityException(UnifiedErrorSpec.BasisOrder.OCCUPY_STOCK_FAIL, e);
} finally {
Profiler.registerInfoEnd(callerInfo);
Profiler.registerInfoEnd(sectionInfo);
}
}
}
/**
* @Description: 接单库存预占后置活动
*/
@DomainAbility(name = "仓配领域能力-接单库存预占后置活动", parent = DomainConstants.SUPPLY_CHAIN_ORDER_DOMIAN_CODE)
@AbilityScene(businessScenes = {BusinessSceneEnum.CREATE}, isDefault = false)
public class CreateOccupyStockPostAbility extends OccupyStockPostAbility { private static final Logger LOGGER = LoggerFactory.getLogger(CreateOccupyStockPostAbility.class); @Override
public void execute(SupplyChainOrderContext supplyChainOrderContext, BDomainFlowNode bDomainFlowNode) {
//根据Batrix配置,获取扩展点实现
IOccupyPostExtensionPlugin extension = this.getMiddleExtensionFast(IOccupyPostExtensionPlugin.class,
supplyChainOrderContext,
SimpleReducer.listCollectOf(Objects::nonNull), bDomainFlowNode);
if (extension != null) {
String orderNo = StringUtils.isNotBlank(supplyChainOrderContext.getSupplyChainOrderModel().orderNo())
? supplyChainOrderContext.getSupplyChainOrderModel().orderNo() : supplyChainOrderContext.getSupplyChainOrderModel().getChannel().getCustomerOrderNo();
//订单域货品信息
ICargoDelegate iCargoDelegate = supplyChainOrderContext.getSupplyChainOrderModel().getCargoDelegate();
//订单域发货仓
IWarehouse iWarehouse = supplyChainOrderContext.getSupplyChainOrderModel().getConsignor().getWarehouse();
//订单域智能策略
ISmartPattern iSmartPattern = supplyChainOrderContext.getSupplyChainOrderModel().getSmartPattern();
//库存域预占结果
Integer stockOccupyResult = supplyChainOrderContext.getStockSupportModel().getStockOccupyResult();
//库存域货品信息
IStockCargoDelegate iStockCargoDelegate = supplyChainOrderContext.getStockSupportModel().getStockCargoDelegate();
//库存域缺量信息
IStockShortDelegate iStockShortDelegate = supplyChainOrderContext.getStockSupportModel().getStockShortDelegate();
//预占库房类型
String occupyWarehouseType = supplyChainOrderContext.getStockSupportModel().getOccupyWarehouseType();
//库存质押结果
Boolean pledgeResult = supplyChainOrderContext.getStockSupportModel().getPledgeResult();
IOccupyPostRequest iOccupyPostRequest = new OccupyPostRequest();
iOccupyPostRequest.getExtendProps().put(BusinessConstants.OCCUPY_WAREHOUSE_TYPE, occupyWarehouseType);
iOccupyPostRequest.createWith(iCargoDelegate, iStockCargoDelegate, iStockShortDelegate, orderNo, stockOccupyResult, iSmartPattern, iWarehouse, pledgeResult);
if (LOGGER.isInfoEnabled()) {
LOGGER.info("单号:{},库存预占后置扩展插件请求入参:{}", orderNo, JSONUtils.beanToJSONDefault(iOccupyPostRequest));
}
IOccupyPostResponse iOccupyPostResponse = extension.execute(supplyChainOrderContext.getSupplyChainOrderModel().requestProfile(), iOccupyPostRequest);
if (LOGGER.isInfoEnabled()) {
LOGGER.info("单号:{},库存预占后置扩展插件返回结果:{}", orderNo, JSONUtils.beanToJSONDefault(iOccupyPostResponse));
}
if (!iOccupyPostResponse.isSuccess()) {
LOGGER.error("库存预占后置扩展插件异常", iOccupyPostResponse.getThrowable());
throw new BusinessDomainException(UnifiedErrorSpec.BasisOrder.OCCUPY_STOCK_FAIL).withCustom(iOccupyPostResponse.getMessage());
}
//预占结果:成功
if (OccupyResultEnum.SUCCESS.getCode().equals(iOccupyPostResponse.getOccupyResult())) {
//获取预占后处理的货品信息
ICargoDelegate iOrderCargoDelegate = iOccupyPostResponse.getCargoDelegate();
//根据预占后处理的货品信息回写订单域货品信息
assembleCargoOccupyStockNum(supplyChainOrderContext, iOrderCargoDelegate);
//回写订单域预占结果
assembleOccupyResult(supplyChainOrderContext, OccupyResultEnum.SUCCESS.getCode(), null);
LOGGER.info("单号:{},库存预占后置扩展插件,预占成功回写订单域预占结果和货品预占数量:{}", orderNo, JSONUtils.beanToJSONDefault(supplyChainOrderContext));
} else if (OccupyResultEnum.STOCK_SHORTAGE.getCode().equals(iOccupyPostResponse.getOccupyResult())) {
// 库存不足情况处理逻辑
LOGGER.info("单号:{},库存预占后置扩展插件,库存不足补全库存、异常支撑域", orderNo);
handleStockShortage(supplyChainOrderContext);
} else {
handleOtherException(supplyChainOrderContext, iOccupyPostResponse);
//预占结果:目标仓预占失败
LOGGER.info("单号:{},库存预占后置扩展插件,目标仓预占失败,兜底原仓预占", orderNo);
//清空计划库房(目标仓)
cleanPlanWarehouse(supplyChainOrderContext);
}
}
}
}

中期:清楚能力所负责的业务逻辑,针对业务逻辑清楚的了解哪些能扩展,哪些不能扩展,针对扩展逻辑所使用的参数字段,有合理的规划。(交易产品域处在当前阶段。)

方案建议:针对业务逻辑进行合理的拆分,精细化的方式,针对能力内的逻辑有详细设计,哪些逻辑能扩展,哪些逻辑必须执行都有规划,每个Extension方法的出参和入参都有设计。对Extension根据具体逻辑和使用场景,能够进行本地扩展点调用和远程扩展点调用等区分。

示例

/**
* 产品日历信息能力点
*/
@Slf4j
@Component
@AbilityNodeUnit(code = "productCalendarNode", name = "产品日历信息能力点")
public class ProductCalendarNode extends AbstractRequiredAbilityNode { /**
* 派送开始时间和结束时间的格式
*/
private static final String RECEIVE_TIME_FORMAT = "yyyy-MM-dd HH:mm";
/**
* 开始和结束时间的格式
*/
private static final String TIME_RANGE = "HH:mm"; @Autowired
private AddressStationAbility addressStationAbility;
@Autowired
private SliceDeliveryAcquirer sliceDeliveryAcquirer;
@Autowired
private AbilityCheckProcessor abilityCheckProcessor; @Override
public boolean paramCheck(InputMessage inputMessage) {
return true;
} @Override
public void acquireInfo(InputMessage inputMessage) throws Exception {
ProductRequestDto productRequestDto = acquireProductRequestDto(inputMessage);
List<ProductDto> needCheckProductDtoList = new ArrayList<>();
try {
//校验逻辑
if (productRequestDto.getRequestType() == ProductRequestDto.CHECK_REQUEST_TYPE) {
//获取需要校验此能力点的产品列表
needCheckProductDtoList = matchAbilityCode(inputMessage);
}
//供给逻辑
if (productRequestDto.getRequestType() == ProductRequestDto.SUPPLY_REQUEST_TYPE) {
//校验所有配置了预约派送日历的产品
needCheckProductDtoList = matchAbilityCodeIgnoreStatus(inputMessage);
}
log.info("[Node][日历信息能力点]ProductCalendarNode.acquireInfo(),checkProducts:{}", abilityCheckProcessor.getProductNosStr(needCheckProductDtoList));
if (!CollectionUtils.isEmpty(needCheckProductDtoList)) {
//从扩展点返回结果中获取站点信息完成
addressStationAbility.getAddressStationInfo(inputMessage, this);
//从路由接口获取日历信息
sliceDeliveryAcquirer.acquire(needCheckProductDtoList, productRequestDto);
}
} catch (Exception e) {
log.info("[Node][日历信息能力点]ProductCalendarNode.acquireInfo() exception msg:" + e.getMessage(), e);
}
}
}
/**
* 地址和站点信息获取能力点
* @date 2022/12/5
*/
@Slf4j
@Component
public class AddressStationAbility extends BBaseDomainAbility<DomainModel, AddressStationCheckService> { @Autowired
private CalendarAbilityProcessor calendarAbilityProcessor; @Override
public AddressStationCheckService getDefaultExtension() {
return null;
} public void getAddressStationInfo(InputMessage inputMessage, BDomainFlowNode bDomainFlowNode) {
BDomainModel bDomainModel = (BDomainModel) inputMessage.getBody();
ProductRequestDto productRequestDto = (ProductRequestDto) bDomainModel;
//获取Batrix扩展点调用,实际为远程扩展点
AddressStationCheckService addressStationCheckService = getMiddleExtensionFast(AddressStationCheckService.class, bDomainModel,
SimpleReducer.firstOf((String f) -> f != null), bDomainFlowNode);
//从扩展点返回结果中获取站点信息完成
if (addressStationCheckService != null) {
AddressStationCheckServiceAdapter addressStationCheckServiceAdapter = new AddressStationCheckServiceAdapter(addressStationCheckService);
//调用扩展点
AddressStationCheckResponse addressStationCheckResponse = addressStationCheckServiceAdapter.check(DtoUtils.createRequestProfile(productRequestDto),
calendarAbilityProcessor.createAddressStationCheckRequest(productRequestDto), productRequestDto);
//解析出参,赋值到ProductRequestDto
calendarAbilityProcessor.parseAddressStationCheckResponse(addressStationCheckResponse, productRequestDto);
}
}
}
/**
* 从路由接口获取日历切片
*/
@Slf4j
@Component
public class SliceDeliveryAcquirer { @Autowired
private CalendarAbilityProcessor calendarAbilityProcessor; /**
* 获取日历切片信息
*/
public void acquire(List<ProductDto> productDtoList, ProductRequestDto productRequestDto) {
boolean getDeliveryInfo = productRequestDto.getEndStationDto().getSupportDaytimeDelivery() != null && productRequestDto.getEndStationDto().getSupportNightDelivery() != null;
if (!getDeliveryInfo) {
log.error("[Ability][日历信息校验]ProductCalendarCheckAbility.doAcquireInfo(),从扩展点获取站点配送能力失败,停止获取路由日历信息");
return;
}
calendarAbilityProcessor.acquireSliceDelivery(productRequestDto, productDtoList);
}
}
/**
* 能力校验逻辑
*/
@Component
@Slf4j
public class AbilityCheckProcessor {
/**
* 根据业务身份判断校验项
*/
@Autowired
private ProductMarkConstant productMarkConstant; /**
* 验证该产品是否需要校验此能力点
* @return true:校验。false:不校验
*/
public boolean check(ProductRequestDto productRequestDto, ProductDto productDto, String abilityNo) {
if (productDto == null) {
return false;
}
if (StringUtils.isBlank(productDto.getCode())) {
return true;
}
if (!ProductValidResMsgEnum.SUCCESS.getCode().equals(productDto.getCode())) {
return false;
}
return productMarkConstant.check(productRequestDto.getBusinessUnitEnum(), productDto.getProductNo(), abilityNo);
} /**
* 验证该产品主增嵌套关系是否需要校验此能力点,返回所有需要校验的ProductDto
*/
public List<ProductDto> check(ProductRequestDto productRequestDto, ProductResultDto productResultDto, String abilityNo) {
List<ProductDto> result = new ArrayList<>();
// 增值产品如果主产品校验失败,下面的增值产品不用校验。终端揽收不走此逻辑
if (!ProductValidResMsgEnum.SUCCESS.getCode().equals(productResultDto.getMainProductDto().getCode())) {
if (!BusinessSceneEnum.COLLECT.getCode().equals(productRequestDto.getBusinessIdentity().getBusinessScene())) {
return result;
}
}
if (check(productRequestDto, productResultDto.getMainProductDto(), abilityNo)) {
result.add(productResultDto.getMainProductDto());
}
for (ProductDto addValDto : productResultDto.getValueAddedProducts().values()) {
if (check(productRequestDto, addValDto, abilityNo)) {
result.add(addValDto);
}
}
return result;
}
}

后期:能力本身逻辑、健壮性等都处在较高水平,但能力的使用场景过多,且差异无法抽象统一到一个能力内。

方案建议:不用强求把所有需求集合到一个能力内,成本和复杂度过高。可以把一个业务能力做拆分,提供2-3个不同模式的技术能力出去,业务使用方根据需要,进一步选择扩展,融入到业务里边。使用2-3个不同的能力模式,覆盖85%-95%的业务场景,如果依然有极个别的业务无法使用,那针对业务开发个性化能力支持。

如何复用

能力的使用,分几个层次,相关优先级如下:

当需求提出后,经过分析,现有标准能力是否满足诉求。同一个能力可以有2-3中模式,其中某一个模式满足需求的话,就可以提供使用。

现有标准能力不满足的情况下,评估使用扩展能力是否满足诉求,从标准能力的不同模式中,选一个最接近需求的进行扩展。

扩展能力本质上是标准能力和扩展点的组合,标准能力负责通用逻辑的执行,扩展点负责个性化逻辑的执行。业务系统基于稳定性、扩展性的方便考虑,还可以使用远程扩展点,把扩展逻辑放到独立的服务上运行。

如果扩展能力依旧不满足需求的话,可以新建能力。

如果分析后发现是能力缺失,则新建标准能力,以备后续业务复用。

如果分析后发现是小场景需求,可以针对该需求新建自定义能力,只针对该业务场景使用。

确定好使用哪些能力后,可以使用Batrix提供的控制台-神行平台,进行能力的流程编排,通过能力的编排组合最终形成满足业务需要的逻辑。如下:

该流程为中小件isv-销售出-接单流程,由交易订单域承接,通过流程编排满足中小件ISV的销售出库业务接单逻辑。可以通过对Batrix框架同异步双引擎模式的使用,控制能力的同步执行和异步执行,满足业务对性能和异步履约调度的不同诉求。

随着业务需求的版本迭代,能力也会进行更新替换。如果能力不满足新的业务使用,则需要对能力进行开发,或者建立新的能力编排进去,替换原有逻辑。如果业务已经没有使用方,没有调用量了,则可以删除流程,进而删除能力,保障系统工程的长期完整、稳定,降低腐蚀

企业能力共享

当前能力的复用,也仅限于能力所属的业务域系统。承接需求的吞吐速度取决于能力的Owner的支持速度,但随着业务复杂度提高,各BP部门需求吞集中到中台部门。中台资源有限,BP基于可复用的中台能力实现扩展点,可抵消一部分对中台资源的依赖。但对能力上的需求,只能采用工程共建的方式解决,长期来讲存在很多隐患,功能无法解耦,系统稳定性无法保证。全部依赖中台开发能力的话,中台资源可能会成为交付瓶颈。

所以Batrix企业能力库支持了能力共享模式,主要为解决如下问题:

通过能力共享模式,进一步解决资源瓶颈问题。

通过流程编排按需加载,加速业务交付效率。

基于能力共享模式,前中台协同方式有所调整:

中台研发:开发可复用、稳定的能力,设计扩展点插槽,发布能力。

BP研发:基于业务需求,选择能力进行业务流程编排,通过扩展点实现,补全中台能力不满足需求的部分。如果中台能力不满足业务需要,BP可自建能力进行流程编排运行。

业务应用:业务Owner,通过流程编排把不同能力运行在自己的系统中,负责对外业务的对接维护。中台提供能力的稳定性和复用度建设,维护内部的稳定性,最终提高业务的交付效率。

调整后交易订单系统架构如下:

应用架构设计,通过中台流量中间件统一收口对外业务请求,通过中台数据中间件统一收口对内数据处理,中间业务应用容器处理一部分适配逻辑,通过Batrix框架进行共享能力调用。

中台流量中间件:接收业务流量渠道的请求,根据分流属性 ,把请求路由到不同的业务系统中。

业务应用容器:

通过REMOTE层适配对外接口标准

通过LOCAL层处理转化对领域服务模型

通过BATRIX层运行业务能力,按流程编排运行,满足业务诉求。

中台数据中间件:处理数据操作请求。

辅助系统:通过配置中心、监控中心、安全中心辅助系统运行,加速交付效率,提升系统稳定性。

能力共享并不是适用于所有业务和所有能力,他的使用门槛较高,对能力的建设者和业务的使用者都有一些要求。

能力的建设者要求:

如何保证提供的能力稳定、高效。

如何保持必要的基本开发原则。

能力版本迭代的时候领域上下文如何管理,和现有业务如何兼容。

能力内涉及到下游接口、中间件、数据存储服务是共用一套,还是隔离开。

业务的使用者要求:

如何清晰的知晓所使用的能力内部逻辑,以便运维自己的业务。

业务异常的情况下,如何快速定位问题点并予以修复。

业务逻辑开发调试起来体验较差。

能力治理

能力开发完成后,如果不进行管理,就容易出现能力爆炸。大家都去新建能力,但不进行优化沉淀,依然达不到效果。

根据不同阶段,能力的生命周期如下:

需求承接后可以创建新的能力进行逻辑开发。

创建能力后需要进行不断的沉淀优化,具备稳定的服务后才能对外发布。

能力开发完成后可以进行流程编排,提供业务运行使用。

随着时间推移和版本迭代,能力本身会存在很多冗余代码,需要对能力本身进行周期性的沉淀优化,保障能力的稳定性,承接更多的业务。

如果长期看能力不能在进行优化了,那需要对能力本身废弃,生成新的能力提供服务。

旧能力下线,需要在对应的使用流程中进行删除,删除对能力有依赖的业务流程引用,最后删除相关代码。

如何判断能力开发的好坏,哪些需要优化,哪些需要清除,逻辑是否能够通用?

当前Batrix能力治理提供几个维度衡量:

调用度:标准能力被流程使用的次数(由标准能力创建出来的扩展能力也算标准能力的使用),复用度越高,表明能力支持的业务越多。

业务身份覆盖度:使用能力的的业务身份数量。

扩展度:由标准能力创建出来的扩展能力数量,该指标需要和复用度综合来看。复用度越高,扩展度约低,表明能力的通用性越高,不用进行扩展,就可以支持多业务条线。反之,复用度越低,扩展度越高,表明能力的通用逻辑不够支持业务诉求,需要进行大量的扩展,才能满足。

运行度:能力运行次数的量级,运行的次数量级越大,表明支持的业务体量越大,能力越有价值。

稳定性:能力代码的变动频率,变动量越低,说明能力越成熟,越稳定。变动量越高,说明能力不成熟,需要频繁变动才能满足业务。

易用性:是否有文档能够描述清楚能力的逻辑。业务系统能不能方便的对能力进行扩展。开发调试业务的过程中,能力是否能够准确表达业务语义。

其他待完善

通过以上多个方面综合评估,可以对能力是否够好,有一个初步了解。针对有问题的能力,可以具体分析原因,进一步进行优化。度量指标目前只考量复用度和扩展度,下一阶段会对后续标准进行分析。

当前Batrix企业能力库已有标准一级业务域共2个,二级业务域4个,非标准业务域4个。共有标准能力520个,其中:

综合评分超过200的能力2个。

综合评分超过100的能力16个。

业务身份覆盖度超15的能力有29个。

调用度超50的能力有11个。

调用度超10的能力有110个。

扩展度超50的能力有4个。

扩展度超10的能力有29个。

最佳实践

什么能力是好能力,很难给出准确的标准,以可度量的综合评分维度举例最佳实践:

1、订单域-纯配基本信息校验:

标准能力名称:基本信息校验

负责团队:交易平台组-订单中心

综合评分:235.3

业务身份覆盖度:19

调用度:126

扩展度:55

运行度:5

说明:纯配基本信息校验能力负责交易订单纯配业务的基本校验逻辑,从设计角度触发,所有条线业务都需要进行最基本的数据校验处理,只是或多或少、复杂度的问题。能力边界清晰,职责明确,代码结构简洁。个性化逻辑可以通过扩展点处理,基于不同诉求,共有55个不同的扩展点实现,最终满足业务逻辑的全量支持。

2、产品域-产品主增关系能力点:

标准能力名称:产品主增关系能力点

负责团队:交易平台组-产品中心

综合评分:138

业务身份覆盖度:32

调用度:36

扩展度:0

运行度:4

说明:产品主增关系能力负责产品域主产品和增值产品相关逻辑计算,为产品供给校验接口必选能力,所有业务逻辑必须执行。统一了各个业务线的产品主增模型结构,是产品域核心能力。能力边界清晰,职责明确。代码结构简洁,当前不支持扩展逻辑,如果后续如果有扩展需求,可以直接将代码重构成Abiliey和Extension的组合,支持个性化扩展逻辑使用。

结语

能力建设是中长期的事情,相对来讲难以量化,作为技术中台架构升级战略项目的相关系统,我们的能力建设成果,希望通过业务的口碑,通过BP的使用及落地效果,对业务战略和规划的影响力,用技术驱动导致的业务线损益呈现来衡量。

本文通过交易域能力建设的实践,总结能力建设方法,为大家建设自己能力提供借鉴意义。

作者:京东物流 史建刚

来源:京东云开发者社区 自猿其说Tech 转载请注明来源

Batrix企业能力库之物流交易域能力建设实践的更多相关文章

  1. 不要向没权力&能力的人证明自己的能力

    [不要向没权力&能力的人证明自己的能力] 不是所有的上级都有足够的权力和能力.一个没权力的Leader,即使你向他证明了自己的能力,你所能获得的也只能是他的邮件表扬的荣誉.对于加薪,他能给的仅 ...

  2. 【SSO单点系列】(2):CAS4.0 之 跨域 Ajax 登录实践

    CAS4.0 之 跨域 Ajax 登录实践 一.问题描述 CAS实现单点 实现一处登录 可访问多个应用 . 但是原登录是CAS默认登录页面和登出页面是无法重定向到自定义页面的   此处使用Ajax+I ...

  3. 【TP3.2】跨库操作和跨域操作

    一.跨库操作:(同一服务器,不同的数据库) 假设UserModel对应的数据表在数据库user下面,而InfoModel对应的数据表在数据库info下面,那么我们只需要进行下面的设置即可. class ...

  4. IM 融云 之 通讯能力库API

    参考:http://www.rongcloud.cn/docs/api/ios/imlib/Classes/RCConversation.html 0. 使用说明 使用该文档注意,每个蓝色标题点击后, ...

  5. Android事件总线分发库EventBus3.0的简单讲解与实践

    Android事件总线分发库EventBus的简单讲解与实践 导语,EventBus大家应该不陌生,EventBus是一款针对Android优化的发布/订阅事件总线.主要功能是替代Intent,Han ...

  6. AD域安全攻防实践(附攻防矩阵图)

    以域控为基础架构,通过域控实现对用户和计算机资源的统一管理,带来便利的同时也成为了最受攻击者重点攻击的集权系统. 01.攻击篇 针对域控的攻击技术,在Windows通用攻击技术的基础上自成一套技术体系 ...

  7. 人工智能AI库Spleeter免费人声和背景音乐分离实践(Python3.10)

    在视频剪辑工作中,假设我们拿到了一段电影或者电视剧素材,如果直接在剪辑的视频中播放可能会遭遇版权问题,大部分情况需要分离其中的人声和背景音乐,随后替换背景音乐进行二次创作,人工智能AI库Spleete ...

  8. HDU 2178.猜数字【分析能力练习】【读题能力练习】【8月10】

    猜数字 Problem Description A有1数m.B来猜.B每猜一次,A就说"太大"."太小"或"对了" . 问B猜n次能够猜到的 ...

  9. iOS消息体系架构详解-融云即时通讯云

    iOS SDK 体系架构 本文档将详细介绍融云的 SDK 产品架构和消息体系,以便于您更深入的了解融云并更快速的开发自己的产品. 融云 SDK 系统架构 IMKit IMKit 的功能主要是封装各种界 ...

  10. 企业架构研究总结(38)——TOGAF架构能力框架之架构能力建设和架构治理

    为了确保架构功能在企业中能够被成功地运用,企业需要通过建立适当的组织结构.流程.角色.责任和技能来实现其自身的企业架构能力,而这也正是TOGAF的架构能力框架(Architecture Capabil ...

随机推荐

  1. 其实webpack编译"模块化"的源码没那么难

    我们在 webpack初体验 这篇文章中演示到,浏览器不支持 CommonJS ,在特定场景下才支持 Es Module ,而 webpack 可以将这些模块化的代码解析成浏览器可识别的语法. 那么 ...

  2. vue3 + ElementPlus 封装函数式弹窗组件

    需求场景:弹窗组件需要支持自定义的插槽内容,删除的弹窗也要使用这个组件,只是样式不一样而已,希望在父组件使用删除弹窗的时候直接调用某个方法就可以显示弹窗 组件模拟 cuDialog 假设我的弹窗组件有 ...

  3. 【Unity3D】调整屏幕亮度、饱和度、对比度

    1 屏幕后处理流程 ​ 调整屏幕亮度.饱和度.对比度,需要使用到屏幕后处理技术.因此,本文将先介绍屏幕后处理流程,再介绍调整屏幕亮度.饱和度.对比度的实现. ​ 本文完整资源见→Unity3D调整屏幕 ...

  4. Java日志系列:Log4j使用和原理分析

    目录 一.简介 二.使用 三.日志级别 四.组件说明 Loggers Appenders Layouts 五.配置 加载初始化配置 配置文件加载 查看日志记录器的详细信息 六.Layout的格式 七. ...

  5. Zimbra禁止接收带有加密的文件邮件 提醒病毒(Heuristics.Encrypted.PDF)

    最近碰到一个国际性大客户,一定要发送经过加密的文件,因为是合约相关的文件,对方公司有这方面要求.但是Zimbra默认是禁止接收加密的文件 - 'Block encrypted archives',这样 ...

  6. 【测试】自定义配置 RocksDB 进行 YCSB 测试

    目录 简介 编译 RocksDB 编译 YCSB 修复报错 自定义配置 RocksDB 进行 YCSB 测试 参考资料 本文主要记录在利用 YCSB 使用配置文件测试 RocksDB 的过程中遇到的一 ...

  7. 《SQL与数据库基础》06. 函数

    目录 函数 字符串函数 数值函数 日期函数 流程函数 本文以 MySQL 为例 函数 函数是指一段可以直接被另一段程序调用的程序或代码. 要查看函数操作的结果,可以使用 SELECT 函数(参数); ...

  8. 如何调用API接口获取商品数据

    在当今数字化的时代,电子商务的崛起使得网购成为了人们生活中不可或缺的一部分.作为电子商务中最为熟知和流行的平台之一,拥有大量的商品资源和用户群体.如果你是一名开发者或者是对数据分析感兴趣的人,你可能会 ...

  9. CodeForces 1343E Weights Distributing

    题意 多组样例 给定\(n,m,a,b,c\),给定一个长度为\(m\)的数组\(p[]\),给定\(m\)条边,构成一个\(n\)个点\(m\)条边的无向图,\(Mike\)想要从\(a\)走到\( ...

  10. 地表最帅缓存Caffeine

    简介 缓存是程序员们绕不开的话题,像是常用的本地缓存Guava,分布式缓存Redis等,是提供高性能服务的基础.今天敬姐带大家一起认识一个更高效的本地缓存--Caffeine. Caffeine Ca ...