领域对象与业务逻辑关系设计思路

前一段时间我用面向的对象的方式来写我自己的一个小JEE应用程序,业务逻辑比较简单,就是一些“增删改查”的工作,我一直想把我的小应用程序纳入实际应用中,后来在JDON上看到了大家关于DDD的困惑,我也遇到了业务逻辑该写在什么地方。
经过一番思考之后,我设计出了一个新的编程方式,我认为已经从理论上解决这个问题,所以在这里与大家分享一下。如图:



我的每一个Model都是由“对象”+“上下文”(一个*.properties配置文件,也可没有),model可以完成自己的“增删改查”,Model也可以说是“领域对象”,包含属性和行为,在没有什么业务逻辑的时候,即只有“增删改查”,也就没有必要写service类了。只有当model(领域对象)的某行为包含业务逻辑的时候,才写包含业务逻辑的Service,然后让这个model在调用service完成,时常调用者会把自己作为一个参数传入,意味着自己参与了逻辑。无论逻辑上怎么变化,view与后台交互始终未变,因为view调用的是model的方法。

在说说业务逻辑,其包含:制度,规则,算法。领域对象应该是参与在制度和规则之中,只有算法可以认为是工具。所以我才说Model调用service的时候,如果service是制度和规则描述体,就应该把领域对象自己传入其中。想想你去银行办一张卡,你,柜员,电脑,银行都是交易规则和银行制度的参与者。

你这个想法很好,你将业务逻辑的行为放入Service中,比如你去银行办一张卡,你,柜员,电脑,银行都是交易规则和银行制度的参与者。

这实际上代表了面向函数语言派的一种观点,一个交易动作中涉及到多个实体,我们是不能将交易动作封装到实体模型中,那是不是放入服务中呢?

初步来看,是可以的,如果业务行为比较多,我们就建立粗粒度的大服务,这就是SOA中讲的服务,那么我们的架构就成为以服务为切入点的SOA系统。

所谓一山不容二虎,就象当初数据库和对象两种到底谁为第一一样,因为在实际操作执行中,必须明确先主后次,才有可操作性。关于方向战略的选择上,从来没有两个并列,就象东南西北,你只能选择一个方向。

如果复杂系统中,我们以SOA的服务组件为主,那么无疑我们的领域模型就居于其下,那么我们用什么统一语言和业务专家沟通?

SOA是想让业务专家学会SOA语言,所以,你看到很多软件公司销售员嘴上都是SOA了;而这种将技术架构普及给不懂计算机的业务专家做法,曾经在数据库时代失败过,他们也曾经想把数据库概念让不懂计算机的业务专家去接受。

在我最近写的DDD的PPT中其实已经回答这个问题。答案就在DCI架构。

所以,DCI能够消灭MVC,也能会和SOA进行竞争,当然,你可以把SOA的服务帽子套在DCI上(见这个案例),但是时间久了,人们就要问:还要这顶服务的帽子干什么呢?

有两个很明显的地方,觉得有待商榷:
1)每个model都可以完成“自己”的增删改查?
2)界面只与model打交道?

我的观点如下:
1)如果这个model是个“元素”级别的实体,那么“元素”自身,可以增删改查吗?

举个简单的例子,比如一篇博文(blog),博文没有“生命”或者说并没有“自主行为”,博文的“增删改查”操作,也许放在“博主”(blogger)这个模型中更合适,也更符合常识。

那么博文本身就是简单的数据容器吗?比如,博文本身的格式化与反格式化(解析)的方法(可能会调用一些格式化工具)可以放在博文这个实体之中,这样业务场景或博主调用时,很容易想到。

也就是说,尽管博文本身是没有生命的,但出于方便(而非逻辑),也可以将一些方法放在博文中。

2)如果这个model是个“集合”级别的实体,那么“集合”是可以对其元素进行“增删改查”的。但是领域中总有一个(些)“顶级集合”(容器),其“增删改查”可能要放在service中。

除了这些顶级“集合”的“增删改查”操作,可能要放在service中。一些元素的“增删改查”操作也可以出现在service中。

比如,一些业务场景也许要“查”博文(推荐、搜索等),那么这个“查”就无需去调用“博主”的“查”博文的方法了。

