我正在阅读Eric Evans的精彩作品“领域驱动设计”。但是,我不禁感到'层'模型是人为的。为了扩展该陈述,似乎它试图将各种概念变成一个特定的,整洁的模型,即彼此交谈的层。在我看来,层模型太简单了,无法实际捕获(好)软件的工作方式。
进一步扩展:埃文斯说:
“将复杂程序划分为多个层。在每个层中开发一个具有内聚性的设计,并且仅依赖于下面的层。遵循标准的架构模式,以提供与上面层的松散耦合。”
也许我误解了“取决于”意味着什么,但据我所知,它可能意味着a)X类(例如在UI中)具有对具体类Y的引用(在主应用程序中) )或b)X类引用提供Y-ish类服务的Y-ish类对象(即作为接口持有的引用)。
如果它意味着(a),那么这显然是一件坏事,因为它无法重新使用UI作为提供Y-ish功能的其他应用程序的前端。 但如果它意味着(b),那么UI如何依赖于应用程序,而不是应用程序依赖于UI?两者在彼此交谈的同时尽可能地彼此分离。
埃文斯的依赖关系图层模型似乎过于简洁。
首先,如果说设计的每个区域都提供了一个本身就是岛屿的模块,并且理想情况下所有通信都是通过接口,在契约驱动/责任驱动的范例中,这不是更准确吗? ? (即,只设计'对较低层的依赖')。同样,域层与数据库通信 - 域层与数据库解耦(通过DAO等),因为数据库来自域层。两者都不依赖于另一个,两者都可以换掉。
其次,概念直线的概念(如从一层到下一层)是人为的 - 不是更多的互通网络,而是单独的模块,包括外部服务,公用事业服务等,分支在不同的角度?
非常感谢 - 希望您的回复可以澄清我对此的理解。
编辑:事实上,埃文斯后来澄清说他是在谈论:“层是松散耦合的,只有一个方向的设计依赖性。上层可以直接使用或操纵较低层的元素通过调用它们的公共接口,保持对它们的引用。...当一个对象需要向上通信时,我们需要另一种机制..比如OBSERVERS“
这似乎完全是对我的设计。它似乎将设计依赖性与交互流混淆。 UI需要对应用程序的引用,因为操作通常是用户启动的!对于应用程序启动的操作,例如某些内部计时器事件警告用户某事,应用程序现在需要对UI的引用,就像UI需要先前对应用程序的引用一样。在什么意义上还有一个“设计依赖”而不是另一个?为了支持我的观点,要实现OBSERVER,需要一个对UI组件的引用列表的APP。
在我看来,引用和设计SEEM从顶层到底层的唯一原因是因为通常会启动操作。当在应用程序中启动操作时,引用必须以相反的方式进行..
答案 0 :(得分:2)
我同意你的看法。 DDD中的想法听起来很酷但很难实现。另请注意,对于具有两个或三个表单的简单CRUD应用程序,应用程序的分层本身可能是一个重大的过度杀伤。
但请注意DDD中的Layer Architecture正试图解决的问题。
在面向对象的程序中,UI, 数据库,以及其他支持代码 直接写入 业务对象。额外的业务 逻辑嵌入在行为中 UI小部件和数据库脚本。这个 因为这是最简单的方法 在短期内使事情有效。
与域相关的代码时 通过如此大的数量扩散 其他代码,它变得非常 很难看到和推理。 UI的表面变化可以 实际上改变了业务逻辑至 更改业务规则可能需要 细致的UI代码追踪, 数据库代码或其他程序 元素。实施连贯, 模型驱动的对象变成了 不切实际的。自动化测试是 尴尬。拥有所有技术和 每项活动涉及的逻辑,a 程序必须保持非常简单或它 变得无法理解。
请注意Microsoft的这张图,它描述了使用DDD实现分层架构的非常好的实现。请注意UI视图的概念。
答案 1 :(得分:2)
Eric对分层架构的描述确实有些令人困惑。据我所知,他指出良好的应用程序有一些层,这些层应该解耦。图层的唯一主题是值得拥有的书,并且在书中没有进一步描述(除了你引用的小片段)。
实际上DDD并不依赖于分层架构,但我敢打赌DDD的所有实际实现都在架构中使用层。关于构建基于DDD的解决方案的更好(比埃文斯)的解释,它来自Jeffrey Palermo:the onion architecture。巴勒莫详细讨论了每一层的责任以及沟通的途径。我个人在我自己的项目中使用这种方法取得了很大的成功。如果你想看看它在实践中的表现,请看看DDDSample(如果你是Java人)或DDDSample.Net(我编写的DDDSample的.NET端口)
答案 2 :(得分:1)
你在暗示Dependency inversion principle:
一个。高级模块不应该依赖于低级模块。两者都应该取决于抽象。 B.抽象不应该依赖于细节。细节应取决于抽象。
A部分意味着您的应用程序的分区不直接依赖于另一个分区的实现细节。
答案 3 :(得分:0)
我认为他意味着“较低级别”独立于“更高级别”,并且可以独立存在。
没有服务层存在的UI层是没有意义的。但是,如果服务作为Web服务或RPC端点公开,那么在没有UI层的情况下拥有服务层是完全合理的。
类似于Repository或DAO层。
我同意你的观点,如果这些很好地解耦,你可以从UI下面交换服务层(就像使用flatfile“服务层”模型一样,在写入服务层代码之前创建UI) )或存储库(通常在服务层的集成/单元测试中交换等。
但是让较高层存在以使更高层运行的依赖性是一种概念上的单向依赖。
答案 4 :(得分:0)
我不认为使用接口将模块彼此分层和解耦是相互排斥的,这就是我解释你的帖子的方式。
在我的世界中,层与功能范围和编译/运行时依赖性一致。另一层顶部的一个层使用并添加某种值(从用户的角度来看)到它下面的一个层 - 它通常由多个“孤岛”模块组成,这些模块可以完成定义良好的功能。我总是隐藏界面背后的实现,减轻了更改它的痛苦。这也使我能够编译/构建我的代码而无需实现每一层,这反过来又使多人在一起工作变得更容易。
这是一个很好的图表,适用于我构建的大多数严肃的东西:
UI/API controllers
|
A Facade
|
Composite Services
|
Isolated Services <--> External Services
|
DAOs
|
Persistance
答案 5 :(得分:0)
我已经看到过非常有效地使用图层的想法,而且我已经看到了需要但不使用的应用程序。
以第一个案例为例。我参与了一个项目,我们为面向服务的架构构建了基础架构和服务。 (我们这样做是因为它是1997年,你不能只是下载这些作品)。此示例在另一个上面构建了一个层。有一个网络层,一个协议层(使用XDR实现可移植性)等。在某种程度上,它类似于TCP中的分层 - 请注意发送方如何以与接收方剥离它们相反的顺序添加到传输的单元。这种方法的一个关键部分是,下层可以被彻底证明是坚如磐石的,并且可以在各个地方重复使用。大多数错误都不会出现在这些层中,我们不必担心它们。这大大减少了调试时间。
对于后者的一个例子 - 我在另一个建立在IRC之上的网络应用程序上工作。可悲的是,在这种情况下,没有分层。这段代码让我想起了我早年看到的意大利面条代码。在该项目中,有一个User对象一直用于网络层。那是一场灾难。 User对象中的某些错误可能会将问题一直分解到代码将内容放在网络上的位置。我已经看到了我不得不抛弃代码并重新开始的情况。我想在这种情况下这样做,但政治环境阻止了它。
是否适合每个应用分层?可能不是。但是当你构建一个任何复杂程度的应用程序时,你应该拥有它。这是帮助我们遵守分离关注规则的一种机制。
答案 6 :(得分:0)
这不一定是人为的,但在实践中很难实现。
给出UI和域层的示例:域对象的目的只是存储与要解决的问题有关的信息,并公开用于修改该信息的方法和属性以及何时进行更改的通知。这需要零耦合到上层 - UI。
你指出“这显然是一件坏事,因为它无法重新使用UI作为提供Y-ish功能的其他应用程序的前端。”推广用户界面,使其可以容纳具有类似功能/意图的无数域模型,这不是我熟悉的设计目标,我也不相信它是DDD的设计目标。我想像MVC这样的模式可以允许它,但这只会将问题移到控制器上。
尽管你走的越远,它就会变得有点愚蠢。如果您正在使用POXO(普通旧[插入语言]对象),那么您的持久性框架在某些时候将需要依赖于它上面和下面。(1)
然而,大多数代码都可以保持向下的依赖图,直到你接近底部。
(1)如果您正在使用DTO,ORM知道您的域模型+数据库,那么它就知道DTO和数据库等。