别在领域模型迷失了自己

原文地址:http://www.cnblogs.com/tsoukw/archive/2007/09/28/908983.html

本不想对这个图书馆再掀话题﹐看了亚同志的重构图书馆惊魂夜﹐觉得还是有必要完整地解释一下图书馆与领域模型﹐毕竟这个问题由我而起﹐善终一下吧。

首先把图书馆系统的背景说明一下吧﹕公司每个成员通过局域网登录图书管理系统﹐然后预借书籍﹐图书管理员收到预借信息后﹐核准借阅﹐并通知借书人前来领书﹐告知相关事项。

领域模型的价值不在于它的设计优美(它只是一些对象﹐最重要的也就是对象之间的关系)﹐而在于它体现了系统的核心价值。什么是系统的核心价值呢?我想我们的图书馆系统和华尔街的一个商业系统本质的区别应该不是用了什么语言﹐OO还是过程﹐用了什么数据库。因为这两个系统使用什么语言﹐采用什么方式﹐利用什么DBMS﹐都是不能本质区别这两个系统的价值的。

那什么才是系统真正的价值呢?

那就是﹕系统能为使用提供什么服务﹐以及提供的质量(即如何服务﹐这也是系统内部的我们要做的事情)。

那什么体现的系统的服务呢?系统的运行方式﹐系统的运行过程﹐系统的业务逻辑。

以数据库为中心的开发方式﹐将所有业务逻辑都放在了数据库﹐并结合系统的代码来保证业务逻辑的实现。

而面向对象﹐则其表现点则是直接在对象本身上﹐在于对象之间真正的交互过程﹐结果也是保留在对象的属性和对象与对象的关系中。

从以上方式可以看出﹐无论以数据库为中心的开发如何的OO﹐如何多的设计模式﹐架构体系如何优美﹐它始终离不开数据库。它的OO只是一种手段﹐而非目的。(大家别激动﹐我始终没有提这种方式不好﹐只是实现系统的两种方法而已)

面向对象则不同﹐它将逻辑直接存在于对象上﹐这与现实情况是吻合的﹐这也是面向对象引起这么大热潮的根本原因(我想﹐不明白这一点﹐您会在ORM,AOP等等方面不知其所以然的)。

领域模型是一种思维﹐是一种方法,是在系统分析阶段使用﹐而不是程序员在自己的代码中进行纯设计时的工具。我们不是为了OO而领域﹐不是为了最终要新增数据库而领域﹐这也是为什么在没有理解领域模型本质时﹐使用它进行片断式的代码编写﹐得不到任何好处的原因。想想你在编码中弄出的那些用户﹐图书对象﹐它在系统中的位置是什么?您不能否认它就是为了让新增数据库时更方便吧?

领域模型的建立决不是像如何实现某些纯软件逻辑而进行的纯软件设计。(我这里提出的纯软件设计﹐指的是如何在设计中体现扩展性﹐灵活性﹐可维护性而进行的设计)。而是为了能够分析系统提供的服务而产生的一种思维结果。

系统分析员在接手一个系统后﹐首先要做到的事情就是得出系统的服务和服务场景。也就是我们经常所讲的用例(use case)

可惜的是﹐很多使用者一直将用例的作用和价值没弄清楚。我想如果没有真正理解用例的作用时﹐您的用例分析将会一直停留在和别人Demo系统之上﹐提供一個漂亮的圖形好說話而已﹐而不会对我们的系统开发有任何实质作用。

用例表示的是使用系统的一个场景﹐其本质在于详细描述了系统用户(actor)与系统是如何交互的﹐以及交互的后果是什么﹐详细而完善的用例将指导您进行系统开发的全过程?

还以图书管理系统的借书为例﹐这是用例的文本描述(也是真正的用例)﹕

系统﹕显示图书馆的所有书籍类别

用户﹕浏览并选择一个书籍类别

系统﹕显示这个书籍类别的所有书籍

用户﹕浏览并选择一本书籍

