分层服务架构中最容易范的最昂贵错误 - Giedrius


最昂贵的错误是:业务逻辑通常保存在服务层中

几乎每个人对服务层的定义都略有不同。阅读这篇文章的每个人可能也会对它的定义有所不同。但是,我注意到人们对服务层的定义之间的共同点是服务层应该封装业务逻辑。

服务层应该封装业务逻辑,这是什么意思?
这句话可以有不同的解释,所以它没有告诉我们任何有价值的东西。

我看到最多的是这样的信念:服务层不过是一个网关,表现层通过它来访问业务层。
听起来很糟糕,但在实践中却很常见。
这是我在一些公司(和很多在线博客)中看到的,所以我猜想在很多组织中也是如此。

这种信念的结果往往是意识到,如果服务层只是向它传递请求,那么拥有一个独立的业务层是没有意义的,这就是让人们把服务层和业务层合并在一起,并声明服务层包含业务逻辑的原因。

为什么将服务层和业务层合并成一个层是一个大问题
合并层的结果是一个不正常的3层架构,它由表现层、服务层和持久层组成。
最糟糕的是服务层,因为在其中你通常只看到我所说的服务类,例如,BookService、UserService、OrderService,等等。

那么问题出在哪里?
人们认为服务层是包含业务逻辑的。
然而,如果你看一下,你只看到服务类。
业务域在哪里?它无处可寻。从本质上讲,你有同一个类,它有两方面的作用:业务逻辑和服务层的一部分。

这样,导致服务层通常由上帝对象组成
让我们再定义一个东西。一个常见的反模式,叫做神对象、上帝对象。
什么是上帝对象?
它是一个知道并做所有事情的对象。很容易理解。
但是,我们都知道,这是一个应该避免的糟糕的反模式。

为什么在这里要谈论上帝对象呢?
如果你仔细看一下服务类,它们做所有与领域对象有关的事情,比如,书、用户或订单。它们是字面上的上帝对象。
本应是业务逻辑的东西往往在上帝对象里面。它并不作为一个独立的单元而存在。

这就产生了很多问题。上帝对象是很难维护的,因为所有的东西都在这里,而且有很多的耦合。它们违反了大多数良好设计的原则。例如,单一责任原则。如果你看一下一个典型的服务类的实现,你会发现它有不止一个变化的理由。我不打算在此展开,因为这是个值得单独写一整篇文章的故事。

你可能会想 "好吧,有道理,但是,我在我的项目中不使用OOP,所以我不写服务类",如果是这样的话,请先听我说,因为这个问题与你所使用的编程范式或具体的类没有关系。

如果你写的是过程性代码,这并不重要,你仍然可以创建一个相当于上帝对象的模块,其中的耦合性是通过屋顶的。

服务层和业务层的合并是由于人们没有意识到服务和业务层的区别。这就是为什么你有服务神对象,他们做任何事情,都依赖于外部世界。

服务和业务层之间的区别
服务实际上应该是什么?服务是一组功能,客户可以调用它来做一些事情,无论是执行计算还是检索一些数据。它是一种抽象。抽象的存在是有原因的。它们隐藏了某些事情的完成方式。坐落在一个模块中的东西,作为一个抽象的东西,知道在抽象的背后正在做什么。这意味着,它不是一个独立的单元。

业务层应该是什么?它应该是一个独立于系统中发生的其他一切的单元,只知道你的业务领域,换句话说,你的业务模型,你做什么来提供一些有用的服务。
当你把依赖的服务层和独立的业务层合并成一个层时会发生什么?
当你合并层时,你消除了它们之间的界限,而且,由于你把它们看作是一个整体,你最终使业务层依赖于外部世界。

以下是我在从事将服务层和业务层合二为一并由上帝对象组成的项目时的痛苦经历所得出的关键经验。

#1.不要把服务层当作包含业务逻辑的层
我仍然相信,如果你做这样的事情,你仍然可以不费吹灰之力,但这需要很小心。你可能明白这一点,但是,如果你在一个团队中工作,可能会有(而且经常有)人不明白,这就是为什么要把不该在一起的东西分开。

如果你发现你的应用程序主要由服务类组成,你也许应该重新考虑你在做什么。

上帝对象是不好的,称它们为服务并不能改变它们是什么。

#2.不要把服务层仅仅看成是通向业务层的一个通道
把业务层看作是独立于外部世界的一个层。

把服务层看作是一个封装了你的应用程序中其他层的功能的层,它的目的只是作为一个抽象,当业务层需要执行不属于它的东西时,它可以参考这个抽象。

#3.不要认为分层架构可以保证质量
不会的。架构的选择应该是一个知情的决定。如果你选择分层架构,它所包含的层也应该是一个知情的决定。

#4.必要时允许一个层绕过另一个层
这在分层架构中是允许的。然而,如果你发现自己经常需要绕过各层,这表明有些层是不必要的,或者架构不是最适合的,你应该做一些改变。

#5.在服务层和业务层之间建立一个明确的区别
这个区别是什么应该是你的知情决定,它最适合你的项目。确保每个在你的项目上工作的人都知道这种区别。

#6.避免没有明确目的的服务和管理类
当你有像BookService、UserService、BookManager或UserManager这样的类时,它们并没有真正告诉你它们做什么。

总结
将服务层与业务层合并是一个很大的错误,最终会导致一堆错误的决策,使您的代码库无法使用并可能产生严重后果。