微服务时代的得墨meter耳定律

问题

假设我们具有三个组成部分: ABC。

另一个组件Main依赖于A并想要调用属于组件C的方法foo:

a.getB().getC().foo();

上一片段等同于:

B b = a.getB();
C c = b.getC();
c.foo();

这段代码的问题在于,为了调用foo, Main需要一直通过组件AB一直到达C,并最终调用该方法。 Main需要了解所有3个组件及其内部结构,以便能够调用该调用。

虚线表示组件的依赖性,箭头表示调用

尽管我们的示例过于简化,但可以说Main需要知道要调用哪种方法才能从A获得B ,从B获得C并调用foo 。 此外,中间组件( BC )需要提供一些API以支持这些链接的调用。 显然,此类API会改变其性质。 组件A不应充当组件B的提供者,而应具有证明其存在的API。 问题是我们在这里描述的是违反Demeter定律的

得墨meter耳定律是一个非常简单的准则,可帮助我们编写未紧密耦合的组件。 Wiki页面使用以下3个简单的句子总结了Demeter法则:

  • 每个单元对其他单元的了解都有限:只有与当前单元“密切相关”的单元。
  • 每个单元只能与朋友交谈; 不要和陌生人说话。
  • 只与您的直系朋友交谈。

在OOP中,我们了解到对象之间通过传递消息进行通信,因此方法调用实际上是从调用方发送到被调用方的消息。 例如, a.getB()仅仅意味着MainB发送消息给A“向” 。 在第二个片段中,很明显, Main负责发送所有消息,而中间组件除了提供内部组件外没有任何实际逻辑。

现在,让我们考虑一下Main需要做什么。 它需要调用foo,但它所知道的只是A组件。 无需“搜索”所有其他组件。 甚至不需要知道它们的存在。 它需要做的就是“告诉”组件A查找并调用方法fooA可能会将这项任务委派给B,B将与C做类似的事情。 在这种情况下,组件A“告诉” B该怎么做。

优点

得墨meter耳定律有一些非常有益的含义。 每个组件仅与其直接依赖项进行交互。 没有间接依赖性是已知的,这使得更容易独立地更改其实现。 想象一下,如果我们将组件A的实现更改为调用某个组件X而不是组件B。 通过链式调用,此类决策会传播到调用方。 Main必须更改才能采用此更改。

测试也容易得多。 在我们的示例中,我们将必须模拟所有中间组件,这显然是一种设计气味。 实际上,当我们编写测试时,通过注意到必须创建多少无用的模拟程序,很容易识别代码的味道。

这些是内部实现泄漏和不良封装的副作用,导致紧密耦合的组件。 得墨meter耳定律通过使组件更具自主性/解耦性来帮助系统设计,在我看来,这是最重要的优势。

微服务中的得墨meter耳定律

在微服务领域,组件可以是位于网络中不同位置的服务。 最重要的区别是组件之间的所有调用现在都是远程调用,因此故障实际上是​​可能的情况。

在我们的示例中考虑一下,如果每个getComponent()调用都是一个远程调用。 Main对3个不同组件/服务进行远程调用时,它的脆弱性有多大? 当然,即使我们应用Demeter定律,也必须沿着组件传递3条消息才能获得foo()的结果,但是现在Main仅与需要的1个组件进行通信。 正如我们将在以下段落中看到的那样,在可用性和性能方面,通信协议的这一变化具有很大的收益。

通常,为了解决组件故障的问题并提高我们系统的性能和弹性,我们使用事件驱动的体系结构。 在事件驱动的体系结构中,微服务之间的通信是通过将事件发送给事件调度程序而不是直接将消息发送给我们要调用的组件来实现的。 被叫方监听这些事件,并在事件可用时对其进行处理。 这些事件被异步发送到调度程序。 可以在这篇文章中找到有关事件驱动架构的更多信息

调用a.bar()在概念上与将带有此调用的事件发送到组件A订阅了这些事件的调度程序相同。 由于调用方不需要知道如何调用A,但是这种额外的间接级别使组件之间的耦合程度更高,但是通信是异步的,这意味着在某些情况下,调用方不必等待A完成其处理。 在我们的示例中,如果foo()实际上是发送通知或电子邮件的方法,则调用者将不必等待此任务完成。

即使在示例中使用异步通信,在违反Demeter定律的情况下,由于Main发送的事件相互依赖,这一设计仍然存在一些问题。 Main要求组件AB ,以便能够向组件BC等。这种交谈是最终事件的构造所必需的。 收益C(实际上是B就返回C的位置)前不能发送调用FOO到C的事件。 另外,如果中间组件/服务之一不可用,则Main将无法构造要发送给C的最终事件,因此Main将无法完成其过程,并且根据业务规则,它可能会失败完成任务。 在这种情况下,间接组件的不可用性会传播到Main

该设计在性能方面也存在一些问题。 Main可能已经编写了立即调用foo的事件,并继续做一些有用的事情,而不是发现其他组件。

所有这些问题都暗示着一个事实,即Main而不是“告诉”它需要的东西,而是试图通过“询问”其他组件来收集信息来找到一种完成大部分任务的方法。 可以说Main的行为就像不信任其他组件一样!

显然,如果我们以不违反Demeter法则的方式设计系统,那么这些问题几乎是免费的。 Main将仅发送1个事件并继续其任务,即使其他组件不可用也是如此。 由于Main仅发送1个事件,因此没有像以前一样创建此事件的额外步骤,因此已优化了通信协议。 显然, Main的可用性不取决于其他组件(事件分发程序除外)的可用性。 Main仅通过“说出”需要完成的任务来完成其任务。 当其他组件再次变得可用时,它们将最终从调度程序获取事件。

我们应该将组件设计为尽可能松散地耦合,并且得墨meter耳定律是朝着这个方向发展的好方法。

进一步阅读:

https://martinfowler.com/bliki/TellDontAsk.html
https://pragprog.com/articles/tell-dont-ask

From: https://hackernoon.com/the-law-of-demeter-in-the-era-of-microservices-3186f4c399a1

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值