对这两点稍微归纳一下:“增删改查”操作,一些“只”出现在model中(大部分元素级别的实体),一些“只”出现在service中(顶级集合级别的实体),一些则可能同时出现在model和service中(一部分元素级别的实体)。

但“增删改查”无论出现在哪里,都为领域语言所封装,界面不直接看到这些操作,看到的是领域语言所描述的操作,而非数据库语言所描述操作。此外,service可能包含一些复杂的交互,如业务规则、算法等。

就我个人而言,我将“赠删改查”理解为“领域模型或服务”的“数据”的记忆机制,不过目前好像缺少了对“行为”的记忆机制,这也许需要引入动态语言机制。

3)model与service我看作是领域的两个方面,前者包含一个实体自己就可以完成的事情,后者包含诸多实体相互协作方能完成的事情。界面都可以直接使用它们。目前,我一般命名为domain和service,这个和你的model与service应该是相同的划分。

一个实体可以完成的事情相对固定,而诸多实体相互协作可以完成的事情就很多且有良好的扩展性(或者说符合开闭原则)。这也是“领域模型”(实体)相对“领域服务”(服务、场景)相对稳定一些的原因之一吧。

我觉得模型(实体)与服务(场景)是对领域的一种划分,模型关注领域的个体行为,场景关注领域的群体行为,模型关注领域的静态结构,场景关注领域的动态功能。这也符合了现实中出现的各种现象,有动有静,有独立有协作。这样理解,也许更符合常识,也就是说将需要学习和领悟的时间降低到最少。

2011年01月05日 10:29 "jdon007"的内容
模型(实体)与服务(场景)是对领域的一种划分,模型关注领域的个体行为,场景关注领域的群体行为,模型关注领域的静态结构,场景关注领域的动态功能。 ...

jdon007

你可以看一下,我的这一篇blog领域对象设计
这篇文章讨论了,你提的问题。
[该贴被cuwkuhaihong于2011-01-06 12:26修改过]

看了你推荐的文章,浏览一些代码。谈谈对你的说法的认识,看有没有理解错,顺便再解释一下我的观点。

1)每个model都可以完成“自己”的增删改查?
IObject中的I不是表示接口(Interface),而是智能(Intelligent)的意思吧?若是代表接口,这种写法有一本书《clean code》倒是不提倡,我一般也不这么写。智能对象(IObject)具备“可观察”和“自我记忆”(增删改查)的能力。

你举例Object的equlas、clone等方法,认为“只有具备智能的动物或者智能机器人才有的能力做这样的结论,大多数的对象是没有此能力的”。 对于没有智能的object1和object2,其比较只能由某个智能对象执行equals(object1, object2),而不是object1.equals(object2)。

智能对象具有这些方法,自不必多说。但非智能对象也具备这些方法,我觉得更多地考虑到使用上的方便,而非逻辑。且从使用者的角度上看,两种写法只是记法不同而已(一个OO,一个PO),也就是从说使用者的角度来看,其语义都是两个对象(不论是否智能)的比较。

你希望每个Object都天生具有“可观察”(observable)和“自我记忆”(memory)的能力,就像James Gosling设计的Object天生具有“通知”(notify/notifyAll)和“自我克隆”(clone)等能力。从这点来看,我是支持每个模型都可以具有“增删改查的”的操作。

之前我的观点是,“增删改查”的操作,尽量按逻辑分配到各个模型(model)中去,比如博文(非智能对象或元素级别的实体)的“增删改查”就应该由博主(智能对象或集合级别的实体)来做,也就说除了最底层的实体不包含“增删改查”的操作,较高层次的实体总有负责一些逻辑相关的较低层次的实体的“增删改查”操作,当然最顶级的实体的“增删改查”操作可交给service(模型之外)来进行。

我个人认为“非智能”对象的notify、notifyAll、clone等方法,是语义服从于语法的表现,是面向对象语法的约束力所致,此时也许用面向过程的表达形式更合理(不一定更方便),但在一般的OO中,这种情况我们似乎没有别的选择。

