谁应该“吃”掉它

背景

做一个项目的时候,遇到一个问题。如图所示,流程如下:

STEP1: 协议发起协议下单,下单成功后,获取 E 单号;

STEP2: 协议使用 E 单号调用代扣款服务;

STEP3: 代扣款服务将 E 单号转为 X 单号,然后使用支付收单服务,支付成功。

STEP4: 交易监听收单消息,进行支付回调处理。

问题来了:由于代扣款服务将 E 单号转为 X 单号,支付给交易的消息中,只有 X 单号。交易不识别 X 单号,报订单号非法。


讨论

直觉上会认为:交易应该拿到 X 单号对应的 E 单号,然后去进行后续处理。 要完成这个功能,确实可以这么做。但是,这样做合适吗?

能做与应该做

在讨论问题时,常常会出现一些模糊边界,这个边界的每个系统都可以做,但是,—— 能做,就应该去做吗 ?

这里涉及到定位的问题。交易的定位是:订单的创建、订单的生命周期管理。协议也有自己的定位:处理会员与商家之间的关系。

看上去,对于这个示例,交易和协议的定位都不太适合去做这个事情。

角色的相对性与转换

在这个示例中,容易意识到:这里需要有一个流程的全局主导者,将协议下单、支付、完成的生命周期串联起来。

从业务上来说,这是个协议下单的业务,理论上,协议应该就是业务方; 从流程上看,协议方下单、调用扣款,做的也是控制流程的事情。 不过,协议方认为,自己只是协议服务,不应该关注这么多东西。

不过我觉得,角色定义是相对的,是可以在基础服务和全局流控之间转换的。比如交易,在下单流程中,交易就是主导者角色,调用商品、营销、支付、会员等,将交易相关的信息聚合起来落库,将整个购买流程串起来;但对于更上游的业务,比如微商城、零售、教育,交易又作为一个基础服务。

同理,协议方虽然也是一个基础服务,不应该关注支付、订单之类的事情,但在协议下单这个业务语境里,它应该担当一个流控主导者角色。

信息的主控方

仔细思考,这个问题的产生源于订单号与收单号在代扣款服务的自动转换。交易和协议,都不具有单号映射的转换信息。只有代扣款服务有这个信息及解释权。理论上,既然是代扣款服务“闯的祸”,也应该它来“将功补过”才对。

通用能力

协议同学又提出了一个观点: 如果有业务方 A,B,C,也需要下单成功后代扣款能力,但不需要协议能力,是不是每个业务方都需要做相似的事情 ? 是否可以提炼为通用的能力 ?

好问题。 看上去,交易吃掉这块逻辑,无论对交易还是对业务方都有益。

语义层面

仔细分析下。 通用能力,通常包含相同的部分和相异的部分。相同的部分可以复用,相异的部分则使用扩展点来实现。

在这个示例里:相同的部分显而易见:下单成功 -> 代扣款服务 -> 监听收单消息,将订单状态改为已支付。 一气呵成,多好 !

那么,有什么潜在的问题呢 ?

  1. 如果代扣款失败,该怎么处理 ?

  2. 如果支付成功后,业务方需要做一些处理,再修改订单状态,怎么办 ?

有人说,这两个问题都可以通过扩展点来实现呀!这里又衍生出两个话题:过度设计 和 复用模式。 稍后会谈到。

更关键的一点是,必须从语义层面而不是技术层面来思考通用能力。 怎么理解呢 ? 请注意, 下单后代扣款,并不是一个通用的交易能力,只是从技术角度上看是可以复用的,也是比较明显的;协议下单才是一个完整的交易能力,而这个能力包含了下单后代扣款。理解通用能力,必须从一个更完整的概念来理解,否则就会做成一个残缺的通用能力。

这里,如果要做成通用能力,交易必须吃掉整个协议下单。而过程会变成如下图所示:


过度设计

考虑系统的通用性和可扩展性是合理的,不过,是否要在最初就做的很完善呢 ? 通过扩展点的方式,看似可以完美支持这些功能,但是,如果后续很长一段时间并不需要类似能力,这个扩展点的设计和实现就容易变成一个复杂度高、可维护性低、成本高的过度设计的方案。

此外,交易核心流程依赖上游扩展点实现,基础服务依赖业务实现,是依赖的反模式,容易导致可靠性问题。 可见,扩展点虽然万能,却不能滥用。

复用模式

理想化的复用模式是:定义一个万能的流程,这个流程中有很多插槽,这些插槽都是扩展点,可以供上游实现和插入。

实际系统中,对于比较常用的核心的地方,会定义一些插槽。可是:是否插槽越多越细,支持的能力越灵活越好呢 ?

还有一种相对低级而有效的复用模式:就是提供一系列拼板,上游可以根据自己的需求去拼接自己的业务图景。

一种是在核心流程中插入许多细小的插槽,业务方可以插入自己的实现,来完成定制化的能力。但这种方式有个弊端: 插槽越多,就越难理解和维护;核心流程依赖业务方的实现越多,核心流程就可能越不稳定。

一种是基础服务方只提供基础拼板,由业务方自行根据需求去拼接。这种方式,业务方更灵活自由,但是也会有很多相似的东西要重复做。

你更倾向于哪一种呢 ? 是否有更好的方案来解决这个问题 ?

复用能力

实际上,交易已经做了一个支付回调的通用组件,只是这个组件无法处理各种由上游导致的特殊情况。在这个示例里,订单号被修改了,导致通用组件失效。

交易应该扩展这个通用组件的能力,提供一些扩展点吗 ? 还是上游去解决这些特殊情况,使得通用组件依然能够生效 ?

潜在变化

上面谈到,是否应该做一个支付回调的扩展点来支持,这是很自然想到的一个方案,因为问题的最直观点就出在这里。 然而,在思考项目方案时,很容易受到现有结构的影响,而忽视了潜在的变化。

仔细想想,为什么要改这里呢 ? 是因为我们依赖了一个潜在的假设:代扣服务是依靠收单系统来实现的,然后收单发送消息给交易。 如果未来代扣服务不再依赖收单系统来实现了呢 ?那么交易就无法接受到收单的消息,而必须去监听代扣服务的消息了。 在第二天的讨论中,代扣服务的同学就谈到了这个改造。


先例

遇到问题,寻找可供参考的先例也是一种办法。

比如零售,也遇到过类似问题。当时采取的办法就是: 零售去做一层收单号与订单号的转换,然后调用交易提供的支付回调服务,将订单状态更新为已支付。

不过零售场景跟这个有点差异: 零售是先支付,再创建订单,创建订单的时机是不确定的; 而协议下单的场景是,先下单再支付,创建订单的时机是确定的。

先例是否能够用到当前场景下,也值得仔细思考。

成本与风险

有一个重要因素:实现成本和风险考量。

交易:需要调用代扣款服务的查询接口,做一层转换处理。 风险:由于交易订单量非常大,而真正需要调用代扣款服务接口的订单量很小,很可能导致:a. 绝大部分的订单都没必要调用这个接口; b. 代扣款服务容易被拉挂; c. 代扣款服务如果被拉挂,交易的核心流程的稳定性会受到严重影响。

协议:需要自己去调用代扣款服务查询接口,做一层转换,然后调用交易提供的支付回调服务,将订单状态更新为已支付。 风险:无。不过有一些开发工作量,而且看似不太适合协议的定位。

代扣款服务:做一层转换,将 X 单号转成 E 单号,然后透传给收单服务,收单服务发送跟之前一样的支付收单消息。这样协议不会有工作量,交易也可以复用原来的支付回调组件。风险 : 可能影响支付的一些原有的服务。

实现成本和风险考量往往是一个很有决定性的因素。 当然,在“成本和风险小但不符合系统定位”的情况下,需要破例,不按照这个角度来定方案。


最终的目标

回过来再思考下:为什么要讨论这个问题 ?最终想要达成的目标是什么 ?

讨论这个问题,并非因为这个问题无解,而是因为这个问题有多种解决,交易、协议、代扣款都可以解决这个问题。那么为什么要讨论这个问题呢 ? 这是“踢皮球”的行为吗 ?我认为不是。恰恰相反,借这个问题,我想呈现的是,方案上的决策过程是怎样的,决策依据有哪些,这些依据的优先级如何。

最终的目标,不是为了偷个懒,而是为了达成全局最优: 成本实现和风险小,每个功能都有合适的承接者,可以增强现有系统的能力,容易理解和维护。对当前适宜,有利于后续扩展。 如果为了后续扩展而导致当前实现很复杂,可能是得不偿失的。

现在请问:交易、协议、代扣服务、支付收单,谁应该来解决这个问题 ? 关键依据是什么 ? 反对的关键依据是什么 ?

小结

在中大型项目中,常常会有一些由于模糊的系统边界而导致的“灰色功能区域”。每个毗邻“灰色区域”的系统,似乎都有权利去实现这个灰色功能区域。那么,谁应该做这件事呢 ? 这篇文章提出了一些基本的考量因素:

  • 系统的定位是怎样的 ?是否适合承接“灰色区域”的功能需求 ?
  • 在当前业务场景下,谁是流程的主控方 ?
  • 在当前业务场景下,谁拥有信息的解释权 ?
  • 是否从语义层面能够描述一个完整的通用能力 ?
  • 在考虑通用能力的同时,是否有过度设计的嫌疑 ?
  • 实现方式是否容易理解、维护、可靠、稳定 ?
  • 采用何种复用的形式更适宜 ?
  • 现有方案是否依赖了某个有潜在变化的假设 ?
  • 是否有先例可以参考 ?
  • 实现成本和风险如何 ?

posted @ 2019-11-26 00:15  琴水玉  阅读(325)  评论(0编辑  收藏  举报