系统﹕显示书籍的详细信息

用户﹕借阅书籍

系统﹕办理预借手续

用例后果﹕产生一份预借记录。并将预借记录与书籍关联。

领域模型如何提取的具体办法就不讲了﹐其实在基本的面向对象的书籍中都有提到过﹐一个最简单的方法就是从语句中的名词找出领域对象。

经分析﹐这里的领域模型有﹕图书馆﹐书籍类别﹐书籍﹐预借记录(所有类别﹐所有书籍﹐详细信息这些也是名词﹐但是经过分析﹐它们分别是属于图书馆﹐书籍类别﹐书籍的属性﹐而不是领域模型)

领域模型中最重要的是领域模型之间的关系(当然还有属性)﹐我们的系统的逻辑也是体现在领域对象与对象之间的关系上(如您借了一本设计模式的书﹐在系统中就是设计模式这本书对象与借阅记录对象关联)。

得到领域模型后﹐再根据用例﹐我们分析出领域模型对象的交互过程。

请注意﹐这里的显示****,并没有UI过程﹐实际上就是返回这个对象的某个属性﹐至于如何显示﹐那是UI层的故事。

根据”借阅”用例的描述﹐我们不难得到用例的业务过程的代码表示了﹕

借阅记录  aaa = new 借阅记录

设计模式.借阅记录 = aaa;

很简单﹐就是产生一个借阅记录对象﹐并将它与书籍对象关联﹗

依照迭代过程﹐我们对所有的用例都完成这样的建模过程。系统也就完成了。

在不断的迭代过程中﹐有很多属性呀﹐过程呀﹐都会逐步地由浅至深﹐最终完全符合了用户的要求。

在完成这些过程之后﹐一个系统的领域模型就建造完毕。所有系统的本质属性都体现在这些领域模型的活动过程之中。

一个系统要变更业务逻辑﹐我们只要针对领域模型作变化即可﹐再也不需要抱怨变化了。

终于要考虑系统其它部分了。

数据库﹐日志﹐邮件通知﹐当然还有UI(在之前的评论中有详细描述﹐感兴趣的朋友可以去找找这几天的评论)

这些都属于系统核心以外—领域模型以外的东西。

这些模块与领域模型的接触在哪里呢?当然不能在领域模型内部﹐您不能在借阅逻辑中加入这些对象。

只有一个地方﹐那就是边界。

系统边界﹐即域控制器﹐即上面顺序图的”系统”对象所处的位置﹐系统对象除了与领域模型﹐用户打交道以外﹐它还会与系统的其它模块交互。如持久化系统(您不能因为客户由sql server要求转向oracle而去更改业务逻辑吧)﹐日志系统(您不希望修改日志规则而影响业务逻辑吧)﹐信息通知系统(您不能因为用户要求由邮件通知改为短信通知就修改领域模型吧)。

将领域模型独立于持久化系统的真正原因﹕系统业务逻辑与如何存储数据以便使系统正常持续运行无任何关系。

当然﹐还有设计原则也会支持这种做法。持久化系统依赖领域模型时﹐只有领域模型本身变动才需要修改持久化过程﹐反之则不然﹐这正是依赖倒置原则的一个体现。

我稍微写一下域控制器的代码实现过程吧﹕

Public void 借阅()

{

//这个借阅处理者是纯粹的软件对象﹐其存在的目的就是将借阅的实际处理过程独立出来

借阅处理者 处理者 = new 借阅处理者(当前书籍﹐当前登录人姓名);

Bool successful = 处理者.借阅()            //這個方法主要就是上面的那2行代碼

If(successful){

持久化系统.add(当前书籍);

日志系统.add日志(当前当籍,”借阅”)

邮件系统.发送邮件(当前书籍.当前借书人姓名)

}

}

Void 持久化系统.add(书籍 当前书籍)

