【问题标题】:Is the design notion of layers contrived?层的设计概念是人为的吗?
【发布时间】:2011-03-04 05:01:09
【问题描述】:

我正在阅读 Eric Evans 的出色作品,领域驱动设计。但是,我不禁觉得“层”模型是做作的。为了扩展该声明,它似乎试图将各种概念硬塞到一个特定的、简洁的模型中,即层之间相互对话的模型。在我看来,层模型过于简化,无法真正捕捉(好)软件的工作方式。

进一步扩展:埃文斯说:

“将复杂的程序划分为多个层。在每一层内开发一个具有凝聚力且仅依赖于下层的设计。遵循标准架构模式以提供与上层的松散耦合。”

也许我误解了“依赖”的含义,但据我所知,它可能意味着 a) X 类(例如在 UI 中)引用了具体的 Y 类(在主应用程序中) ) 或 b) X 类具有对提供 Y 类服务的 Y 类对象的引用(即作为接口持有的引用)。

如果它的意思是 (a),那么这显然是一件坏事,因为它无法将 UI 重新用作提供 Y-ish 功能的其他应用程序的前端。 但是如果它的意思是(b),那么 UI 是如何依赖于应用程序的,而不是应用程序依赖于 UI 呢?两者在相互交谈的同时尽可能地相互分离。

Evans 的单向依赖层模型看起来太简洁了。

首先,更准确的说法是,设计的每个区域都提供了一个模块,该模块本身几乎就是一个孤岛,理想情况下,所有通信都是通过接口,在合同驱动/责任驱动的范式中进行的? (即,“仅对较低层的依赖”是人为的)。与域层与数据库通信的情况类似——域层与数据库的解耦(通过 DAO 等)就像数据库与域层的解耦一样。两者都不依赖于另一个,两者都可以交换。

其次,概念上的直线(如从一层到下一层)的想法是人为的 - 不是有更多的相互通信的网络,而是独立的模块,包括外部服务、公用事业服务等,分支不同的角度?

谢谢大家-希望您的回答能澄清我对此的理解..

编辑:事实上,埃文斯后来澄清说,他所说的是情况 (b):“层是松散耦合的,设计依赖只在一个方向上。上层可以直接使用或操纵下层的元素通过调用它们的公共接口,持有对它们的引用。......当一个对象需要向上通信时,我们需要另一种机制......例如 OBSERVERS"

这在我看来完全是人为的。它似乎将设计依赖与交互流程混淆了。 UI 需要对应用程序的引用,因为操作通常是用户发起的!对于应用程序启动的操作,例如一些内部计时器事件提醒用户注意某事,应用程序现在需要对 UI 的引用,就像 UI 需要对应用程序的引用一样。在什么意义上,一种比另一种更“依赖于设计”?为了支持我的观点,为了实现 OBSERVER,应用程序需要一个对 UI 组件的引用列表。

在我看来,参考和设计 SEEM 从顶层到底层的唯一原因是因为这是通常启动操作的地方。当在应用程序中启动操作时,引用必须转到其他方式..

【问题讨论】:

  • 这应该是社区 Wiki。
  • @Bears:希望这个问题有一个实际的答案,并且他正在讨论的 DDD 原则并不是完全主观的。

标签: design-patterns architecture oop domain-driven-design


【解决方案1】:

Eric 对分层架构的描述确实有些混乱。据我了解,他指出好的应用程序有一些层,这些层应该解耦。层的唯一主题是自己的书,书中没有进一步描述(除了你引用的小片段)。

事实上,DDD 并不依赖于分层架构,但我敢打赌,DDD 的所有实际实现都使用架构中的层。 Jeffrey Palermo 提供的关于构建基于 DDD 的解决方案的更好(比 Evans)解释:the onion architecture。 Palermo 详细讨论了每一层的职责和通信路径。我个人在自己的项目中使用这种方法并取得了很大的成功。如果您想了解这在实践中的效果,请查看DDDSample(如果您是 Java 人)或DDDSample.Net(我编写的 DDDSample 的 .NET 端口)

【讨论】:

  • 很棒的答案-谢谢!您已经深入了解了困扰我的问题的核心,Palermo 文章中的洋葱架构看起来更接近于代表性模型。事实上,考虑到本书的其余部分,我很惊讶 Evans 的模型不涉及将域模型作为中心部分。我会看看你的示例代码 - 谢谢!
【解决方案2】:

我同意你的看法。 DDD 中的想法听起来很酷,但很难实现。另请注意,对于具有两个或三个表单的简单 CRUD 应用程序,应用程序本身的分层可能是一个主要的过度杀伤。

但请注意 DDD 中的层架构试图解决的问题。

在面向对象的程序中,UI, 数据库和其他支持代码经常 被直接写入 业务对象。附加业务 逻辑嵌入在行为中 UI 小部件和数据库脚本。这 发生是因为这是最简单的方法 让事情在短期内发挥作用。

当域相关的代码是 通过如此大量的扩散 其他代码,它变得非常 很难看到和推理。 对 UI 的表面更改可以 实际改变业务逻辑。到 更改可能需要的业务规则 UI代码的细致追踪, 数据库代码或其他程序 元素。实施连贯, 模型驱动的对象变成 不切实际的。自动化测试是 尴尬的。凭借所有的技术和 每个活动涉及的逻辑,一个 程序必须保持非常简单,否则 变得无法理解。

请注意这张来自 Microsoft 的图表,它描绘了使用 DDD 很好地实现分层架构。请注意 UI 视图的概念。

