zoukankan      html  css  js  c++  java
  • 【转】EntityFramework之领域驱动设计实践(五)

    原文地址:http://www.cnblogs.com/daxnet/archive/2010/07/07/1772606.html

    聚合

    聚合(Aggregate)是领域驱动设计中非常重要的一个概念。简单地说,聚合是这样一组领域对象(包括实体和值对象),这组领域对象联合起来表 述一个完整的领域概念。比如,根据Eric Evans《领域驱动设计》一书中的例子,一辆车包含四个轮子,轮子离开“车”就毫无意义,此时这个联合体就是聚合,而“车”就是聚合根 (Aggregate Root)。

    从实践中得知,并非领域模型中的每个实体都能够完整地表述一个明确的领域概念,就比如客户与送货地址的关系。假设在某个应用中,系统需要为每个客户 维护多个送货地址,此时送货地址就是一个实体,而不是值对象。那么这样一来,领域模型中至少就有了“客户”和“送货地址”两个实体,而事实上,“送货地 址”是针对“客户”的,离开“客户”,“送货地址”就变得毫无意义。于是,“送货地址”就和“客户”一起,完整地表达了“客户可以有多个送货地址,并能对 它们进行维护”的思想。

    《实体框架之领域驱动实践(三) - 案例:一个简易的销售系统》一文中,我们简单地设计了一个领域模型,其中包含了一些必要的实体和值对象。现在,我用不同颜色的笔在这个领域模型上圈出了三个聚合:客户、订单以及产品分类,如下图所示:

    24150813078

    【注意】:如果像上图所示,Category-Item组成一个聚合,那么此时聚合根就应该是Item,而不是Category, 因为Category对Item从概念上并没有包含/被包含的关系,而更多情况下,Category是 Item的一种信息描述,即某个Item是可以归类到某个Category的。在这种情况下,我们不需要对Category进行维护,Category就 以值对象的形式存在于领域模型中。如果是另一种应用场合,比如,我们的系统需要针对Category进行促销,那么我们需要维护Category的信息, 由此Category和Item就分属两个不同的聚合,聚合根为各自本身。

    首先是“客户-信用卡”聚合,这个聚合表示了一个客户可以拥有多张信用卡,类似于上面所讲的 “客户-送货地址”的概念;其次是“订单-订单行”的聚合,类似地,虽然订单行也是一个实体,因为在应用中需要对每个订单行进行区分,但是订单行离开订单 就变得毫无意义,它是“订单”概念的一部分;最后是“产品分类-产品”的聚合。

    每个聚合都有一个根实体(聚合根,Aggregate Root),这个根实体是聚合所表述的领域概念的主体,外部对象需要访问聚合内的实体时,只能通过聚合根进行访问,而不能直接访问。从技术角度考虑,聚合 确定了实体生命周期的关注范围,即当某个实体被创建时,同时需要创建以其为根的整个聚合,而当持久化某个实体时,同样也需要持久化整个聚合。比如,在从外 部持久化机制重建“客户”对象的同时,也需要将其所拥有的“信用卡”赋给“客户”实体(具体如何操作,根据需求而定)。不要去关注聚合内实体的生命周期问 题,如果你真的这么做了,那么你就需要考虑下你的设计是否合理。

    由此引出了“领域对象生命周期”的问题,这个问题我会在后面两节单独讨论,但目前至少知道:

    1. 领域对象从无到有的创建,不是针对某个实体的,而是针对某个聚合的
    2. 领域对象的持久化(通常所说的“保存”)、重建(通常所说的“查询”)和销毁(通常所说的“删除”)也不是针对某个实体的,而是针对某个聚合的

    很可惜,微软的EntityFramework(实体框架,EF)目前并不支持“聚合”的概念,所有的实体都被一股脑地塞到 ObjectContext中:

    241532814506

    为了实现聚合的概念,我们又一次地需要用到“部分类(partial class)”的功能。我们首先定义一个IAggregateRoot的接口,修改每个聚合根的实体类,使其实现IAggregateRoot接口,如下:

    隐藏行号 复制代码 IAggregateRoot
    1. public interface IAggregateRoot
    2. {
    3. }

    -----【以下为原文网友评论及回复信息】-----
     

    Re:实体框架之领域驱动实践(五)

    [ 2010-1-11 9:22:00 | By: ruson(游客) ]

    理论上是很好的,但实践中感觉有些局限性。
    如上文所说Category和Item是一个聚合,Category是聚合根。那如果一个apsx页中要打开id为1的Item信息,是否还要把 Category的id也传过来。先从数据库中取出Category,再从Category中取Item呢。

    以下为blog主人的回复:
    你的问题很有价值!
    Category 可以是聚合根,也可以不是,应该根据实际情况进行考虑。如果我们不需要对Category进行维护,那么将Category和Item划归一个聚合,聚合 根应该是Item而不是Category,也就是说,Category应该是Item的一部分,Category作为值对象存在。考虑另外一种应用场合, 比如在订单上使用销售折扣,这个折扣可以应用在Item上,也允许应用在Category上(比如某个客户如果买了这个Category 下的Item,那么就按多少的折扣给他),那么此时就不得不去维护Category的信息,于是,Item和Category分属两个不同的聚合,聚合根 为其本身。
    这里也让我们了解到,实体和值对象没有明确的分界线,只能是设计人员在实践中根据自己的实际经验把握。
    【另外,对于文章中给大家造成的不合理引导,我深表歉意,我会在文章中保留原本错误的部分,然后将勘误用橙色笔标注以备参考。也非常欢迎大家能够提出自己的问题与疑惑】

    Re:实体框架之领域驱动实践(五)

    [ 2010-1-16 9:17:00 | By: ruson(游客) ]

    你好,按现实中的常规,应该先有分类才有分类下的子项,Item是属于Category下的一个集合,Category优先于Item而存在。
    在Item必需有分类的情况下,Item离开了Category就显得无意义,所以我的想法是Category应该为聚合根。但会遇到上面我所提到的问题。以及如果只想对其中某一个Item进行CURD时还要先取出来整个聚合根的话对性能的影响有些疑问。
    谢谢。

    以下为blog主人的回复:
    你好!这个问题确实让人难以理解,开始的时候,我承认我自己也没有经过深思熟虑就把结论写在这里,造成很多朋友的误解,再次深表歉意。
    由于你现在需要对Item进行CRUD,更“领域”一点讲,你需要对Item进行持久化的操作,那么Item就一定是聚合根,那么它是哪个聚合的聚合根?就需要看应用本身的需求了。
    同 理,至于Category和Item是否属于同一个聚合,以及Category是否是聚合根,也要根据实际需求而定。DDD中所讨论的聚合应该是组合聚合 (Composite Aggregation),而不是可共享聚合(Shareable Aggregation),因为在创建聚合的同时也需要创建聚合内部的成员。于是,从语义上讲,子部件无法脱离聚合而单独存在(就像汽车与方向盘、轮子的 关系那样)。但事实上呢?现实生活中,物品就是物品,可以不给它们归类,也可以将它们分属于不同的类别,但归类也好,不归类也好,物品都是客观存在的。不 因为你不给它归类,物品就消失了。
    因此,在做建模的时候,我们可能需要更加注重实际应用与我们模型的距离,以便更加真实客观地反映问题本身。DDD是实践指导,不是理论,就像对待设计模式一样,我们所能做的只是借鉴,而不是照搬。
    希望我的解答能够为你提供帮助。

    Re:实体框架之领域驱动实践(五)

    [ 2010-1-18 15:26:00 | By: ruson(游客) ]

    谢谢博主的回复。
    这段时间在尝试NHibernate + Spring.NET下的DDD实践。

    以下为 blog主人的回复:
    共同探讨,共同进步。
    2008 年的时候我尝试过Spring.NET+Castle ActiveRecord的DDD实践。当时我选用Castle ActiveRecord的原因是因为样品很简单,我不打算去维护NHibernate复杂的mapping XML。其实ActiveRecord是夹在Transaction Script和Domain Model之间的DDD的反模式,DDD社区中不少人指出这种做法不妥,但我觉得只要适合我的实际情况,也没什么大碍。
    与Spring.NET一样,NHibernate也是一种解耦的手段。Spring.NET解耦了对象之间的依赖性,而NHibernate则解耦了对象模型与数据库模型之间的映射关系。两者目的相同:提高系统的延展性和扩充性。

     

  • 相关阅读:
    28完全背包+扩展欧几里得(包子凑数)
    HDU 3527 SPY
    POJ 3615 Cow Hurdles
    POJ 3620 Avoid The Lakes
    POJ 3036 Honeycomb Walk
    HDU 2352 Verdis Quo
    HDU 2368 Alfredo's Pizza Restaurant
    HDU 2700 Parity
    HDU 3763 CDs
    POJ 3279 Fliptile
  • 原文地址:https://www.cnblogs.com/fcsh820/p/1866374.html
Copyright © 2011-2022 走看看