目录
这篇文章的目的在于,详细介绍,如何 通过代码重构,达到代码简洁,语义可读性强的目的,穿插 设计类、接口、抽象类、以及相关命名的思想,以期能够生动形象的揭示出面向对象的思想。
先来看设计混乱,功能区分不明确的代码:
/**
* 数据发送接口
*/
public interface IntfDataApi {
/** 版本号:1.0 */
String VERSION_1_0 = "1.0";
/**
* 获取服务码
*/
String getServiceCode();
/**
* 获取版本号
*/
String getVersion();
/**
* 获取主键ID
*/
Long getId(String key);
/**
* 数据发送
* @param transferType 调用方式, 同步/异步
* @param paramMap 接口调用参数集合
*/
ResultVO send(String userId, String warehouseCode, IntfDataTransferTypeEnum transferType, LinkedHashMap<String, Object> paramMap);
/**
* 同步调用, 并记录调用日志
* @param isolation 是否事务隔离 (非隔离, 当发送实现出现异常时, 异步补偿数据无法记录)
* @param isSync 是否同步处理,非同步则将消息分发至WindQ进行处理(实现sendWindQ方法)
*/
ResultVO syncSend(IntfDataHead head, boolean isolation, boolean isSync);
/**
* 接口调用
*/
ResultVO transfer(IntfDataHead head);
/**
* 回调
*/
ResultVO callback(boolean isSuccess, Object obj);
/**
* 获取待发送数据
* <p>
* 3N次方分钟前, N=失败次数:
* 3(1), 9(2), 27(3), 81(4), 243(5), 729(6), 2187(7)
* </p>
*/
List<IntfDataHead> getPendingData(String serviceCode, int page, int pageSize, int failLimit , String checkedWarehouseCode);
/**
* 获取待发送抬头数据
* <p>
* 默认已实现, 根据业务不同, 自行重写
* </p>
*/
List<IntfDataHead> getIntfDataHeads(String warehouseCode, String serviceCode, int currPage, int pageSize, int failLimit);
/**
* 分页查询数据
* <p>
* 默认已实现, 根据业务不同, 自行重写
* </p>
*/
PageBean<Map<String, Object>> queryByPage(String warehouseCode, Map<String, Object> params, int page, int pageSize);
/**
* 条件查询数据集合
* <p>
* 默认已实现, 根据业务不同, 自行重写
* </p>
*/
List<IntfDataHead> queryListByParam(String warehouseCode, Map<String, Object> params);
/**
* 查head表和param表
*/
List<IntfDataHead> queryListById(String warehouseCode, String serviceCode, List idList);
/**
* 添加抬头信息
* <p>
* 默认已实现, 根据业务不同, 自行重写
* </p>
*/
boolean insertHead(IntfDataHead head);
/**
* 更新抬头
* <p>
* 默认已实现, 根据业务不同, 自行重写
* </p>
*/
boolean modifyHead(IntfDataHead head);
int updateHead(IntfDataHead head);
/**
* 构建数据发送抬头对象, 包含参数集合
*/
IntfDataHead generateIntfDataHead(String userId, String warehouseCode, LinkedHashMap<String, Object> paramMap);
/**
* 发送WindQ
*/
ResultVO sendWindQ(IntfDataHead head);
}
看到这个接口,第一反应就是多而乱,不该这个接口拥有的功能方法都被集成到该接口中。比如:
与要发送的数据有关的数据库操作(查询,更新,插入)及生成发送数据的抬头操作。
这个接口是发送接口,是一个顶级接口,只需要向外界提供send方法即可,使得接口的定义不仅简洁而且功能清晰。更重要的是,该顶级接口的设计,向外界屏蔽了具体实现细节。
下面是我改造的这个接口的一部分代码:
/**
* 发送数据,统一入口
*
* @author: 18109115
* @since: 1.0
* @see [相关类/方法](可选)
*/
public interface TransmitData {
String MODULE = "发送数据";
/**
* 数据发送
* @param dataPacket 数据包裹
* userId 数据用户
* warehouseCode 数据地点
* transmitter 数据发送方,此定义要具有业务属性说明,唯一
* transmitDataWay 发送数据方式 sync, job, windq
* dataParam 实际要发送的数据
*/
ResultVO send(DataPacket dataPacket);
}
到这,我们只是完成了万里长征的第一步,功能方法的剥离。被剥离的并不代表不重要,及可以被舍弃。接口的定义很重要,剥离是手段跟方法,是针对接口的定义而进行的,剥离的是与接口定义无关的功能跟方法。
本真实案例中,这个接口明确写了自己设计该接口的目的:数据发送接口
。这就是接口的语义边界内的定义。因此,我们在重新设计这接口,定义该接口时,为了做到语义清晰,代码简洁明了,定义了上述接口。【敲重点,接口代码语义越简洁,越能设计出面向使用者的代码——屏蔽具体实现的思想,后文中,也针对该思想做了详细的阐述,用三个发送的例子】
我们再回到,上文中提到过的,原来的数据发送接口IntfDataApi
中,总共提到了三个发送方法:
/**
* 数据发送
* @param transferType 调用方式, 同步/异步
* @param paramMap 接口调用参数集合
*/
ResultVO send(String userId, String warehouseCode, IntfDataTransferTypeEnum transferType, LinkedHashMap<String, Object> paramMap);
/**
* 同步调用, 并记录调用日志
* @param isolation 是否事务隔离 (非隔离, 当发送实现出现异常时, 异步补偿数据无法记录)
* @param isSync 是否同步处理,非同步则将消息分发至WindQ进行处理(实现sendWindQ方法)
*/
ResultVO syncSend(IntfDataHead head, boolean isolation, boolean isSync);
/**
* 发送WindQ
*/
ResultVO sendWindQ(IntfDataHead head);
乍一看,这三个方法基本相互独立,毕竟,有详细方法使用的注释说明,但是,如果仔细看的话,在到底使用哪个方法之前还是有困惑。
比如困惑一:send可以同步异步发送,sync同步发送,事物是否隔离,windq发送消息,也可以认为是异步。我们选异步发送,到底是send异步呢还是windq消息异步发送?显然,这些方法的提供有模糊性。
为了抉择,我们到底该使用哪种方法,我们需要查看这几个方法的具体实现,无法做到向使用者屏蔽底层实现的思想(敲重点,通俗点说,无法做到让使用者不去关心方法的具体实现,这是对屏蔽底层实现思想的解读),因为使用者需要去看他们的实现来抉择选择哪个异步发送方法合适。
看该原接口的实现,这三个向外提供的方法关联度很高。原接口的实现代码如下:
/**
* 数据发送接口抽象服务
*/
public abstract class AbstractIntfDataService extends BaseServiceImpl implements IntfDataApi {
/**
* 我们通过send方法的具体实现,才知道,我们在调用数据统一发送接口时,什么场景下该选择
* send方法。
* 同步,方法直接调用
* 异步,向数据库插入发送数据
*/
@Override
public ResultVO send(String userId, String warehouseCode, IntfDataTransferTypeEnum transferType, LinkedHashMap<String, Object> paramMap) {
if (transferType == null) {
return this.generateResult(false, "请求参数校验失败");
}
ResultVO result = null;
// 构建、记录数据发送抬头对象
IntfDataHead head = this.generateIntfDataHead(userId, warehouseCode, paramMap);
// 数据入库
if (!intfDataService.insertHead(head)) {
logger.error("[接口数据发送调用] userId={}, warehouseCode={}, transferType={}, paramMap={}, 发送记录入库失败", userId, warehouseCode, transferType, paramMap);
result = this.generateResult(false, "发送记录入库失败");
}
// 同步调用, 调用同步调用接口, 并记录返回结果
if (IntfDataTransferTypeEnum.SYNC.equals(transferType)) {
// 顶级接口中,方法sync 与 方法syncSend的关联。【不合理的设计1】
result = this.syncSend(head, false, true);
}
// 无结果信息, 则异步成功
if (result == null) {
result = this.generateResult(true, "异步调用, 直接返回成功");
}
result.setObject(head);
return result;
}
/**
* 我们也是看了该接口的实现,才知道什么场景使用它
* 数据同步处理
* 1.数据头添加服务码
* 2.调用数据头前置处理
* 3.异步处理发送WindQ 同步处理
* 4.调用处理接口 (transfer())
* 5.标记处理结果
* */
@Override
public ResultVO syncSend(IntfDataHead head, boolean isolation, boolean isSync) {
if (head == null) {
return null;
}
// 重置服务码
head.setServiceCode(this.getServiceCode());
// 前置设置
this.beforeSet(head);
// 非windq调用, 调用分发接口, 若接口未实现则继续同步逻辑
if (!isSync) {
ResultVO result = this.sendWindQ(head);
if (result != null) {
result.setAsync(true);
return result;
}
}
// 记录调用开始时间
head.setTransferTime(new Timestamp(System.currentTimeMillis()));
// 接口调用
ResultVO result = null;
try {
if (isolation) {
// Spring事物隔离
result = transactionIsolationService.transferNoTran(this, head);
} else {
// 直接调用
result = this.transfer(head);
}
} catch (Exception e) {
logger.error("[接口数据发送] head={}, 接口同步调用异常, {}", head, e.getMessage(), e);
// String msg = StringUtils.isBlank(e.getMessage()) ? null
// : e.getMessage().length() <= 256 ? e.getMessage()
// : e.getMessage().substring(e.getMessage().length() - 256);
//result = generateResult(false, msg);
}
// 设置调用结果
if (result != null && result.isSuccess()) {
// 若之前已发送成功又发送的, 则标记一次失败
if (null != head.getSendStatus() && IntfDataSendStatusEnum.SUCCESS.getValue() == head.getSendStatus()) {
head.setFailNum(head.getFailNum() == null ? 0 : head.getFailNum() + 1);
}
head.setSendStatus(IntfDataSendStatusEnum.SUCCESS.getValue());
} else {
head.setSendStatus(IntfDataSendStatusEnum.FAIL.getValue());
head.setFailNum(head.getFailNum() == null ? 0 : head.getFailNum() + 1);
}
// 记录响应时长
head.setResponseTime(new Timestamp(System.currentTimeMillis()));
head.setConsumeTime(head.getResponseTime().getTime() - head.getTransferTime().getTime());
// 更新接口抬头信息
head.setGmtModifiedBy(head.getGmtCreateBy());
boolean dbResult = this.modifyHead(head);
logger.debug("[接口数据发送] head={}, result={}, 更新接口抬头信息", head, dbResult);
if (result == null) {
result = this.generateResult(true, "接口调用返回空(null), 则标记成功");
}
return result;
}
/**
* sendWindQ 默认实现 【这个也是设计的一诟病,后续我们会看到,如何优化】
* 交给具体的数据发送子类重写Override该发送方法
* 子类是调用windq发送消息
*/
@Override
public ResultVO sendWindQ(IntfDataHead head) {
return null;
}
}
总结一下,代码混乱。(这也是面向对象语言造成的,很随意,以致使用者掌握不了语义清晰。)【这一段针对上面的总结过于宽泛,后续继续,斟酌语言…】
各位小伙伴,接下来,让大家评审一下我改造后的代码。我会介绍在改造的过程中的一些思维历程,以便大家能够更好的掌握面向对象,语义清晰,代码简洁,这些抽象术语。
我们定义了顶级接口TransmitData
即与原来的顶级接口IntfDataApi
对标,接下来就是实现该顶级接口,代码如下:
/**
* 发送数据的基本实现
*
* @author: 18109115
* @since: 1.0
* @see [相关类/方法](可选)
*/
public abstract class AbstractTransmitData implements TransmitData {
@Override
public final ResultVO send(DataPacket dataPacket) {
// 1. 数据包裹参数校验
checkNull(dataPacket);
// 2. 构建、记录数据发送抬头对象
IntfDataHead dataHead = generateHead(dataPacket);
// 3. 抬头数据入库
if (!insertHead(dataHead)) {
log.error("【{}——{}】 userId={}, warehouseCode={}, transmitWay={}, paramMap={}, 发送记录入库失败",
MODULE, dataPacket.getTransmitter(), dataPacket.getUserId(),
dataPacket.getWarehouseCode(), dataPacket.getTransmitDataWay(),
dataPacket.getDataParam());
return generateResult(false, "发送记录入库失败");
}
ResultVO result;
// 4. 发送方式
switch (dataPacket.getTransmitDataWay()) {
case SYNC:
result = sync(dataHead);
// TODO 接口调用 状态记录修改
break;
case JOB:
result = job(dataHead);
break;
case WINDQ:
result = windq(dataHead);
break;
default:
throw new JwmsException("未知的发送数据方式");
}
// todo
result.setObject(dataHead);
return result;
}
}
在自己的实现中,我们定义了三种发送方式:同步发送,job异步发送以及windq异步发送。分别对应原来的同步sync调用和异步windq调用,job调用是我新增的一种发送方式。
到这里我们一个最为大家熟悉的设计就是,将这三种发送方法在抽象接口abstract化,供子类实现。但是,这种最简单的实现方式,有一个缺点,就是子类要么全部实现这三个抽象方法,要么再提供一个支持子类,实现这三个抽象方法,业务子类继承这个支持子类。
第二种方案,明显让设计稍微复杂化,我说的复杂化,指的是语义上的复杂化。这里,我提供一种更为语义简单化的设计方案,针对第二种的,利用jdk1.8新提供的语义default。这样,我们不需要用一个支持子类来解决必须自己实现三种发送方法。
/**
* 发送数据方式,支持sync,job,windq 三种发送方式
*
* @author: 18109115
* @since: 1.0
* @see [相关类/方法](可选)
*/
public interface TransmitDataWay extends TransmitData {
/**
* 同步发送数据,放到这原因是,子类只需要按需实现这三种方式的一种或多种场景的多种方式
* 不允许子类单独调用
*/
default ResultVO sync(IntfDataHead dataHead) { return null; }
/**
* job发送数据
*/
default ResultVO job(IntfDataHead dataHead) { return null; }
/**
* windq 发送数据
*/
default ResultVO windq(IntfDataHead dataHead) { return null; }
}
设计完这个发送方式接口后,原来的抽象类实现的就是这个发送方法接口。我们不再需要一个支持子类来让业务子类达到必须实现三种发送方法的目的。
/**
* 发送数据的基本实现
*
* @author: 18109115
* @since: 1.0
* @see [相关类/方法](可选)
*/
@Slf4j
public abstract class AbstractTransmitData implements TransmitDataWay {
@Override
public final ResultVO send(DataPacket dataPacket) {
// 1. 数据包裹参数校验
checkNull(dataPacket);
// 2. 构建、记录数据发送抬头对象
IntfDataHead dataHead = generateHead(dataPacket);
// 3. 抬头数据入库
if (!insertHead(dataHead)) {
log.error("【{}——{}】 userId={}, warehouseCode={}, transmitWay={}, paramMap={}, 发送记录入库失败",
MODULE, dataPacket.getTransmitter(), dataPacket.getUserId(),
dataPacket.getWarehouseCode(), dataPacket.getTransmitDataWay(),
dataPacket.getDataParam());
return generateResult(false, "发送记录入库失败");
}
ResultVO result;
// 4. 发送方式
switch (dataPacket.getTransmitDataWay()) {
case SYNC:
result = sync(dataHead);
// TODO 接口调用 状态记录修改
break;
case JOB:
result = job(dataHead);
break;
case WINDQ:
result = windq(dataHead);
break;
default:
throw new JwmsException("未知的发送数据方式");
}
// todo
result.setObject(dataHead);
return result;
}
}
同样的道理,像原先实现的抽象类中的其他方法也是同样的逻辑处理,而不是在本抽象类中定义跟实现。因为,那些方法的语义边界不是该抽象类所具有的。比如checkNull方法,以及与数据库相关操作的方法,重新定义一个接口TransmitDataHelper,该接口定义帮助抽象类语义简洁的相关方法。
/**
* 发送数据支持类
*
* @author: 18109115
* @since: 1.0
* @see [相关类/方法](可选)
*/
public interface TransmitDataHelper {
/**
* 服务代码
*/
String serviceCode();
/**
* 服务版本
*/
String serviceVersion();
/**
* 将消息解析成自定义的查询参数
*/
Map<String, Object> paramMap(String content);
/**
* 根据解析后的消息参数,查询落表数据
*/
List<Map<String, Object>> queryHeadById(Map<String, Object> param);
/**
* 将查询结果转换成自定义的实体
*/
List<IntfDataHead> convertToHead(List<Map<String, Object>> resultParam);
}
这个帮助接口的实现类如下,注意归纳并总结其中的命名规范:
/**
* 自定义数据发送的支持类,即数据库操作支持
*
* @author: 18109115
* @since: 1.0
* @see [相关类/方法](可选)
*/
@Slf4j
public abstract class TransmitDataSupport extends BaseRepository implements TransmitDataHelper {
private static final String SQL_NAMESPACE = "transmitData";
protected void checkNull(DataPacket dataPacket) {
Assert.hasText(dataPacket.getUserId(), "发送数据包裹的userId为空");
Assert.hasText(dataPacket.getWarehouseCode(), "发送数据包裹的warehouseCode为空");
Assert.hasText(dataPacket.getTransmitter(), "发送数据包裹的transmitter为空");
Assert.notNull(dataPacket.getTransmitDataWay(), "发送数据包裹的transmitDataWay为空");
Assert.notEmpty(dataPacket.getDataParam(), "发送数据包裹的dataParam为空");
}
/**
* 自定义入表跟发送数据的实体
*/
public abstract IntfDataHead generateHead(DataPacket dataPacket);
/**
* 做一个通用的接口数据落表
*/
protected boolean insertHead(IntfDataHead dataHead) {
Map<String, Object> paramMap = DalUtils.convertToMap(dataHead);
return businessDaoOperator.execute(SQL_NAMESPACE + "insertHead", paramMap) > 0;
}
/**
* 查询抬头数据
*/
@Override
public List<Map<String, Object>> queryHeadById(Map<String, Object> param) {
return businessReadOnlyDaoOperator.queryForList(SQL_NAMESPACE + "queryHeadById", param);
}
/**
* 构建响应结果
*/
protected ResultVO generateResult(boolean result, String message) {
ResultVO resultVo = new ResultVO();
resultVo.setSuccess(result);
resultVo.setMessage(message);
return resultVo;
}
}
最后,数据发送的抽象类完整实现,调整为:
/**
* 发送数据的基本实现
*
* @author: 18109115
* @since: 1.0
* @see [相关类/方法](可选)
*/
@Slf4j
public abstract class AbstractTransmitData extends TransmitDataSupport implements TransmitDataWay {
@Override
public final ResultVO send(DataPacket dataPacket) {
// 1. 数据包裹参数校验
checkNull(dataPacket);
// 2. 构建、记录数据发送抬头对象
IntfDataHead dataHead = generateHead(dataPacket);
// 3. 抬头数据入库
if (!insertHead(dataHead)) {
log.error("【{}——{}】 userId={}, warehouseCode={}, transmitWay={}, paramMap={}, 发送记录入库失败",
MODULE, dataPacket.getTransmitter(), dataPacket.getUserId(),
dataPacket.getWarehouseCode(), dataPacket.getTransmitDataWay(),
dataPacket.getDataParam());
return generateResult(false, "发送记录入库失败");
}
ResultVO result;
// 4. 发送方式
switch (dataPacket.getTransmitDataWay()) {
case SYNC:
result = sync(dataHead);
// TODO 接口调用 状态记录修改
break;
case JOB:
result = job(dataHead);
break;
case WINDQ:
result = windq(dataHead);
break;
default:
throw new JwmsException("未知的发送数据方式");
}
// todo
result.setObject(dataHead);
return result;
}
}
有些小伙伴,或许有些疑问,这个新实现的数据发送的结构中,没有Spring的事物隔离的功能。别急,接下来,就介绍事务隔离的接入。
/**
* 无事物接口,可以提升到基础工程中,供发送接口的业务子类去实现。
*
* @author: 18109115
* @since: 1.0
* @see [相关类/方法](可选)
*/
public interface NoTranMethodAware {
/**
* 无事物执行实现方法
*/
ResultVO executeWithNoTran(IntfDataHead head);
}
/**
* 事务隔离支持接口实现,供发送接口的业务子类去调用
*
* @author: 18109115
* @since: 1.0
* @see [相关类/方法](可选)
*/
public interface TransactionIsolationService {
/**
* 接口数据异步处理, 新事务接口调用
*/
ResultVO orderDispatchNoTran(NoTranMethodAware service, IntfDataHead head);
}
/**
* 事务隔离接口支持
*
* @author: 18109115
* @since: 1.0
* @see [相关类/方法](可选)
*/
@Service("transactionIsolationService")
public class TransactionIsolationServiceImpl implements TransactionIsolationService {
@Transactional(propagation = Propagation.NOT_SUPPORTED)
@Override
public ResultVO orderDispatchNoTran(NoTranMethodAware service, IntfDataHead head) {
return service.executeWithNoTran(head);
}
}
最后,看一下接单业务发送类实现:
/**
* 接单数据分发 中央处理器
* 事物接口拿到子工程来做。
* @author: 18109115
* @since: 1.0
* @see [改造 OrderReceiveDistributeIntfDataServiceImpl ]
*/
@Service
@Slf4j
public class OrderReceiveDispatcher extends AbstractTransmitData implements NoTranMethodAware {
private static final String optName = "接单中央处理器";
private final Producer messageProducer;
private final TransactionIsolationService transactionIsolationService;
private final DeployOrderLockService deployOrderLockService;
private final OrderDistributeService orderDistributeService;
@Autowired
public OrderReceiveDispatcher(Producer messageProducer,
TransactionIsolationService transactionIsolationService,
DeployOrderLockService deployOrderLockService,
OrderDistributeService orderDistributeService) {
this.messageProducer = messageProducer;
this.transactionIsolationService = transactionIsolationService;
this.deployOrderLockService = deployOrderLockService;
this.orderDistributeService = orderDistributeService;
}
@Override
public String serviceCode() {
return "order_receive_distribute";
}
@Override
public String serviceVersion() {
return "0.0.1";
}
/**
* 生成发送的抬头数据
*/
@Override
public IntfDataHead generateHead(DataPacket dataPacket) {
IntfDataHeadByOrderReceiveDistribute dataHead = new IntfDataHeadByOrderReceiveDistribute();
dataHead.setUserId(dataPacket.getUserId());
dataHead.setWarehouseCode(dataPacket.getWarehouseCode());
dataHead.setLogisticOrderId(MapUtils.getString(dataPacket.getDataParam(), "logisticOrderId"));
dataHead.setStatus(TransmitDataStatusEnum.TO_BE.getValue());
dataHead.setGmtCreateBy(dataPacket.getUserId());
dataHead.setGmtModifiedBy(dataPacket.getUserId());
return dataHead;
}
/**
* 实际发送 windq 接单分发消息
* windq执行完,向LWMS提供的rsf接单分发服务完成
*/
@Override
public ResultVO windq(IntfDataHead dataHeadParam) {
IntfDataHeadByOrderReceiveDistribute dataHead = (IntfDataHeadByOrderReceiveDistribute) dataHeadParam;
try {
JSONObject json = new JSONObject();
json.put("warehouseCode", dataHead.getWarehouseCode());
json.put("id", dataHead.getId());
json.put("logisticOrderId", dataHead.getLogisticOrderId());
json.put("userId", dataHead.getUserId());
// 订单分发 消息发送,让链路异步化 接单 -> 订单分发 -> 订单分析,分担单一机器压力
// 同时,让LWMS跟JWMS之间的rsf实时下发接单数据的调用耗时短,以便JWMS能够接收更多的LWMS的接单数据
String sendResult = messageProducer.send(WindQTopicEnum.JWMS_ORDER_RECEIVE_DISTRIBUTE_SEND.getTopic(),
json.toString());
log.debug("【{}】warehouseCode={}, logisticOrderId={}, sendResult={}, 接单分发, WindQ消息发送完成",
optName, dataHead.getWarehouseCode(), dataHead.getLogisticOrderId(), sendResult);
} catch (Exception e) {
log.error("【{}】warehouseCode={}, logisticOrderId={}, 接单分发, WindQ消息发送异常, {}",
optName, dataHead.getWarehouseCode(), dataHead.getLogisticOrderId(), e.getMessage(), e);
return ResultVO.buildErrorResult("接单时,发送分发订单消息异常");
}
return ResultVO.buildSuccessResult();
}
@Override
public Map<String, Object> paramMap(String content) {
// TODO
return null;
}
@Override
public List<IntfDataHead> convertToHead(List<Map<String, Object>> resultParam) {
// TODO
return null;
}
/**
* 通过接单发送消息 -> 接单接收消息 -> 同步调用 [接单分发处理]
*/
@Override
public ResultVO sync(IntfDataHead head) {
// 记录调用开始时间
head.setTransmitTime(new Timestamp(System.currentTimeMillis()));
// 接口调用
ResultVO result = null;
try {
// 实际订单分发:
// 入库的:插入入库订单落地job表
// 出库的:一些列出库订单保存
result = transactionIsolationService.orderDispatchNoTran(this, head);
} catch (Exception e) {
log.error("[接口数据发送] head={}, 接口同步调用异常, {}", head, e.getMessage(), e);
}
// 发送入库通知单生成消息和出库订单的订单分析
return result;
}
/**
* 无事物执行订单分发
*/
@Override
public ResultVO executeWithNoTran(IntfDataHead head) {
return doOrderDispatch(head);
}
/**
* 实际的订单分发
*/
private ResultVO doOrderDispatch(IntfDataHead head) {
// 调用接单分发
IntfDataHeadByOrderReceiveDistribute convertHead = (IntfDataHeadByOrderReceiveDistribute) head;
// 获取分布式锁
String orderLockKey = deployOrderLockService.lock(convertHead.getLogisticOrderId());
if (StringUtils.isEmpty(orderLockKey)) {
return ResultVO.buildErrorResult("订单分发,获取分布式锁:" + orderLockKey + " 失败");
}
// 接单分发
try {
// 接单分发
ResultVO resultVO = orderDistributeService.distributeOrder(convertHead.getUserId(), convertHead.getWarehouseCode(), convertHead.getLogisticOrderId());
if (resultVO == null || !resultVO.isSuccess()) {
return resultVO;
}
// 回调使用
// resultVO.setObject(this.recordHandleByNext(convertHead));
return resultVO;
} catch (Exception e) {
log.error("【{}】warehouseCode={}, logisticOrderId={}, 接单分发异常, {}", "接单分发", convertHead.getWarehouseCode(), convertHead.getLogisticOrderId(), e.getMessage(), e);
throw new JwmsException(e.getMessage());
} finally {
// 释放分布式锁
deployOrderLockService.unlock(convertHead.getLogisticOrderId());
}
// return null;
}
}
很明显,重新优化的设计,比原来的接口语义更清晰,方法更简洁,直观感受就是,比如接下来的业务类实现发送数据的抽象接口时,不再是提供一坨的方法,而是会层次分明(类)的提供方法。
再来对比,原来接单数据接口的设计:
在设计接口及实现类中,我们还需要再认识一点,不要为了设计模式而设计,这样会割裂业务语义,不便于理解业务。
比如,我们项目在接单分发时提供的回调方法,本来你都在抽象类直接区分出入库单跟出库单了,就让其两个分之业务走完(这里的意思就是,直接发送相关消息),而不是再提供回调方法,在回调方法中,又一次区分入库单跟出库单,选择不同的windq发送消息。入库单就是订单落地消息(生成入库通知单,入库模块),出库单接下来的操作是订单分析消息。
我想,当时该接口的设计者是考虑了出库单中有一部分订单类型(销售出库单,属于出库单,但是订单状态为取消单标识,这也是属于重新入库类型的订单)属于业务上的入库单,才会这样设计回调,毕竟,该订单在我们jwms库中是出库单,我们需要将其状态变更,再重新入库。
可这并不影响两个分之走业务,我们只需要在出库单消息发送过程中,增加类型判断,发送不同的业务消息即可。这总比,我们通过回调割离业务语义更具有业务意义。
接下来,看消息接收统一接口:
/**
* 支持发送数据的消息消费者
*
* @author: 18109115
* @since: 1.0
* @see [相关类/方法](可选)
*/
@Slf4j
public abstract class TransmitDataMessageConsumerSupport extends AbstractMessageListener implements ApplicationContextAware {
private Map<String, TransmitDataHelper> SERVICE_MAP = new HashMap<>();
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
/**
* 使用数据处理服进行处理
* 1.根据ServiceCode和Version获取指定数据处理服务
* 2.将数据解析成map
* 3.锁单 调用同步处理方法
* 4.调用回调接口 释放锁
* */
@Override
public void consume(String msgId, String content) {
log.debug("【{}】topic={}, msgId={}, content={}, 开始处理", this.logMark(),
this.topicName(), msgId, content);
// 获取接口Service
String serviceName = serviceCode().concat("@").concat(serviceVersion());
TransmitDataHelper transmitDataHelper = SERVICE_MAP.get(serviceName);
if (transmitDataHelper == null) {
log.error("【{}】topic={}, msgId={}, content={}, serviceCode={}, 数据发送接口, 服务码未注册",
this.logMark(),
this.topicName(), msgId, content,
this.serviceCode());
return;
}
// 解析, 获取参数, 自定义解析参数
Map<String, Object> params = transmitDataHelper.paramMap(content);
if (MapUtils.isEmpty(params)) {
return;
}
// 查询抬头数据
List<Map<String, Object>> headList = transmitDataHelper.queryHeadById(params);
if (CollectionUtils.isEmpty(headList) || headList.get(0) == null) {
log.error("【{}】topic={}, msgId={}, content={}, headList={}, 无抬头数据", this.logMark(),
this.topicName(), msgId, content, headList);
return;
}
// 将查询结果进行解析转换
List<IntfDataHead> dataHeads = transmitDataHelper.convertToHead(headList);
IntfDataHead head = dataHeads.get(0);
// 获取分布式锁
// String headKey = AsyncSendRunnable.REDIS_KEY_PREFIX + intfDataService.getServiceCode() + ":" + head.getId();
// try {
// boolean isLock = jedis.lock(headKey, AsyncSendRunnable.DEFAULT_LOCK_TIME);
// if (!isLock) {
// log.error("【{}】topic={}, msgId={}, content={}, headKey={}, 分布式锁获取失败", this.logMark(), this.getTopicEnum().getTopicName(), msgId, content, headKey);
// return;
// }
// } catch (Exception e) {
// log.error("【{}】topic={}, msgId={}, content={}, headKey={}, Redis异常, {}", this.logMark(), this.getTopicEnum().getTopicName(), msgId, content, headKey, e.getMessage(), e);
// }
// 同步调用
ResultVO result;
// 发送方式转化
TransmitDataWay transmitDataWay = (TransmitDataWay) transmitDataHelper;
try {
result = transmitDataWay.sync(head);
log.debug("【{}】topic={}, msgId={}, content={}, serviceCode={}, result={}, 同步发送完成", this.logMark(), this.topicName(), msgId, content, this.serviceCode(), result);
} catch (Exception e) {
log.error("【{}】topic={}, msgId={}, content={}, serviceCode={}, 同步发送异常, {}", this.logMark(), this.topicName(), msgId, content, this.serviceVersion(), e.getMessage(), e);
}
// 发送结束, 移除抬头锁
// try {
// jedis.unLock(headKey);
// log.debug("【{}】topic={}, msgId={}, content={}, headKey={}, 分布式锁移除", this.logMark(), this.getTopicEnum().getTopicName(), msgId, content, headKey);
// } catch (Exception e) {
// log.error("【{}】topic={}, msgId={}, content={}, headKey={}, 分布式锁移除异常, {}", this.logMark(), this.getTopicEnum().getTopicName(), msgId, content, headKey, e.getMessage(), e);
// }
// if (result == null) {
// return;
// }
// 回调
// try {
// ResultVO resultCallback = intfDataService.callback(result.isSuccess(), result.getObject());
// log.debug("【{}】 serviceCode={}, head={}, result={}, resultCallback={}, 回调完成", this.logMark(), this.getServiceCodeEnum().getValue(), head, result, resultCallback);
// } catch (Exception e) {
// log.error("【{}】 serviceCode={}, head={}, result={}, 回调异常, {}", this.logMark(), this.getServiceCodeEnum().getValue(), head, result, e.getMessage(), e);
// }
}
/**
* 日志模组标记
*/
public abstract String logMark();
public abstract String serviceCode();
public abstract String serviceVersion();
public abstract String topicName();
@PostConstruct
private void init() {
Map<String, TransmitDataHelper> beans = applicationContext.getBeansOfType(TransmitDataHelper.class);
for (Map.Entry<String, TransmitDataHelper> entry : beans.entrySet())
SERVICE_MAP.put(entry.getValue().serviceCode() + "@"
+ entry.getValue().serviceVersion(), entry.getValue());
}
}
接单分发消息的实现类:
/**
* 订单分发消息消费者
*
* @author: 18109115
* @since: 1.0
* @see [相关类/方法](可选)
*/
@Service("JWMS_orderReceiveDistribute")
@Slf4j
public class OrderReceiveDispatchMessageConsumer extends TransmitDataMessageConsumerSupport {
@Override
public String logMark() {
return "接单分发消息消费处理";
}
@Override
public String serviceCode() {
return "order_receive_distribute";
}
@Override
public String serviceVersion() {
return "0.0.1";
}
@Override
public String topicName() {
return WindQTopicEnum.JWMS_ORDER_RECEIVE_DISTRIBUTE_SUB.getTopicName();
}
}
总结
以上代码改造的思路都是从读Spring源码中借鉴而来,比如Support和Helper的命名规范跟理解等。看源码,对自己的代码设计十分重要。