【讨论】:

    【解决方案3】:

    你在暗示Dependency inversion principle

    A.高级模块不应该依赖于低级模块。两者都应该依赖于抽象。 B. 抽象不应依赖于细节。细节应该取决于抽象。

    A 部分意味着您的应用程序的任何分区都不会直接依赖于另一个应用程序的实现细节。

    【讨论】:

      【解决方案4】:

      我认为他的意思是“低层”独立于“高层”,因为它们可以独立存在。

      在没有服务层的情况下拥有 UI 层是没有意义的。但是,如果服务公开为 web 服务或 RPC 端点,那么有一个没有 UI 层的服务层是非常有意义的。

      Repository 或 DAO 层类似。

      我同意您的观点,如果这些都很好地解耦,您可以从 UI 下交换服务层(就像使用平面文件“服务层”模型在编写服务层的代码字母之前创建 UI 所做的那样) 或存储库(通常在服务层的集成/单元测试等中换掉。

      但是,存在较低层以使较高层发挥作用的依赖性是一种概念上的单向依赖性。

      【讨论】:

      • 谢谢彼得。我喜欢你回答的方向,但我不明白“独立”是什么意思。如果我尝试它的意思是“本身没有意义”,让我们看看它如何按顺序应用于不同的层。 UI 本身没有意义(但与较低层结合使用时)。是的,同意服务层本身没有意义(但如果与较低层结合使用)。是的,同意域层本身没有意义。嗯 - 它确实有持久层 - 本身没有意义,或者因为没有低层,所以没有意义。所以我还是很困惑..
      【解决方案5】:

      我不认为通过使用接口将模块彼此分层和解耦是相互排斥的,这就是我对您帖子的理解。

      在我的世界中,层与功能范围和编译/运行时依赖项相吻合。另一层之上的一层使用并向其下方的一层添加某种价值(从用户的角度来看)——它通常由多个“孤岛式”模块组成,这些模块完成了一个明确定义的功能。我总是将实现隐藏在接口后面,以减轻更改它的痛苦。这也使我能够编译/构建我的代码,而无需实现每一层,这反过来又使多人一起工作更容易。

      这是一个很好的图表,适用于我构建的大多数严肃的东西:

      UI/API controllers
             |
          A Facade
             |
      Composite Services
             |
      Isolated Services <--> External Services
             |
            DAOs
             |
        Persistance
      

      【讨论】:

        【解决方案6】:

        我已经看到层的想法被非常有效地使用,并且我看到了需要但未使用的应用程序。

        以第一种情况为例。我参与了一个项目,我们为面向服务的架构构建了基础设施和服务。 (我们这样做是因为那是 1997 年,那时你不能只下载这些片段)。此示例在另一层之上构建了一层。有一个网络层、一个协议层(使用 XDR 实现可移植性)等。在某种程度上,它类似于 TCP 中的分层——注意发送方如何以与接收方剥离它们的相反顺序添加到传输单元.这项工作之所以奏效的一个关键部分是,可以彻底证明下层是坚如磐石的,并可以在不同的地方重复使用。大多数错误都没有发生在这些层中,我们不必担心它们。这大大减少了调试时间。

        以后者为例 - 我曾在另一个基于 IRC 的网络应用程序上工作。可悲的是,在这种情况下,没有分层。该代码非常让人想起我早年看到的意大利面条代码。在那个项目中,有一个用户对象一直使用到网络层。那是一场灾难。 User 对象中的某些错误可能会一直破坏到代码将内容放在网络上的位置。我见过我不得不扔掉代码并重新开始的情况。我想在这种情况下这样做,但政治环境阻止了它。

        分层是否适合每个应用程序?可能不是。但是,当您构建任何复杂程度的应用程序时,您应该拥有它。这是帮助我们遵守关注点分离规则的一种机制。

        【讨论】:

          【解决方案7】:

          不一定是人为的,但在实践中很难实现。

          鉴于您的 UI 和域层示例:域对象的目的只是存储与正在解决的问题有关的信息,并公开用于修改该信息的方法和属性以及何时进行更改的通知。这需要与上层 - UI 零耦合。

          您指出“这显然是一件坏事,因为它无法将 UI 重新用作提供 Y-ish 功能的其他应用程序的前端。”概括一个 UI 以使其可以容纳具有相似功能/意图的无数域模型不是我熟悉的设计目标,我也不相信它是 DDD 中的设计目标。我想像 MVC 这样的模式可以允许它,但这只是将问题转移到控制器。

          不过你越往下走,它就会变得有点危险。如果您使用的是 POXO(Plain Old [Insert Language] Objects),那么您的持久性框架在某些时候将需要它上面和下面的依赖项。(1)

          然而,大部分代码可以维持一个仅向下的依赖图,直到你接近底部。

          (1) 如果您使用 DTO,ORM 知道您的域模型 + 数据库,那么它知道 DTO 和数据库等。

          【讨论】:

          • “将 UI 通用化,使其能够适应具有相似功能/意图的无数领域模型,这不是我熟悉的设计目标,我也不相信它是 DDD 中的设计目标” - 我会将网络浏览器归为这一类——它们可以连接到多个后端,这些后端都实现了 http 请求/响应,但没有网络浏览器与一个特定的网站耦合。这就是为什么我认为 Evans 描述的仅向下依赖关系图具有吸引人的简洁性并且易于绘制,但实际上并不能准确地表示真正的依赖关系。
          猜你喜欢
          • 2010-09-24
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-07-16
          • 1970-01-01
          • 2013-12-16
          相关资源
          最近更新 更多