{

借阅关系 Bbb = new 借阅关系

Bbb.id = 产生ID()

Bbb.图书ID = 当前书籍.id;

Bbb.借阅人 = 当前书籍.借阅记录.借阅人姓名

Bbb.借阅时间 = 当前书籍.借阅记录.借阅时间

Bbb.Save();

}

其它模块处理过程类似。

從這里可以看出﹐業務過程是系統的核心﹐其它模塊都是依賴于它而存在。

如果有ORM﹐它使用的地方就是这里了。

在现实系统中﹐我在if(successful)这里作了一些纯软件设计﹐如利用C#具有Event特性﹐将借阅方法后公开出一个事件﹐这样我在再要添加什么外围模声时﹐只要响应事件就可以了﹐不需要再来动这个方法

另外一个不容回避的问题﹐统计报表﹐虽然循环内存中的对象﹐理论上完全可以抓出所有的数据。

但是对于这一类问题﹐我就不麻烦领域对象了(其实它与领域模型﹐业务逻辑也真的没有关系)﹐而是将它独立实现﹐直接调用持久化模块﹐下sql抓资料。

还有一个实际的问题﹐就是对象的加载。理论上﹐每次系统重启后﹐都要将所有内存中的对象全部还原﹐构建领域环境。

然而对于大部分实际运用的系统﹐这是不可能的﹐如这里的图书馆对象﹐它可能有上千本书﹐因此一次加载肯定不行﹐我使用的方法是﹐利用Proxy﹐继承领域模型﹐延迟加载。关于这一点﹐就属于纯设计问题了﹐如何实现﹐欢迎大家有兴趣再探讨﹗

(請教一下大家用什么方法發表blog呀﹐我每次從word中貼過來好痛苦)

------------------------------

2011.1.22补录

此篇内容已过时

领域驱动真实实践时还会遇到很多问题

目前我的看法是:使用数据库,才是真正的问题解决之道

时间: 2025-02-01 12:03:57

别在领域模型迷失了自己的相关文章

拨开迷雾,找回自我:DDD(领域驱动设计)应对具体业务场景,Domain Model(领域模型)到底如何设计?

