领域驱动设计 - 层应该如何组织?
Posted
技术标签:
【中文标题】领域驱动设计 - 层应该如何组织?【英文标题】:Domain Driven Design - how the layers should be organized? 【发布时间】:2011-08-18 10:05:55 【问题描述】:我对软件开发非常陌生。我认为分层架构是减少面向对象软件开发过程中出现的复杂性的好方法,更不用说保持代码的组织性了。
我有兴趣了解领域驱动设计方法,但我遇到了一些问题来让自己了解它(当然,初学者级别的)。 在这里——
我想构建一个应用程序来将人员相关数据保存在数据库中并在 WPF DataGrid
中显示人员详细信息(我知道,DDD 绝对不适合这种规模的应用程序,而只是为了让业余爱好者保持简单像我这样的)。所以,我创建了一个域类“Person”,类似于 –
public class Person
public Person(dataType paramA)
this.PropertyA = paramA;
private dataType _fieldA;
public dataType PropertyA
//encapsulates _fieldA
public dataType PropertyX
//some code that manipulates private field
private dataType MethodPQR(dataType param)
//some code
现在,我对 DDD 的理解说架构(它最简单的版本)应该如下(如果我错了,请纠正我)-
注意:
我希望DataGrid
绑定到一些ObservableCollection
,以立即反映任何类型的更改。
这是一个 WPF 应用程序,但不一定是 MVVM 模式,我特意想使用后面的代码。
我的问题是 -
什么样的代码属于Application Layer
?
我的猜测是,我绝对不应该将我的域对象(即Person
)的ObservableColletion
绑定为DataGrid
的ItmsSource
。那么我应该从域对象中提取什么类型的对象,以及如何提取?
为了保持Presentation Layer
和Domain Layer
之间的解耦,可能有一个类似never instantiate domain objects directly in the presentation layer
的约定。那么non-direct
方法是什么?
如果代码隐藏与Application Layer
对话,那么Application Layer
是否应该与Data Repository
对话?但是,如果需要某种与 数据访问相关的域访问(可能不在此应用程序中,但它可能会发生,对吗?)在那种情况下,那个X
是谁( Domain Layer
中的子层/模块)Application Layer
应该与之对话?
我知道我的问题属于非常业余的水平,但它们确实是从我面临的问题中提出的问题,以便清楚地了解情况。因此,如果有人有时间,任何回复都将不胜感激。
编辑:我不确定Data Repository
是否应该有Domain Model
的引用。
【问题讨论】:
Application Service 可以返回域对象[Implementing DDD, page 522] 但不应该消费它(客户端应该调用应用服务方法来修改对象,但不应该修改它直接)。 层的类似插图可以在这里找到dddsample.sourceforge.net/architecture.html 【参考方案1】:就更“经典”的 DDD 而言,是的,域对象通常不允许在域之外的任何地方使用。但表示层不使用领域对象也不是绝对的规则。例如,Naked Objects 代表了直接使用领域对象的一种思想流派。我自己主要坚持不直接使用域对象的哲学,所以我不熟悉他们建议的所有实践,我个人认为直接绑定到域对象是不明智的,但是......只要坚持请注意,并非所有人都认为这是一项要求。
如果您不允许域本身之外的域对象,您通常会使用 DTO 或数据传输对象,它们只是具有属性的类,而此类 DTO 类没有域行为。 DTO 通常准确地反映域模型结构,但并非必须如此。
业务逻辑应该在域模型中实现,因此应用层中的大部分内容都涉及协调各种服务,通常是将数据传入和传出客户端应用程序。许多人为此使用某种形式的 SOA 或至少是 Web 服务。这些调用存储库,但也需要其他组件(例如汇编程序)获取从存储库调用返回的域对象并将属性值复制到 DTO,然后可序列化并返回给调用者。调用者通常是演示者或控制器,但如果您不使用 MVC 或 MVP,调用者仍将位于表示层。反向行程更复杂 - UI 可能会发回代表更新的 DTO 或代表要添加的新对象的 DTO。应用层的主要目的是调解这些来回活动。
就领域层的“非数据访问”而言,有几个典型的例子。大多数人通常将您可能认为的“X”组件称为域服务。域服务与应用服务的不同之处在于它接近域模型并且存在实际业务逻辑。
例如,如果一个应用程序涉及某种订单下单,实际上有两个问题 - 下单和订单履行。应用程序服务协调将制定订单放置所需的数据传输到 UI,然后返回用户希望放置的订单。但这只是调解数据传输,这就是应用程序服务结束的地方。然后可能需要域服务来应用业务规则并构建实际履行该订单所需的其他域对象。
总的来说,我发现这是一个有用的概念或比喻,可以应用于许多场景 - 应用程序服务促进某种类型的请求,仅就请求提交而言。另一方面,域服务促进实际请求履行。
除了面向数据之外,我遇到或可以轻松想象的唯一“访问”模式是面向过程的功能。这并不是在每个应用程序中都会遇到,但在某些领域很普遍。例如,在我工作的医疗保健领域,您可能需要包含管理临床数据和临床过程的重要元素的应用程序。我解决了这个问题,方法是不让这个过程强调成为我的领域模型的一部分,而是使用不同的工具来解决这个问题。
OOP 技术不太适合实际流程本身,它们对于向流程提供数据和从流程捕获数据很有用。毕竟,面向对象也主要是面向名词的。对于实时流程管理,您需要“面向动词的编程”而不是“面向名词的编程”。工作流工具是“面向动词”的工具,可以补充数据密集型和流程密集型应用程序的领域驱动模型。我做了很多涉及 C# DDD 模型和 Workflow Foundation 模型的工作,但同样只需要某些类型的应用程序。许多典型的商业应用只需要领域模型和服务。
最后,DDD 最重要的方面不是任何技术或架构。它的真正核心围绕着无处不在的语言以及与领域专家的互动(在我看来是直接互动),以提炼出关键的领域知识。 (我认为大多数声称做 DDD 的公司都没有,因为很多公司拒绝让业务和开发直接交互,但那是另一个话题......)它是领域知识的提取和合并,而不是任何真正将 DDD 与传统 OOP 区分开来的技术,这就是 DDD 的真正价值所在。
编辑
就存储库的使用而言,该图是正确的。通常,应用层总是通过域对象的存储库。首先,您必须能够将数据带到应用程序中,并且大多数应用程序还需要一定程度的查询能力。
OTOH 域层通常不与存储库交互。通常,您希望域模型是自包含的并与任何特定技术分离,即它应该代表“纯领域知识”。持久性本质上与某种特定技术紧密耦合,因此一般来说,人们努力使他们的领域模型摆脱任何持久性实现。您有存储库,但您通常不想在域模型中调用存储库方法。
在领域模型本身中,对象要么作为新对象(可以直接实例化或通过工厂实例化)获得,要么通过遍历关联获得。有时在创建新对象时,将所需的所有内容传递给构造函数是不切实际的,因此这是您可能需要在域模型本身内进行某种数据访问的一种情况。通常人们所做的是通过接口传入数据服务,以便可以为域模型提供数据访问,但仍与数据层实现分离。但在大多数情况下,域对象与已经实例化的其他域对象进行交互。
【讨论】:
@Sisyphus:到目前为止,我的概念完全同意您关于域模型的观点,但我问的是应用层是否应该调用某种数据访问服务或验证检查服务 域层,不在域模型中,它依次检查域约束,然后调用存储库或拒绝请求。假设 Person 的 FirstName 必须至少包含 6 个字符。当应用层尝试将个人数据保存到数据库时,哪一层负责执行此类域约束检查? 对于这个例子,我只需要 Person 对象中的规则。虽然没有人回答。始终尝试使实体保持有效状态。将逻辑放在哪里可能会有所不同。一个激烈争论的话题 - 谷歌“验证 DDD”查看意见/示例。一般来说,正确分配值与实体、避免设置器、使用像工厂这样的模式来创建对象和规范来验证复杂条件是可以减少问题的有价值的技术。这些技术可以消除许多基本问题。但如何处理复杂的验证案例却没有一个答案。 最后的评论。不要太沉迷于技术细节。 “严格按照书本”做DDD很难,需要经验才能真正尝试。我参与的第一个 DDD 项目是在这个想法变得大之前。我们有埃文斯要经过,经验有限,当时几乎没有其他指导。我们没有区分值和实体对象,聚合在设计中被认为是概念,但在代码中没有强制执行。 DDD 纯粹主义者会称其为垃圾,但我们编写了良好的代码并且应用程序从中受益。当您获得更多经验时,您可以改进您的技术。以上是关于领域驱动设计 - 层应该如何组织?的主要内容,如果未能解决你的问题,请参考以下文章