领域模型的行为设计

13-04-22 banq
                   

领域模型的行为设计是面向对象领域建模设计的重要部分。

在没有设计的朴素的情况下,领域模型一般是一个数据对象(DTO等),其中只有setter/getter方法,是一种纯粹的数据结构,然后将很多数据结构的算法操作设计在Service等专门接口类中。这样,数据对象作为服务接口方法的参数传入,在服务的方法中被加工。如下代码:

//失血模型  贫血模型
public class A{
    private int id;
    ...//只有setId(int id) 和getId()方法
}

public class AServiceImp implements AService{
    //失血模型作为方法参数传入,被操作
     public void createA(A a){
           ...
     }

}
<p>

而DDD领域驱动设计告知我们要注重领域模型的业务方法设计,领域模型=数据结构+操作方法,才是一个完整的真正对象,也才能够真正发挥对象封装的作用。

但是一个模型对象可能有很多方法,哪些方法应该作为对象本身的方法?哪些方法又应该依赖其他对象进行?举例:

public class A{
  
    //对象本身独立行为
    public void thisIsMyMethod(){
       ....
    }

   //依赖其他对象的交互行为
   public void replyOthers(B b){
      b.xxxx();
     ..
   }

}
<p>

我们把A对象自身固有行为看成是A的一种能力,而把需要依赖其他对象的方法称为交互行为。哪些属于A的自身方法?哪些属于交互方法?设计思路和方法是如何考虑的?

这种常见的貌似非常简单的问题其实不简单,而单纯依靠几个设计模式并不能解决。

还有在不同场景下,有时A对象依赖B对象,同时A依赖C对象,有时只依赖B对象,如何反应这种应场景变化而导致交互方式的不同?

如下图:

[该贴被admin于2013-04-23 07:39修改过]



                   

14
banq
2013-04-22 15:45

第一个思路: DCI,根据不同场景,将其对应的角色职责动态注入数据模型中。

这种方法认为,对象的行为都是其在一定场景下扮演某个角色才具备的,因此,先将行为设计到相应角色对象中,然后在需要时,将某个角色与数据对象混合mixin。

这种方法的特点非常类似依赖注入,是两个对象的合并组合,类似桥模式。

[该贴被admin于2013-04-22 20:32修改过]


banq
2013-04-22 15:57

2. DDD聚合根思路,先去除不必要的关联依赖,找出高聚合,比如结合业务发现,A和B是代表各自聚合的实体根。切割后分别设计,聚合根实体对象的行为应该是保证对象内部状态一致性的那些动作。

所谓逻辑一致性,也就是业务的规则 约束或校验,以日常例子说明,如果一群人的观点一致,那么我们就可以用XX组织 XX帮派来称呼他们,人以群分,物以类群,人或物因为有内部一致性才归类。

具体来说,类似状态模式,如果当前进入播放状态(假设有开始 播放 暂停 停止四个状态),那么下一个状态只可能是暂停或停止状态,肯定不是开始状态,那么这种一致性判断在什么时候什么地方判断呢?

很显然应该是在触发状态改变之前的动作行为中判断,那么这些动作就不能放在领域模型以外了,这也就是失血模型的根本问题所在。

除此保证内部一致性以外的动作方法可以不用放在领域模型内部,这些和业务场景有关的交互行为可以在服务中,也可以使用DCI将接口注入领域模型中,还可以用消息或事件实现。也就是说,用消息来实现交互,不管这种交互是由事件引起的,还是领域模型对技术架构发出的一种命令。

其实,聚合根也可以看成是一种角色,其职责是:维持聚合边界内状态的一致性(逻辑一致性)。

因此,聚合根与DCI可以结合,如下图:

在一个聚合设计中,我们可以考虑DCI,比如A实体是聚合根,也就是说,A已经固定扮演了聚合根这个角色,如果我们还希望A实现其他场景的角色职责?怎么办?

比如希望让A扮演持久化的角色,或者让A实现消息生产者的角色,这些职责虽然不是业务场景职责,毕竟A是生活在计算机世界中,也要遵守计算机领域场景的一些规则游戏。

使用Mixin/AOP实现的动态组合太多角色可能破坏A实体充当聚合根这个主要角色,在这种情况下,以DCI名义只引入一个事件发送者角色,让A实体主要实施聚合根职责,其他以外的职责全部通过以事件消息的形式委托其他类来实现。

http://www.jdon.com/45318案例为说明如下,BacklogItem假设等同与聚合根A, product相当聚合根B, A和B的依赖交互可用消息事件实现:

public class BacklogItem{
      //ProductVO是另外一个聚合根实体Product的值对象
      private ProductVO productVO;
      
     @Inject   //DCI的角色注入句柄 组入(织入)一个领域事件发送者角色
     private DomaineventsRole domaineventsRole;

      //需要交互的方法
      public void updateProduct(){
            //向Product聚合根发事件消息实现交互操作
            domaineventsRole.send(new ProductUpdatedEvent(productVO.getProductId));
      }

       //开始记录方法会改变自身内部状态,直接作为基本方法。
      public void startLog(){
            ..
      }

}
<p>

[该贴被banq于2013-04-23 08:28修改过]

[该贴被banq于2013-04-23 09:43修改过]

tangxuehua
2013-04-23 18:40

banq,在网上找到了一个比较典型的例子,就是处理“会议位置订单”的一个流程,流程图如下:

更多信息见这个地址:http://msdn.microsoft.com/en-us/library/jj591569.aspx

请问,按照你关于角色的思路来分析,该如何实现上图呢?

注意,上图的流程中的实现特色是有一个中心节点,就是order process manager,负责协调整个订单处理的过程。

但我知道,按照你的关于角色的理论应该是没有这个中心节点的。我多次在你的网站上提到“saga”,"processmanager"字样的概念,但你都直接无视,这次我希望你能明确思考和回答下,如果用去中心化的角色之间直接交互的设计,针对上图的流程,我们该如何定义角色,如何设计每个角色的交互行为呢?

[该贴被tangxuehua于2013-04-23 18:46修改过]

flyzb
2013-04-23 20:41

这个问题非常关键,我觉得要弄清楚2点:

1、对象为什么会拥有行为?是主动式的?还是响应式的?

2、对象如何拥有行为?

对于第一个问题,我认为“对象因为受到刺激而发生响应(行为)“。

对于第二个问题,我认为”对象拥有行为“并不是简单的”贫血对象“或者”充血对象“的问题,而是要如何组织那个”驱动对象发生行为“的因。对于目前DDD采取的”聚合根“,我并不太赞同,我认为”一致性“既然是对象之间必然的业务联系,那么完全可以通过”事件响应“去保证”一致性“,可以不必关注什么”集合根“。

5Go 1 2 3 4 ... 5 下一页