所以,总的说来,你所提倡的每个模型都具有自我“增删改查”的操作的“统一做法”是可行的,但是我认为按照逻辑分配这些操作,理解和使用可能更自然一些,分配时可能多费一点周折,但也许是值得的。

2)界面只与model打交道?
最初的MVC含义,就提倡智能的模型(Smart Models),简易的控制(Thin Controllers)和直观的视图(Dumb Views)。你所说的View我觉得意义可能有所缺失,即少了Controller,两者组成User Interface(UI)。暂且认为你所说的View,就是我理解的UI,这点上应该没有什么分歧。

Trygve Reenskau提出DCI范式,就是弥补MVC的缺陷,也就是弥补“智能模型”这一说法的不足。因为这种说法,隐藏了一种观点:无论用户进行任何操作,总有一个智能模型可以完成。

但是呢?总有一些操作的执行,需要多个智能模型相互协作方能完成,这就是场景。用户直接调用某个场景(Service)就可以使用多个智能模型相互协作提供的服务。再者,每个场景包含的服务或业务逻辑,并不适合放在任何一个模型中,因为这是多个模型相互协作才能完成的。所以,界面也要和场景(service或context)打交道。

补充一点:个人认为,Trygve Reenskaug在介绍DCI时,将model和controller中的一些东西抽离出来放入场景context与交互interaction中,这是表示两种范式某种意义上的联系,而不是相互取代的关系。因为MVC中的controller与DCI中的interaction的意义相近,都包含“交互、相互作用”之意,MVC的model与DCI的data的意义相近,都包含“实体、模型”之意。

个人认为equals和clone这两个方法,是为方便而已,按“只有具备智能的动物或者智能机器人才有的能力做这样的结论,大多数的对象是没有此能力的”的方式去想,的确很难信服这两个方法正确,但请注意,思考方式不可能只有一种而已,个人认为equals和clone并不是实体方法,他是技术上需要而已,object1.equals(object2)注意是引用相等,但在DDD中已经说过:区别实体的是实体的唯一标识。

而equals和clone,你可以把“计算机”作为智能体,计算机左手拿着object1,然后从其他地方拿过来object2,对比完事,它觉得相等,则告诉我们相等。对像,值对象,方法,这些是技术上的术语,但回到设计上,是实体,实体状态,实体交互。equals和clone是技术范畴,他是java技术所需要的,当你业务就是判断实体或实体状态相等时,则需要自己定义的智能体,不能单单用equals,例如客服人员判断这个客户是否是同一个(假设这就是一个业务)。

而技术范畴尽可能少入侵领域实体,这是一向所追求的,而增删改查,这些技术尽可能回避实体模型,xml配置的初衷也是这个,反对hibernate反向工程生成的对象作为实体,也是这个原因。

我的回避方法是,把所有Entity放到监控中(监控可设置为即改即存,或者周期储存),即改即存时,当发生修改后,则把“持久事件”发到持久模块,让其持久,事件中是实体状态,并不是实体。可以说我这样的想法源自看到scala中的对象不变性和cqrs的相关知识,在我看来大多数外界对领域的处理都是处理“模型的状态”(不敢说一切处理,我还在学习和探索中……)。
[该贴被SpeedVan于2011-01-07 11:59修改过]

楼主有这样的设计,基本都是以另外一篇所谓的智能对象设计为基础的。
http://www.jdon.com/jivejdon/thread/39157#23131463
我想说的是这个基础就是非常不严谨,而且甚至错误的,详细请见我的回复。所以我无法让自己有理由相信一个没有坚实基础,没有成功案例(只有CRUD的应用还需要设计吗?)的设计是值得我们花时间去实践和讨论的。

2011年01月13日 17:07 "supernavy"的内容
楼主有这样的设计,基本都是以另外一篇所谓的智能对象设计为基础的。
http://www.jdon.com/jivejdon/thread/39157#23131463
我想说的是这个基础就是非常不严谨,而且甚至错误的,详细请见我的回复。所以 ...

软件设计是思想的产物,想不到的东西,又怎么去实践,已经实践了,并且验证其正确可行,讨论还有什么意义。
讨论就是要弥补这种设计思想的瑕疵。感觉你还没有说完就戛然而止了,不严谨在哪里,错误又错在什么地方呢。

