
加一个导航关于如何设计聚合的详细思考见这篇文章。2004年Eric Evans 发表Domain-Driven Design –Tackling Complexity in the Heart of Software 领域驱动设计简称Evans DDD。领域驱动设计分为两个阶段以一种领域专家、设计人员、开发人员都能理解的通用语言作为相互交流的工具在交流的过程中发现领域概念然后将这些概念设计成一个领域模型由领域模型驱动软件设计用代码来实现该领域模型由此可见领域驱动设计的核心是建立正确的领域模型。为什么建立一个领域模型是重要的领域驱动设计告诉我们在通过软件实现一个业务系统时建立一个领域模型是非常重要和必要的因为领域模型具有以下特点领域模型是对具有某个边界的领域的一个抽象反映了领域内用户业务需求的本质领域模型是有边界的只反应了我们在领域内所关注的部分领域模型只反映业务和任何技术实现无关领域模型不仅能反映领域中的一些实体概念如货物书本应聘记录地址等还能反映领域中的一些过程概念如资金转账等领域模型确保了我们的软件的业务逻辑都在一个模型中都在一个地方这样对提高软件的可维护性业务可理解性以及可重用性方面都有很好的帮助领域模型能够帮助开发人员相对平滑地将领域知识转化为软件构造领域模型贯穿软件分析、设计以及开发的整个过程领域专家、设计人员、开发人员通过领域模型进行交流彼此共享知识与信息因为大家面向的都是同一个模型所以可以防止需求走样可以让软件设计开发人员做出来的软件真正满足需求要建立正确的领域模型并不简单需要领域专家、设计、开发人员积极沟通共同努力然后才能使大家对领域的认识不断深入从而不断细化和完善领域模型为了让领域模型看的见我们需要用一些方法来表示它图是表达领域模型最常用的方式但不是唯一的表达方式代码或文字描述也能表达领域模型领域模型是整个软件的核心是软件中最有价值和最具竞争力的部分设计足够精良且符合业务需求的领域模型能够更快速的响应需求变化领域通用语言UBIQUITOUS LANGUAGE我们认识到由软件专家和领域专家通力合作开发出一个领域的模型是绝对需要的但是那种方法通常会由于一些基础交流的障碍而存在难点。开发人员满脑子都是类、方法、算法、模式、架构等等总是想将实际生活中的概念和程序工件进行对应。他们希望看到要建立哪些对象类要如何对对象类之间的关系建模。他们会习惯按照封装、继承、多态等面向对象编程中的概念去思考会随时随地这样交谈这对他们来说这太正常不过了开发人员就是开发人员。但是领域专家通常对这一无所知他们对软件类库、框架、持久化甚至数据库没有什么概念。他们只了解他们特有的领域专业技能。比如在空中交通监控样例中领域专家知道飞机、路线、海拔、经度、纬度知道飞机偏离了正常路线知道飞机的发射。他们用他们自己的术语讨论这些事情有时这对于外行来说很难直接理解。如果一个人说了什么事情其他的人不能理解或者更糟的是错误理解成其他事情又有什么机会来保证项目成功呢在交流的过程中需要做翻译才能让其他的人理解这些概念。开发人员可能会努力使用外行人的语言来解析一些设计模式但这并一定都能成功奏效。领域专家也可能会创建一种新的行话以努力表达他们的这些想法。在这个痛苦的交流过程中这种类型的翻译并不能对知识的构建过程产生帮助。领域驱动设计的一个核心的原则是使用一种基于模型的语言。因为模型是软件满足领域的共同点它很适合作为这种通用语言的构造基础。使用模型作为语言的核心骨架要求团队在进行所有的交流是都使用一致的语言在代码中也是这样。在共享知识和推敲模型时团队会使用演讲、文字和图形。这儿需要确保团队使用的语言在所有的交流形式中看上去都是一致的这种语言被称为“通用语言Ubiquitous Language”。通用语言应该在建模过程中广泛尝试以推动软件专家和领域专家之间的沟通从而发现要在模型中使用的主要的领域概念。将领域模型转换为代码实现的最佳实践拥有一个看上去正确的模型不代表模型能被直接转换成代码也或者它的实现可能会违背某些我们所不建议的软件设计原则。我们该如何实现从模型到代码的转换并让代码具有可扩展性、可维护性高性能等指标呢另外如实反映领域的模型可能会导致对象持久化的一系列问题或者导致不可接受的性能问题。那么我们应该怎么做呢我们应该紧密关联领域建模和设计紧密将领域模型和软件编码实现捆绑在一起模型在构建时就考虑到软件和设计。开发人员会被加入到建模的过程中来。主要的想法是选择一个能够恰当在软件中表现的模型这样设计过程会很顺畅并且基于模型。代码和其下的模型紧密关联会让代码更有意义并与模型更相关。有了开发人员的参与就会有反馈。它能保证模型被实现成软件。如果其中某处有错误会在早期就被标识出来问题也会容易修正。写代码的人会很好地了解模型会感觉自己有责任保持它的完整性。他们会意识到对代码的一个变更其实就隐含着对模型的变更另外如果哪里的代码不能表现原始模型的话他们会重构代码。如果分析人员从实现过程中分离出去他会不再关心开发过程中引入的局限性。最终结果是模型不再实用。任何技术人员想对模型做出贡献必须花费一些时间来接触代码无论他在项目中担负的是什么主要角色。任何一个负责修改代码的人都必须学会用代码表现模型。每位开发人员都必须参与到一定级别的领域讨论中并和领域专家联络。领域建模时思考问题的角度“用户需求”不能等同于“用户”捕捉“用户心中的模型”也不能等同于“以用户为核心设计领域模型”。 《老子》书中有个观点有之以为利无之以为用。在这里有之利即建立领域模型无之用即包容用户需求。举些例子一个杯子要装满一杯水我们在制作杯子时制作的是空杯子即要把水倒出来之后才能装下水再比如一座房子要住人我们在建造房子时建造的房子是空的唯有空的才能容纳人的居住。因此建立领域模型时也要将用户置于模型之外这样才能包容用户的需求。所以我的理解是我们设计领域模型时不能以用户为中心作为出发点去思考问题不能老是想着用户会对系统做什么而应该从一个客观的角度根据用户需求挖掘出领域内的相关事物思考这些事物的本质关联及其变化规律作为出发点去思考问题。领域模型是排除了人之外的客观世界模型但是领域模型包含人所扮演的参与者角色但是一般情况下不要让参与者角色在领域模型中占据主要位置如果以人所扮演的参与者角色在领域模型中占据主要位置那么各个系统的领域模型将变得没有差别因为软件系统就是一个人机交互的系统都是以人为主的活动记录或跟踪比如论坛中如果以人为主导那么领域模型就是人发帖人回帖人结贴等等DDD的例子中如果是以人为中心的话就变成了托运人托运货物收货人收货物付款人付款等等因此当我们谈及领域模型时已经默认把人的因素排除开了因为领域只有对人来说才有意义人是在领域范围之外的如果人也划入领域领域模型将很难保持客观性。领域模型是与谁用和怎样用是无关的客观模型。归纳起来说就是领域建模是建立虚拟模型让我们现实的人使用而不是建立虚拟空间去模仿现实。以Eric EvansDDD之父在他的书中的一个货物运输系统为例子简单说明一下。在经过一些用户需求讨论之后在用户需求相对明朗之后Eric这样描述领域模型一个Cargo货物涉及多个Customer客户如托运人、收货人、付款人每个Customer承担不同的角色Cargo的运送目标已指定即Cargo有一个运送目标由一系列满足Specification规格的Carrier Movement运输动作来完成运输目标从上面的描述我们可以看出他完全没有从用户的角度去描述领域模型而是以领域内的相关事物为出发点考虑这些事物的本质关联及其变化规律的。上述这段描述完全以货物为中心把客户看成是货物在某个场景中可能会涉及到的关联角色如货物会涉及到托运人、收货人、付款人货物有一个确定的目标货物会经过一系列列的运输动作到达目的地其实我觉得以用户为中心来思考领域模型的思维只是停留在需求的表面而没有挖掘出真正的需求的本质我们在做领域建模时需要努力挖掘用户需求的本质这样才能真正实现用户需求关于用户、参与者这两个概念的区分可以看一下下面的例子试想两个人共同玩足球游戏操作者用户是驱动者它驱使足球比赛领域中各个“人”参与者的活动。这里立下一个假设假设操作者A操作某一队员a而队员a拥有着某人B的信息那么有以下说法a是B的镜像a是领域参与者A是驱动者。领域驱动设计的经典分层架构用户界面/展现层负责向用户展现信息以及解释用户命令。更细的方面来讲就是请求应用层以获取用户所需要展现的数据发送命令给应用层要求其执行某个用户命令应用层很薄的一层定义软件要完成的所有任务。对外为展现层提供各种应用功能包括查询或命令对内调用领域层领域对象或领域服务完成各种业务逻辑应用层不包含业务逻辑。领域层负责表达业务概念业务状态信息以及业务规则领域模型处于这一层是业务软件的核心。基础设施层本层为其他层提供通用的技术能力提供了层间的通信为领域层实现持久化机制总之基础设施层可以通过架构和框架来支持其他层的技术需求领域驱动设计过程中使用的模式所有模式的总揽图关联的设计关联本身不是一个模式但它在领域建模的过程中非常重要所以需要在探讨各种模式之前先讨论一下对象之间的关联该如何设计。我觉得对象的关联的设计可以遵循如下的一些原则关联尽量少对象之间的复杂的关联容易形成对象的关系网这样对于我们理解和维护单个对象很不利同时也很难划分对象与对象之间的边界另外同时减少关联有助于简化对象之间的遍历对多的关联也许在业务上是很自然的通常我们会用一个集合来表示1对多的关系。但我们往往也需要考虑到性能问题尤其是当集合内元素非常多的时候此时往往需要通过单独查询来获取关联的集合信息关联尽量保持单向的关联在建立关联时我们需要深入去挖掘是否存在关联的限制条件如果存在那么最好把这个限制条件加到这个关联上往往这样的限制条件能将关联化繁为简即可以将多对多简化为1对多或将1对多简化为1对1实体Entity实体就是领域中需要唯一标识的领域概念。因为我们有时需要区分是哪个实体。有两个实体如果唯一标识不一样那么即便实体的其他所有属性都一样我们也认为他们两个不同的实体因为实体有生命周期实体从被创建后可能会被持久化到数据库然后某个时候又会被取出来。所以如果我们不为实体定义一种可以唯一区分的标识那我们就无法区分到底是这个实体还是哪个实体。另外不应该给实体定义太多的属性或行为而应该寻找关联发现其他一些实体或值对象将属性或行为转移到其他关联的实体或值对象上。比如Customer实体他有一些地址信息由于地址信息是一个完整的有业务含义的概念所以我们可以定义一个Address对象然后把Customer的地址相关的信息转移到Address对象上。如果没有Address对象而把这些地址信息直接放在Customer对象上并且如果对于一些其他的类似Address的信息也都直接放在Customer上会导致Customer对象很混乱结构不清晰最终导致它难以维护和理解值对象Value Object在领域中并不是没一个事物都必须有一个唯一标识也就是说我们不关心对象是哪个而只关心对象是什么。就以上面的地址对象Address为例如果有两个Customer的地址信息是一样的我们就会认为这两个Customer的地址是同一个。也就是说只要地址信息一样我们就认为是同一个地址。用程序的方式来表达就是如果两个对象的所有的属性的值都相同我们会认为它们是同一个对象的话那么我们就可以把这种对象设计为值对象。因此值对象没有唯一标识这是它和实体的最大不同。另外值对象在判断是否是同一个对象时是通过它们的所有属性是否相同如果相同则认为是同一个值对象而我们在区分是否是同一个实体时只看实体的唯一标识是否相同而不管实体的属性是否相同值对象另外一个明显的特征是不可变即所有属性都是只读的。因为属性是只读的所以可以被安全的共享当共享值对象时一般有复制和共享两种做法具体采用哪种做法还要根据实际情况而定另外我们应该给值对象设计的尽量简单不要让它引用很多其他的对象因为他只是一个值就像int a 3;那么”3”就是一个我们传统意义上所说的值而值对象其实也可以和这里的”3”一样理解也是一个值只不过是用对象来表示。所以当我们在C#语言中比较两个值对象是否相等时会重写GetHashCode和Equals这两个方法目的就是为了比较对象的值值对象虽然是只读的但是可以被整个替换掉。就像你把a的值修改为”4”(a 4;)一样直接把”3”这个值替换为”4”了。值对象也是一样当你要修改Customer的Address对象引用时不是通过Customer.Address.Street这样的方式来实现因为值对象是只读的它是一个完整的不可分割的整体。我们可以这样做Customer.Address new Address(…);领域服务Domain Service领域中的一些概念不太适合建模为对象即归类到实体对象或值对象因为它们本质上就是一些操作一些动作而不是事物。这些操作或动作往往会涉及到多个领域对象并且需要协调这些领域对象共同完成这个操作或动作。如果强行将这些操作职责分配给任何一个对象则被分配的对象就是承担一些不该承担的职责从而会导致对象的职责不明确很混乱。但是基于类的面向对象语言规定任何属性或行为都必须放在对象里面。所以我们需要寻找一种新的模式来表示这种跨多个对象的操作DDD认为服务是一个很自然的范式用来对应这种跨多个对象的操作所以就有了领域服务这个模式。和领域对象不同领域服务是以动词开头来命名的比如资金转帐服务可以命名为MoneyTransferService。当然你也可以把服务理解为一个对象但这和一般意义上的对象有些区别。因为一般的领域对象都是有状态和行为的而领域服务没有状态只有行为。需要强调的是领域服务是无状态的它存在的意义就是协调领域对象共完成某个操作所有的状态还是都保存在相应的领域对象中。我觉得模型实体与服务场景是对领域的一种划分模型关注领域的个体行为场景关注领域的群体行为模型关注领域的静态结构场景关注领域的动态功能。这也符合了现实中出现的各种现象有动有静有独立有协作。领域服务还有一个很重要的功能就是可以避免领域逻辑泄露到应用层。因为如果没有领域服务那么应用层会直接调用领域对象完成本该是属于领域服务该做的操作这样一来领域层可能会把一部分领域知识泄露到应用层。因为应用层需要了解每个领域对象的业务功能具有哪些信息以及它可能会与哪些其他领域对象交互怎么交互等一系列领域知识。因此引入领域服务可以有效的防治领域层的逻辑泄露到应用层。对于应用层来说从可理解的角度来讲通过调用领域服务提供的简单易懂但意义明确的接口肯定也要比直接操纵领域对象容易的多。这里似乎也看到了领域服务具有Façade的功能呵呵。说到领域服务还需要提一下软件中一般有三种服务应用层服务、领域服务、基础服务。应用层服务获取输入如一个XML请求发送消息给领域层服务要求其实现转帐的业务逻辑领域层服务处理成功则调用基础层服务发送Email通知领域层服务获取源帐号和目标帐号分别通知源帐号和目标帐号进行扣除金额和增加金额的操作提供返回结果给应用层基础层服务按照应用层的请求发送Email通知所以从上面的例子中可以清晰的看出每种服务的职责聚合及聚合根AggregateAggregate Root聚合它通过定义对象之间清晰的所属关系和边界来实现领域模型的内聚并避免了错综复杂的难以维护的对象关系网的形成。聚合定义了一组具有内聚关系的相关对象的集合我们把聚合看作是一个修改数据的单元。聚合有以下一些特点每个聚合有一个根和一个边界边界定义了一个聚合内部有哪些实体或值对象根是聚合内的某个实体聚合内部的对象之间可以相互引用但是聚合外部如果要访问聚合内部的对象时必须通过聚合根开始导航绝对不能绕过聚合根直接访问聚合内的对象也就是说聚合根是外部可以保持 对它的引用的唯一元素聚合内除根以外的其他实体的唯一标识都是本地标识也就是只要在聚合内部保持唯一即可因为它们总是从属于这个聚合的聚合根负责与外部其他对象打交道并维护自己内部的业务规则基于聚合的以上概念我们可以推论出从数据库查询时的单元也是以聚合为一个单元也就是说我们不能直接查询聚合内部的某个非根的对象聚合内部的对象可以保持对其他聚合根的引用删除一个聚合根时必须同时删除该聚合内的所有相关对象因为他们都同属于一个聚合是一个完整的概念关于如何识别聚合以及聚合根的问题我觉得我们可以先从业务的角度深入思考然后慢慢分析出有哪些对象是有独立存在的意义即它是不依赖于其他对象的存在它才有意义的可以被独立访问的还是必须通过某个其他对象导航得到的如何识别聚合我觉得这个需要从业务的角度深入分析哪些对象它们的关系是内聚的即我们会把他们看成是一个整体来考虑的然后这些对象我们就可以把它们放在一个聚合内。所谓关系是内聚的是指这些对象之间必须保持一个固定规则固定规则是指在数据变化时必须保持不变的一致性规则。当我们在修改一个聚合时我们必须在事务级别确保整个聚合内的所有对象满足这个固定规则。作为一条建议聚合尽量不要太大否则即便能够做到在事务级别保持聚合的业务规则完整性也可能会带来一定的性能问题。有分析报告显示通常在大部分领域模型中有70%的聚合通常只有一个实体即聚合根该实体内部没有包含其他实体只包含一些值对象另外30%的聚合中基本上也只包含两到三个实体。这意味着大部分的聚合都只是一个实体该实体同时也是聚合根。如何识别聚合根如果一个聚合只有一个实体那么这个实体就是聚合根如果有多个实体那么我们可以思考聚合内哪个对象有独立存在的意义并且可以和外部直接进行交互。工厂FactoryDDD中的工厂也是一种体现封装思想的模式。DDD中引入工厂模式的原因是有时创建一个领域对象是一件比较复杂的事情不仅仅是简单的new操作。正如对象封装了内部实现一样我们无需知道对象的内部实现就可以使用对象的行为工厂则是用来封装创建一个复杂对象尤其是聚合时所需的知识工厂的作用是将创建对象的细节隐藏起来。客户传递给工厂一些简单的参数然后工厂可以在内部创建出一个复杂的领域对象然后返回给客户。领域模型中其他元素都不适合做这个事情所以需要引入这个新的模式工厂。工厂在创建一个复杂的领域对象时通常会知道该满足什么业务规则它知道先怎样实例化一个对象然后在对这个对象做哪些初始化操作这些知识就是创建对象的细节如果传递进来的参数符合创建对象的业务规则则可以顺利创建相应的对象但是如果由于参数无效等原因不能创建出期望的对象时应该抛出一个异常以确保不会创建出一个错误的对象。当然我们也并不总是需要通过工厂来创建对象事实上大部分情况下领域对象的创建都不会太复杂所以我们只需要简单的使用构造函数创建对象就可以了。隐藏创建对象的好处是显而易见的这样可以不会让领域层的业务逻辑泄露到应用层同时也减轻了应用层的负担它只需要简单的调用领域工厂创建出期望的对象即可。仓储Repository仓储被设计出来的目的是基于这个原因领域模型中的对象自从被创建出来后不会一直留在内存中活动的当它不活动时会被持久化到数据库中然后当需要的时候我们会重建该对象重建对象就是根据数据库中已存储的对象的状态重新创建对象的过程所以可见重建对象是一个和数据库打交道的过程。从更广义的角度来理解我们经常会像集合一样从某个类似集合的地方根据某个条件获取一个或一些对象往集合中添加对象或移除对象。也就是说我们需要提供一种机制可以提供类似集合的接口来帮助我们管理对象。仓储就是基于这样的思想被设计出来的仓储里面存放的对象一定是聚合原因是之前提到的领域模型中是以聚合的概念去划分边界的聚合是我们更新对象的一个边界事实上我们把整个聚合看成是一个整体概念要么一起被取出来要么一起被删除。我们永远不会单独对某个聚合内的子对象进行单独查询或做更新操作。因此我们只对聚合设计仓储。仓储还有一个重要的特征就是分为仓储定义部分和仓储实现部分在领域模型中我们定义仓储的接口而在基础设施层实现具体的仓储。这样做的原因是由于仓储背后的实现都是在和数据库打交道但是我们又不希望客户如应用层把重点放在如何从数据库获取数据的问题上因为这样做会导致客户应用层代码很混乱很可能会因此而忽略了领域模型的存在。所以我们需要提供一个简单明了的接口供客户使用确保客户能以最简单的方式获取领域对象从而可以让它专心的不会被什么数据访问代码打扰的情况下协调领域对象完成业务逻辑。这种通过接口来隔离封装变化的做法其实很常见。由于客户面对的是抽象的接口并不是具体的实现所以我们可以随时替换仓储的真实实现这很有助于我们做单元测试。尽管仓储可以像集合一样在内存中管理对象但是仓储一般不负责事务处理。一般事务处理会交给一个叫“工作单元Unit Of Work”的东西。关于工作单元的详细信息我在下面的讨论中会讲到。另外仓储在设计查询接口时可能还会用到规格模式Specification Pattern我见过的最厉害的规格模式应该就是LINQ以及DLINQ查询了。一般我们会根据项目中查询的灵活度要求来选择适合的仓储查询接口设计。通常情况下只需要定义简单明了的具有固定查询参数的查询接口就可以了。只有是在查询条件是动态指定的情况下才可能需要用到Specification等模式。设计领域模型的一般步骤根据需求建立一个初步的领域模型识别出一些明显的领域概念以及它们的关联关联可以暂时没有方向但需要有111NMN这些关系可以用文字精确的没有歧义的描述出每个领域概念的涵义以及包含的主要信息分析主要的软件应用程序功能识别出主要的应用层的类这样有助于及早发现哪些是应用层的职责哪些是领域层的职责进一步分析领域模型识别出哪些是实体哪些是值对象哪些是领域服务分析关联通过对业务的更深入分析以及各种软件设计原则及性能方面的权衡明确关联的方向或者去掉一些不需要的关联找出聚合边界及聚合根这是一件很有难度的事情因为你在分析的过程中往往会碰到很多模棱两可的难以清晰判断的选择问题所以需要我们平时一些分析经验的积累才能找出正确的聚合根为聚合根配备仓储一般情况下是为一个聚合分配一个仓储此时只要设计好仓储的接口即可走查场景确定我们设计的领域模型能够有效地解决业务需求考虑如何创建领域实体或值对象是通过工厂还是直接通过构造函数停下来重构模型。寻找模型中觉得有些疑问或者是蹩脚的地方比如思考一些对象应该通过关联导航得到还是应该从仓储获取聚合设计的是否正确考虑模型的性能怎样等等领域建模是一个不断重构持续完善模型的过程大家会在讨论中将变化的部分反映到模型中从而是模型不断细化并朝正确的方向走。领域建模是领域专家、设计人员、开发人员之间沟通交流的过程是大家工作和思考问题的基础。在分层架构中其他层如何与领域层交互从经典的领域驱动设计分层架构中可以看出领域层的上层是应用层下层是基础设施层。那么领域层是如何与其它层交互的呢对于会影响领域层中领域对象状态的应用层功能一般应用层会先启动一个工作单元然后对于修改领域对象的情况通过仓储获取领域对象调用领域对象的相关业务方法以完成业务逻辑处理对于新增领域对象的情况通过构造函数或工厂创建出领域对象如果需要还可以继续对该新创建的领域对象做一些操作然后把该新创建的领域对象添加到仓储中对于删除领域对象的情况可以先把领域对象从仓储中取出来然后将其从仓储中删除也可以直接传递一个要删除的领域对象的唯一标识给仓储通知其移除该唯一标识对应领域对象如果一个业务逻辑涉及到多个领域对象则调用领域层中的相关领域服务完成操作注意以上所说的所有领域对象都是只聚合根另外在应用层需要获取仓储接口以及领域服务接口时都可以通过IOC容器获取。最后通知工作单元提交事务从而将所有相关的领域对象的状态以事务的方式持久化到数据库关于Unit of Work工作单元的几种实现方法基于快照的实现即领域对象被取出来后会先保存一个备份的对象然后当在做持久化操作时将最新的对象的状态和备份的对象的状态进行比较如果不相同则认为有做过修改然后进行持久化这种设计的好处是对象不用告诉工作单元自己的状态修改了而缺点也是显而易见的那就是性能可能会低备份对象以及比较对象的状态是否有修改的过程在当对象本身很复杂的时候往往是一个比较耗时的步骤而且要真正实现对象的深拷贝以及判断属性是否修改还是比较困难的不基于快照而是仓储的相关更新或新增或删除接口被调用时仓储通知工作单元某个对象被新增了或更新了或删除了。这样工作单元在做数据持久化时也同样可以知道需要持久化哪些对象了这种方法理论上不需要ORM框架的支持对领域模型也没有任何倾入性同时也很好的支持了工作单元的模式。对于不想用高级ORM框架的朋友来说这种方法挺好不基于快照也不用仓储告诉工作单元数据更改了。而是采用AOP的思想采用透明代理的方式进行一个拦截。在NHibernate中我们的属性通常要被声明为virtual的一个原因就是NHibernate会生成一个透明代理用于拦截对象的属性被修改时自动通知工作单元对象的状态被更新了。这样工作单元也同样知道需要持久化哪些对象了。这种方法对领域模型的倾入性不大并且能很好的支持工作单元模式如果用NHibernate作为ORM这种方法用的比较多一般是微软用的方法那就是让领域对象实现.NET框架中的INotifiyPropertyChanged接口然后在每个属性的set方法的最后一行调用OnPropertyChanged的方法从而显示地通知别人自己的状态修改了。这种方法相对来说对领域模型的倾入性最强。对于不会影响领域层中领域对象状态的查询功能