问题来自于我和同事在一个跨系统交互设计上的分歧。
同事的设计,基本上是这样的:
这种设计很常见,其基本思路就是:服务端接口需要什么数据,客户端就传入什么数据。这种设计的优点在于简单:开发简单,交互简单。但是它的缺点也很明显:扩展性低。一旦服务端对某个业务中的业务-数据依赖关心进行了修改,则客户端很可能也要跟着修改。例如,如果系统B中,完成业务B1需要的数据不再是D1而是D3,则不光系统B要改,系统A也要改。如果还有系统C/D/E/F也调用了这个接口,那么这些系统都面临着修改代码的风险。
实际上,不仅仅是系统间的服务,在系统内部服务的设计上,这种思路也很常见。我曾经见过一个用作金额计算的类。它的计算公式是a+b/c-d^e,而它对外暴露的方法居然就是public BigDecimal calculate(BigDecimal a, BigDecimal b, BigDecimal c, BigDecimal d, BigDecimal e)。当公式需要扩展为a+b/c-d^e-f时,其中的麻烦可想而知。
造成这种风险、麻烦的原因就在于:这个设计不仅简单的实现了客户端对服务端的业务依赖,而且将服务端内部的控制依赖也暴露、延伸到了客户端。
客户端对服务端的依赖,是一种业务上的“正向”依赖。没有服务端提供的服务,客户端的业务也无法正常的进行下去。
但是,我们在做设计的时候,不应当简单的临摹业务流程。业务流程从A到B,系统设计就画两个方框+一个箭头;这是在为现在的自己偷懒。更不应当把业务流程中的依赖关系过度的延伸出去;这简直是在为三个月后的自己挖坟。
对这一个系统的设计,我的观点就是:系统B把业务-数据间的依赖关系全部收到自己的系统中去。如下图。
即,客户端在调用时,提供一个数据主键。服务端根据自己的业务需要,按主键去查询出所需的数据。
这样做的缺点是复杂。代码会复杂,交互也更复杂。由于多了一次交互,性能上会有下降。另外在分布式事务方面还有一点小隐患。
但是这样的优点,恰恰就是易于扩展。只要新业务所需数据仍然落在数据集合D内,那么系统A无需任何改动。
带来这个优点的,就是依赖倒置。虽然在业务上,是客户端依赖于服务端的功能;但是在设计上,是服务端依赖于客户端的数据。并且,这种依赖只是简单的数据依赖。这也是所谓的“好莱坞法则”:Don‘t call me, I‘ll call you!
实际上,同事的设计和我的设计,在我们的系统中都已经有了实践。到目前为止的结果,是按同事的思路设计的另一个系统服务已经经过了二次改造,归入我的思路中了。而按我的思路设计的另一个系统服务,目前在做性能上的优化。
遗憾的是,我没有说服他。他仍然坚持己见,只是在客户端调用接口时,将数据集合D一次性提交到服务端。
这种思路算一个折中。但是,如果服务端所需数据集超过了数据D呢?按他的方案,客户端仍然需要修改;按我的方案,在客户端没有开通对应的查询接口时也需要修改。只不过,客户端是我负责的系统(也许这也是我一直跟他争执的原因之一吧),而这个系统中,已经规划了“每个资源都应有查询服务”。
附,分布式事务上的一点小隐患在于:如果在客户端调用服务端的那个事务中,主键key所对应的数据有部分还未提交,那么,通过查询接口是无法查询到这部分数据的。弥补措施是在服务接口中把这一部分数据传过来,作为“优先”配置或数据,覆盖查询接口中查到的结果。
设计讨论:依赖倒置,与 “I'll call you”