请看您的那篇关于智能对象的文章,我有回复在那边的。因为jdon007所质疑的问题也是我的质疑,而你用那篇智能对象的文章作为解释,然而当我我去读那篇文章的时候,就发现真的非常问题,所以实际上你并没有解决jdon007提出的合理问题。这些问题不是什么不经常遇到的问题,而是在企业级应用中非常常见的问题。请问如果连这些问题从理论上都无法解决,真的是很难让人赞同这是我们值得去努力的方向。

我回答jdon007的第一个问题。以前我也关注过这些问题。
我们把Blog.save()设计成Blog.beSaved()怎么样?从语义上讲这是在说它被保存了,而执行保存功能的那个对象是我们不关心的后台处理器,因为这个处理器太通用以至于没必要在程序中过分关注,让它留在后台就行了,我们只关心blog的行为,包括被保存这一行为。你看这样能不能说通?

2011年01月14日 12:46 "supernavy"的内容
请看您的那篇关于智能对象的文章,我有回复在那边的。因为jdon007所质疑的问题也是我的质疑,而你用那篇智能对象的文章作为解释,然而当我我去读那篇文章的时候,就发现真的非常问题,所以实际上你并没有解决jdon007提出的合理问题。这些问题不 ...

首先我承认对于“智能”这个词的使用有些草率了,容易引起歧义。如果把“intelligent ”改为“smart”或许会更好一些,我也没有想好叫什么才更好。
先不管它叫什么;再说remember()方法,其实它也不是重点,因为你可以根据自己的喜好换成save()等类似的方法。这篇文章的目的
通过这样一个示例来说明,对象可以更改其自身的属性,具体如何实现自己决定,而不需要他人干涉的优势。

只有CRUD的系统是没有必要设计的,这样的系统也几乎没有。但是无论多么复杂的系统,都是有对象和逻辑构成,无论逻辑是怎样的
复杂,最后的体现依然是对象属性和数目的变化。如果采用的继承的方式让所有需要存储的对象,都具有变更自身属性的能力,那么我们不就可以
把最大精力放在逻辑上了吗?

要是觉得这样的对象违背习惯性逻辑和认知,那就想想equals和clone是不是同样违背,无论它存在的目的是因为概念简单还是使用方便,
都无法回避其在逻辑和认知的不合理。

如果我们抛弃习惯性的逻辑和认知,走出传统的对象设计--“素描式(有什么属性写什么属性,有什么方法写什么方法)”,多在方便使用和
提高生产力上思考对象设计,是不是更能适应并引领发展呢。


[该贴被cuwkuhaihong于2011-01-15 11:16修改过]

脱离实际的场景,讨论给什么对象赋予什么样的能力是没有任何意义的。没有场景,对象就应该是什么方法都没有。不过你拿这个理由认为Object不应该有equals和clone是不对的。java.lang.Object是有场景的,JDK就是它的场景,JDK要求Object有这两个方法。JDK本身就是产品,你在JDK基础上开发的东西是另外的产品。
回到对象是不是都应该有记住自己的功能的问题上,请你仔细想想下面几个问题,
第一,放在任意场景中,object.remember() 代表什么意思?你可以找10个做不同业务应用的开发人员问一下,看能得到多少种答案?为什么不能理解为把对象的引用保存在某和数据结构里,为什么不能理解为把某个对象复制到某个地方,为什么不能理解为查询是否持有某个对象的引用,等等,等等,我都没敢往业务领域去的想。如果让是设计个机器人的程序,我猜大部分人对


robot.remember()

的直接想法就是,这个机器人能记住某些东西,而不是这个机器人能记住自己。
第二,你的代码或者设计的消费者真的能想你那样觉得,把某个功能加给某个熟知的业务对象会更合理或者容易理解吗?你认为所有开发过账户类的开发者,有多少觉得

account.remember();



AccountRepository.rememberAcount(account);

更容易理解。
第三,remember() 的确不是问题的核心,核心的问题是你希望大家给熟知对象创造性的增加功能。但是创造性的给狗加上注册的功能,恐怕全世界没几个人会想到。可惜你设计的东西不太可能正好是给这几个能这么想的人看的。