写在前面 阅读目录: 迷雾森林 找回自我 开源地址 后记 毫无疑问,领域驱动设计的核心是领域模型,领域模型的核心是实现业务逻辑,也就是说,在应对具体的业务场景的时候,实现业务逻辑是领域驱动设计最重要的一环,在写这篇博文之前,先总结下之前关于 DDD(领域驱动设计)的三篇博文: 我的“第一次”,就这样没了:DDD(领域驱动设计)理论结合实践:伪领域驱动设计,只是用 .NET 实现的一个“空壳”,仅此而已. 一缕阳光:DDD(领域驱动设计)应对具体业务场景,如何聚焦 Domain Model(领域模

领域模型:聚合、聚合根详解

聚合和聚合根是领域模型里面很重要的一个概念,其实我们在从真实世界对业务对象进行识别和概念建模的时候,关注的就是聚合根,这才是我们真正要管理的业务对象.一个对象可能有多个层次,也可能有多个子实体,但是这些子实体都不可能孤立存在,它们必须依附于一个聚合根存在,它们和根节点具有同样的生命周期. 如果一个客户消亡,客户联系方式,客户的多张银行账户信息将不再有任何意义.如果一张采购订单头消失,那么采购订单明细没有任何存在的意义.客户,采购订单,发票这些从真实业务中转化过来的业务对象才是真正的领域核心对象.

BZOJ 2878: [Noi2012]迷失游乐园( 树形dp )

一棵树的话直接树形dp(求出往下走和往上走的期望长度). 假如是环套树, 环上的每棵树自己做一遍树形dp, 然后暴力枚举(环上的点<=20)环上每个点跑经过环上的路径就OK了. --------------------------------------------------------------------------------------------- #include<cstdio> #include<cstring> #include<algorithm&

CloudNotes之领域建模篇:领域模型简介

CloudNotes领域模型还是相对简单的,并不一定需要采用面向领域驱动的设计方法来解决CloudNotes的领域问题.但出于以下几个方面的原因,我还是采用了面向领域驱动的方式来开发CloudNotes: 领域驱动是企业级应用开发的一种指导性模型,以领域模型作为软件开发的中心,符合解决问题的基本思路 现有的企业级应用开发框架对面向领域的开发模式支持得越来越好,如果选用这种方式,可以在CloudNotes中更好地利用这些框架的最新功能,为系统开发寻求新的机遇 自己对领域驱动设计相对比较熟悉,而且也

.NET应用架构设计—适当使用活动记录模式代替领域模型模式

阅读目录: 1.背景介绍 2.简单介绍领域模型模式.活动记录模式 3.活动记录模式的简单示例及要点 4.总结 1.背景介绍 对软件开发方法论有兴趣的博友应该发现最近"领域驱动设计"慢慢的被人发现被人实践起来,园子里也慢慢有了DDD的学习气氛和宝贵实战经验的分享.其实之前我也痴迷于DDD,为什么会痴迷于它并不是因为它是所谓的新技术,也不是因为各种对它的炒作,而是我觉得我找到了能解放我们进行企业业务系统开发的方法论. DDD可以很好的指导我们开发可靠的软件系统,尤其是现在的企业业务复杂多变

迷失的邮票

描述 L不但喜欢收集邮票,而且还喜欢成对的收集每一种邮票.为了防止混乱,他用一个正整数对每一种邮票进行编号.然而,有一天他却丢失了某两张邮票.已知这两张邮票属于不同的类型,即它们的编号不一样.L希望你能帮他找到这两张丢失邮票的编号. 输入 每个测试数据的输入第一行是一个正整数n(4<=n<=1000000),表示原来L拥有邮票的张数接下去有n-2行,每一行一个正整数x(1<=x<=2^31-1),表示邮票的编号. 输出 每个测试数据的输出只有一行,两个正整数,表示丢失邮票的编号,小

迷失的心,不愿回头

曾以为,只有心的自由,才能赋予笔下文字的灵性,就放任了心的漂流,独步于寂寞的荒原,等候一朵花开. 曾以为,只有情的温暖,才能融化封锁心灵门窗的冰霜,就纵容了心火的燃烧,燃情于忧伤的国度,期待着一扇为我打开的窗. 曾以为,只有灵魂的交融,才能点燃平淡生活的激情,就放飞了相思的触手,飞翔于梦的夜空,找寻心底的那个坐标. 只是,走得久了,累了,才发现已经回不去了.哭得久了,痛了,才明白已经停不下来了.飞得久了,冷了,才知道已经迷失方向了. 走到现在才发觉,已经回不去了.感觉像个孩子在夜晚迷了路,周围只

VO、DTO与领域模型的概念

业务对象模型(也叫领域模型 domain model)是描述业务用例实现的对象模型.它是对业务角色和业务实体之间应该如何联系和协作以执行业务的一种抽象.业务对象模型从业务角色内部的观点定义了业务用例.该模型为产生预期效果确定了业务人员以及他们处理和使用的对象("业务类和对象")之间应该具有的静态和动态关系.它注重业务中承担的角色及其当前职责.这些模型类的对象组合在一起可以执行所有的业务用例. 参考博文: 领域模型的概念 VO.DTO与领域模型

事务脚本-领域模型

Martin Fowler定义是: 事务脚本,将所有逻辑组织在一个单一过程,进行数据库直接调用,每个业务请求都有自己的事务脚本,并且是一个类的公开方法. 领域模型,是一系列相互关联的对象,每个对象代表一定意义的独立体,既可以一起以一种大规模方式协作:也可以小到以单线方式运行. 事务脚本总体来说:就像直奔主题,平铺直叙,就功能谈功能,直接没有回旋余地:领域模型给人感觉好像肚子里就那么点货而领域模型则象是文人骚客,上了一个档次,会使用美妙表达方式,有余地. 比如唐诗:清明时节雨纷纷,路上行人欲断魂: