领域驱动设计 - 层次结构应该如何组织?

45

我对软件开发非常新手。我认为分层架构是降低面向对象软件开发过程中出现的复杂性,同时还能保持代码组织性的好方法。

我有兴趣学习领域驱动设计方法,并且在自我介绍中遇到了一些问题(当然,是初学者级别的)。
它在这里 -

我想建立一个应用程序,将相关人员数据保存在数据库中,并在 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 的理解是架构(最简单的版本)应该如下所示(如果我错了,请纠正我) -
enter image description here
注: 1. 我希望 DataGrid 能够绑定到一些 ObservableCollection,以便能够即时反映任何更改。 2. 这是一个 WPF 应用程序,但不一定要符合 MVVM 模式,而且我故意想使用代码后台。
我的问题是 - 1. 哪种代码属于 Application Layer? 2. 我猜我绝对不应该将我域对象(即 Person)的 ObservableColletion 绑定为 DataGridItmsSource。那么我应该从域对象中提取什么类型的对象呢?如何提取? 3. 为了保持 Presentation LayerDomain Layer 之间的解耦合,可能有一个约定,即“在表示层中不直接实例化域对象”。那么有哪些“非直接”的方法呢? 4. 如果代码后台与 Application Layer 对话,则应该让 Application LayerData Repository 对话吗?但如果需要一些与数据访问无关的域访问(也许不是在此应用程序中,但这种情况可能发生,对吧?)那么在 Domain Layer 中是谁是那个 X 人(子层/模块),Application Layer 应该与之对话? 我知道我的问题非常业余,但它们确实是从我面临的问题提出的问题。所以,如果有人有时间,任何回答都将受到赞赏。
编辑:我不确定 Data Repository 是否应该引用 Domain Model

应用服务可以返回领域对象 *[实现领域驱动设计,第522页]*,但不应该消费它(客户端应该调用应用服务方法来修改对象,而不是直接修改它)。 - Muflix
这里可以找到类似的图层说明:http://dddsample.sourceforge.net/architecture.html - Muflix
1个回答

51

就传统的领域驱动设计而言,通常情况下不允许在领域之外使用领域对象。但是并不存在一个绝对的规则指定领域对象不能在表示层中使用。例如Naked Objects代表了一种思想流派,在这种思想流派中直接使用领域对象。我本人大多数时间遵循一种哲学观点,在其中领域对象不被直接使用,因此我不熟悉他们所建议的所有实践方法。我个人认为将数据直接绑定到领域对象会是不明智的做法,但是请记住,并非所有人都认为这是一项要求。

如果你不允许领域对象超出领域本身,那么你通常会使用DTO或数据传输对象,它们只是具有属性等属性的类,这些DTO类没有领域行为。DTO通常完全反映领域模型结构,但不一定如此。

业务逻辑应该在领域模型中实现,因此应用程序层中的大部分内容涉及协调各种服务,通常是将数据带到客户端应用程序中并从中获取数据。许多人使用某种形式的SOA或至少使用 Web 服务来实现这一点。它们调用存储库,但也需要其他组件(如装配器)来获取从存储库调用返回的领域对象并将属性值复制到DTO中,然后将其序列化并返回给调用者。调用者通常是演示文稿或控制器,但如果您不使用MVC或MVP,则调用者仍然位于表示层中。反向旅程更加复杂-UI可能会发送代表更新或要添加的新对象的DTO。应用程序层的主要目的是协调这些来回活动。

至于领域层的“非数据访问”,有几个典型的例子。大多数人通常将你可能正在考虑的“X”组件称为领域服务。领域服务与应用程序服务不同之处在于它靠近领域模型并具有实际的业务逻辑。

例如,如果一个应用程序涉及到某种订单放置,实际上有两个问题 - 订单放置和订单履行。应用程序服务介导所需数据的传输以制定订单放置,并返回用户希望下订单的订单。但这仅仅是中介数据传输,应用程序服务就到此为止了。然后可能需要一个领域服务来应用业务规则并构建其他领域对象,以实际完成该订单。
总的来说,我发现这是一个可以应用于许多场景的有用概念或隐喻 - 应用程序服务促进某种请求,仅涉及请求提交方面。另一方面,领域服务促进实际请求的履行。
除了面向数据的访问方式之外,我遇到或可以轻松想象的唯一其他访问模式是面向过程的功能。这并不在每个应用程序中都会遇到,但在某些领域中很普遍。例如,在我工作的医疗保健领域中,您可能需要将管理临床数据和临床流程的重要元素纳入应用程序中。解决此问题的方法是不将该过程强调为我的领域模型的一部分,并使用不同的工具进行处理。
面向对象编程技术不适合实际过程本身,它们用于为过程提供数据并从过程中捕获数据。面向对象毕竟也是以名词为主导的。对于实时流程管理,您需要“动词导向编程”而不是“名词导向编程”。工作流工具是“动词导向”的工具,可为既是数据密集型又是流程密集型的应用程序的领域驱动模型提供补充。我做了很多涉及C# DDD模型和工作流基础模型的工作,但这仅适用于某些类型的应用程序。许多 typ 应用程序仅需要领域模型和服务。

最重要的DDD方面不是任何技术或架构,而是围绕着普及语言和与领域专家(在我强烈的意见中是直接交互)进行交互来蒸馏关键领域知识的真正核心。(在我看来,大多数声称进行DDD的公司并没有这样做,因为许多公司拒绝允许业务和开发直接进行交互,但这是另一个话题……)提取和整合领域知识才是实际上将DDD与传统OOP分离的因素,也是DDD真正价值所在的地方。

编辑

就存储库使用而言,图表是正确的。通常情况下,应用程序层总是通过存储库获取领域对象。首先,您必须能够将数据带到应用程序中,大多数应用程序还需要一定程度的查询功能。

然而,领域层通常不会与存储库进行交互。通常情况下,您希望领域模型是自包含的,并从任何特定技术中解耦出来,即它应该代表“纯领域知识”。持久性固有地紧密耦合于某种特定技术,因此一般人都努力使其领域模型不受任何持久化实现的影响。您有存储库,但通常不希望在领域模型中调用存储库方法。

在领域模型内部,对象是作为新对象获取的(可以直接实例化或通过工厂进行实例化),或者通过遍历关联而到达。有时,在创建新对象时,将所有需要的内容都传递给构造函数是不切实际的,因此这是可能需要领域模型本身进行一些数据访问的情况之一。通常人们会通过接口传入数据服务,以便领域模型可以获得数据访问,但仍然与数据层实现解耦。但大多数情况下,领域对象会像与其他已实例化的领域对象相互作用一样运作和交互。


@Sisyphus:我的概念完全赞同你关于领域模型的观点,但我想问的是应用层是否应该调用某种数据访问服务或验证检查服务在领域层中,而不是在领域模型中,然后检查领域约束条件,然后要么调用存储库,要么拒绝请求。比如说,Person的FirstName必须至少包含6个字符。当应用层尝试将Person数据保存到数据库时,哪一层负责执行这样的领域约束检查? - atiyar
3
对于这个例子,我只会在Person对象中设置规则。但是并没有唯一的答案。始终尝试保持实体处于有效状态。逻辑放置的位置可能会有所不同。这是一个激烈辩论的主题 - 可以谷歌“DDD中的验证”查看意见/示例。通常,正确分配值与实体,避免使用setter方法,使用诸如工厂模式创建对象和规范模式验证复杂条件等技术都是有价值的技术,可以使此过程变得不那么棘手。这些技术可以消除许多基本问题。但是对于如何处理复杂的验证情况,没有一个统一的答案。 - Sisyphus
2
最后一句话。不要过于纠结于技术细节。严格按照DDD的书本来做是很困难的,需要经验才能尝试。我参与的第一个DDD项目是在这个概念变得流行之前。我们只有Evans的指导,有限的经验和当时很少的其他指导。我们没有区分价值对象和实体对象,聚合被认为是设计中的概念,但在代码中并没有强制执行。DDD纯粹主义者可能会称其为垃圾,但我们编写了良好的代码,并使应用程序受益。随着经验的积累,您可以改进自己的技术。 - Sisyphus

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接