总结一下,
1,开放思维,换个角度,让对象有更多的功能。这样的理论等于什么都没说。
2,让狗自己去注册,楼主觉得好理解,轻松,很有成就感,自己是挺满足的。我怎么觉得狗能自己做的事情越多越好呢,只要这个和狗相关的所有的方法,我全加到狗上,岂不是更轻松?为什么举这个例子,其实我想说的是,我们的想法不是用来自娱自乐的,是要被人challenge的。只有经得起challenge的思想才是有可能有价值的。我希望楼主能做到先自己换位思考,自己challenge自己,尤其是自己举的例子。
3,可能我的言辞有些不客气,请原谅,都是针对问题,绝对没有针对个人的意思。而且我觉得jdon讨论的都是比较深层次点的问题,不是对错好坏那么简单,要和你讨论的人要花很多的时间去思考你提出的东西,也是有不小的成本的。我真不希望花不少时间在某个新想法上很多时间之后才发现,原来有这么多基本的问题经不起推敲。

[该贴被supernavy于2011-01-15 16:50修改过]
[该贴被supernavy于2011-01-15 16:57修改过]

1、freebox提出blog.beSaved()是解释的通的,但是按cuwkuhaihong统一处理的思路,还是会遇到难以解释的地方。比如博主blogger.beSaved()似乎可以,但blogger.beModified()博主自己更新自己的个人信息(比如密码),被动式的说法就过于牵强了。不论被动式还是主动式,只要是统一处理,就可能出现“内容”服从“形式”, “语义”服从“语法”的情景。与此相似,面向过程与面向对象的表达方式的统一使用也会出现类似的情景。

按语义分配各种操作,有些操作放在对象之内,有些操作放在对象之外;放在外部的有又分两种,一些操作放在场景(多个对象相互传递消息的活动),一些操作放在另一个对象之中(比如,博主负责博文的撰写,增加博文的操作自然放在博主身上)。这种观点另一种简略的说法就是:一部分操作放在模型之中,一部分操作放在场景之中。

对象职责分配,不是对谁操作就放在谁身上,而谁负责这些操作就放在谁身上。至于有一些操作没有明显的负责人,出于方便,可以采用freebox提出的被动式表达,比如博文的格式化操作可以放在博文身上,这样不论是博主或管理员需要时都可以方便调用。其实,这些无明显责任人的操作用被动或主动的表达方式不是很重要,Object的clone、equals这些无明显责任人的操作不是也没采用吗,用主动的方式倒是可以少些几个字母,也不会妨碍语义的理解。

2、supernavy对多功能对象的说法与我的想法有相似之处,不过对场景的解释却有细微的差别。从cuwkuhaidong的回帖可以看出cuwkuhaidong也认为这与人们习惯的逻辑认知有所出入,只是cuwkuhaidong建议抛弃直观的逻辑,我还是有不同意见。

我想退一步来化解这个问题或者说回归问题的初衷。cuwkuhaidong提出的一种持久化自动完成的方案(Thin),让每个领域对象继承ThinObject这个对象,获取自动记忆的功能,入侵性很明显,这个只是技术特点(如果James Gosling放在Object中,就感觉不到了),逻辑性违背习惯的直观认知,这个很难谁服别人。

退一步的解决方案,就是可以尝试采用AOP的方式,将记忆能力从内部切入到相应的领域对象中。以日志记录为例(AOP的一种应用),使用log4j会为每个类注册一个“记录器”。现在稍微需要改变的是,按照逻辑对各个对象的“记忆体”分配,如博主可能需要的“记忆体”包含自身的“记忆体”(改和查)和博文的“记忆体”(增删改查)。“记忆”中“联想”或“关联”部分应该是很复杂,比如涉及到多个表的条件查询。

示意代码的可能形式:
1)


public class Blogger {
// 参数可以直接接收多个类,与常见的记录有所区别
// 这里需要框架提供的自动实现的两个类的记忆体
private Memory memory = Memory.getMemory(Blogger.class, Blog.classs);

public void writeBlog(Blog blog) {
this.memory.addBlog(blog);
// 或memory.saveBlog(); 等等
}

public void setPassword(String password) {
this.memory.updatePassword(password);
// 或者 memory.modifiedBlogger();等等
}
// 其它的略,仅示意。

}

