设计讨论:依赖倒置,与 “I'll call you”

简介:

问题来自于我和同事在一个跨系统交互设计上的分歧。


同事的设计,基本上是这样的:

wKioL1hFc3SAo0eFAABxPEASGtQ882.png-wh_50


这种设计很常见,其基本思路就是:服务端接口需要什么数据,客户端就传入什么数据。这种设计的优点在于简单:开发简单,交互简单。但是它的缺点也很明显:扩展性低。一旦服务端对某个业务中的业务-数据依赖关心进行了修改,则客户端很可能也要跟着修改。例如,如果系统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把业务-数据间的依赖关系全部收到自己的系统中去。如下图。

wKioL1hFedLCdJZ_AADQLjJh6nQ115.png-wh_50


即,客户端在调用时,提供一个数据主键。服务端根据自己的业务需要,按主键去查询出所需的数据。


这样做的缺点是复杂。代码会复杂,交互也更复杂。由于多了一次交互,性能上会有下降。另外在分布式事务方面还有一点小隐患。

但是这样的优点,恰恰就是易于扩展。只要新业务所需数据仍然落在数据集合D内,那么系统A无需任何改动。


带来这个优点的,就是依赖倒置。虽然在业务上,是客户端依赖于服务端的功能;但是在设计上,是服务端依赖于客户端的数据。并且,这种依赖只是简单的数据依赖。这也是所谓的“好莱坞法则”:Don't call me, I'll call you! 


实际上,同事的设计和我的设计,在我们的系统中都已经有了实践。到目前为止的结果,是按同事的思路设计的另一个系统服务已经经过了二次改造,归入我的思路中了。而按我的思路设计的另一个系统服务,目前在做性能上的优化。


遗憾的是,我没有说服他。他仍然坚持己见,只是在客户端调用接口时,将数据集合D一次性提交到服务端。

这种思路算一个折中。但是,如果服务端所需数据集超过了数据D呢?按他的方案,客户端仍然需要修改;按我的方案,在客户端没有开通对应的查询接口时也需要修改。只不过,客户端是我负责的系统(也许这也是我一直跟他争执的原因之一吧),而这个系统中,已经规划了“每个资源都应有查询服务”。


附,分布式事务上的一点小隐患在于:如果在客户端调用服务端的那个事务中,主键key所对应的数据有部分还未提交,那么,通过查询接口是无法查询到这部分数据的。弥补措施是在服务接口中把这一部分数据传过来,作为“优先”配置或数据,覆盖查询接口中查到的结果。




本文转自 斯然在天边 51CTO博客,原文链接:http://blog.51cto.com/winters1224/1879775,如需转载请自行联系原作者

相关文章
|
1月前
|
JavaScript 前端开发
call 和 apply 的区别是什么,哪个性能更好一些
`call` 和 `apply` 都是 JavaScript 中用于改变函数调用上下文(`this`)的方法。`call` 接受参数列表,而 `apply` 接受一个参数数组。在性能上,两者差异不大,但 `call` 通常略快一些。
|
4月前
|
API 运维
开发与运维函数问题之mapConcurrent实现与Pekko-Stream的mapAsync相似之处如何解决
开发与运维函数问题之mapConcurrent实现与Pekko-Stream的mapAsync相似之处如何解决
26 0
|
前端开发
【前端验证】对uvm_info宏的进一步封装尝试
【前端验证】对uvm_info宏的进一步封装尝试
|
编译器 Linux 调度
RT-Thread编程高阶用法-函数扩展之$Sub$与$Super$
RT-Thread编程高阶用法-函数扩展之$Sub$与$Super$
121 0
|
设计模式 存储 Java
【Java设计模式 规范与重构】 五 重构实战:基于ID生成器case(下)
【Java设计模式 规范与重构】 五 重构实战:基于ID生成器case(下)
201 0
|
设计模式 存储 SQL
【Java设计模式 规范与重构】 五 重构实战:基于ID生成器case(上)
【Java设计模式 规范与重构】 五 重构实战:基于ID生成器case(上)
140 0
|
前端开发
前端学习案例1-call,apply的重用
前端学习案例1-call,apply的重用
75 0
前端学习案例1-call,apply的重用
CF1341C. Nastya and Strange Generator(思维 模拟 构造)
CF1341C. Nastya and Strange Generator(思维 模拟 构造)
84 0
Runtime系列:super调用函数本质、isMemberOfClass与isKindOfClass的区别、综合分析【05】
Runtime系列:super调用函数本质、isMemberOfClass与isKindOfClass的区别、综合分析
132 0
Runtime系列:super调用函数本质、isMemberOfClass与isKindOfClass的区别、综合分析【05】
|
C#
改善代码设计 —— 简化函数调用(Making Method “.NET研究”Calls Simpler)
  系列博客       1. 改善代码设计 —— 优化函数的构成(Composing Methods)       2. 改善代码设计 —— 优化物件之间的特性(Moving Features Between Objects)       3.
872 0