2)


public class Blogger {
// 参数直接收接收一个类,与常见的记录有所相似
private Memory bloggerMemory = Memory.getMemory(Blogger.class);
private Memory blogMemory = Memory.getMemory(Blog.classs);

public void writeBlog(Blog blog) {
this.blogMemory.addBlog(blog);
// 或memory.saveBlog(); 等等
}

public void setPassword(String password) {
this.bloggerMemory.updatePassword(password);
// 或者memory.modifiedBlogger();等等
}
}


3) 这只是我预想的一些实现方式,实际上我还没有自己研究各种现有持久化框架的优缺点。uda1341曾经跟我提及过他尝试在自己设计的语言实现自动化记忆的功能,这个应该更先进,不过作为过渡,采用框架实现也不失为一种方案。

至于事务一致性的保证与连接的有效利用等等,在持久化框架中可以开放接口,引入一些现有的技术来实现,也可以自己实现。

如果可以这样做到,从某种意义上就真正消灭了“持久层”,因为框架都已经为你做好了一切。进一步,相似的策略甚至可用到“表现层”,将“表现层”(主要是控制器)也消灭掉了,那么就实现了“技术”与“领域”的分离,也可以达到解放程序员的生产力的目标。

剩下的工作就是领域的设计与界面view部分的设计,性能瓶颈可以通过改善底层的记忆机制与增强领域环境的运行能力来实现。这样的框架润物细无声,无入侵性(你高兴用就用,不高兴用就不用,你可以部分使用,也可以完全使用),甚至可以在已有的项目投入使用。
[该贴被jdon007于2011-01-15 19:35修改过]

jdon007,在这篇文章中讨论如何分配持久化这一职责的分配,甚至去留,其实有些跑题了。楼主其实是提出了一种看似具体,可行,而且合理的职责分配的思路而已,而我只是想指出这个具体思路不可行,不合理,经不起推敲。
在我看来,楼主的思路就是,"为什么不把持久化的职责放在领域对象中,这样不是很清楚而且容易理解吗?什么,有点牵强?觉得有点别扭?拜托,请发挥一下想象力,这可是个聪明的领域对象"。我不是说排斥持久化职责放在领域对象中,而是这样的解释实在是无法让人信服。因为我们的大家都是来自于不同行业的,不同领域的人,我们需要的是抽象的,在特定或者所有领域能够并且易于复用的东西。
既然jdon007提出了自己的持久化职责的想法,我就来说说我的看法,
1)所谓大事化小,小事化无。软件架构为什么要分层,不就是大事化小吗。但是这不是最好的结果,最终的目标是化无。这个无不是说没这层了,而是说这层不用我们自己提供了。也不是说和这层没联系了,而是透明了或者尽可能的少。

2) 良好的分层使层次之间的关系变得很清晰,大大增加了化无的了可行性和可能性。其实这样的例子已经不新鲜了。通过xslt,我们不是已经把xml模型的展示层在某些领域化无了吗?其实持久层的化无也已经有了,JPA呀。如果你能够接受持久化不一定是写数据库,写文件也算的化,那么JDK本身提供的序列化不就是把持久化这层化无了吗,上层和持久化层唯一的联系就是实现Serializable接口。

3)在现实情况下,还有很多层,我们还没有时间精力去化无,但是我们还是应该能做点什么。通过合理的职责分配,在设计上封装层之间的联系应该是最直观的方法。回到持久层的问题上。加如我们认可持久化应该作为单独的一层,那么我们应该考虑到让它在将来能够顺利的化无的问题。我想把和持久层有直接联系的部分放在一个集中的地方是一种很直接的想法,而把这些和持久层有联系的部分分布在整个领域模型中对持久层的化无看起来是灾难性的。

以上纯属个人见解,还请纠错。
[该贴被supernavy于2011-01-16 10:42修改过]
[该贴被supernavy于2011-01-16 10:43修改过]