领域模型(Domain Model )

<<Domain Driven Design>> 和<< Patterns of Enterprise Application Architecture >>,令Domain 这个词很火,也引起了广泛争论。我这里也乱谈一把。

什么是领域模型(Domain Model )
   我以为Domain分两个含义:Domain Object和Domain Service。那么什么样的系统是面向Domain的系统,一个Domain Object和普通的符合OO原则的对象有什么区别;一个Domain Service和普通的Facade或者Manager对象有什么区别。

概念上,一个Domain Object和普通的符合OO原则的对象有声明区别:Domain Object是业务意义上,承载了业务数据(我据此认为所有Domain Object是有状态对象),从本质上说它直接来源于现实世界,没有技术层次上的考虑,“符合OO原则的对象”是用OO方法分析得到的,是基于计算机领域技术的(这样的对象可以是无状态的);但反过来,符合OO的对象不一定反应DOMAIN 的OBJECT。


技术上,Domain Object是指那些包含需要被透明持久化的属性,以及相关业务逻辑(体现在Use Case中)的POJO。仔细观察旧的系统,发现Transaction Script还是Table Module操作都的是来自数据库(或者其它持久化通道)的数据,Transaction的业务逻辑是零星片断。而Table Module其操作的是同一类业务数据集合,包含明显的数据库痕迹。而一个Domain Object包含了这些需要被持久化的业务数据,同时还包含了与之相关所有业务操作,并且有自己的继承体系。Martin Fowler认为有了这些就可以称为是一个Domain Object,因此在其PoEAA中的ORM包含了一些不透明的持久化方案。我认为一个真正的Domain Object需要一个透明持久化。


Domain Service包含的商业逻辑包含了两部分:流程逻辑和控制逻辑。
1. 业务领域的流程逻辑(Business Process)。指一系列的业务行为,包括Domain Object的属性更新;Dao的创建、更新和删除操作以及对Domain Service中的包括Mail,网络等方法的访问。
2. 业务领域的控制逻辑(Business Rule)。A rule is a declarative statement that applies logic or computation to information values。Business Rule 1. 产生一些控制信息,限制或者触发某些行为的执行;2. 产生一些状态信息,提供给业务人员参考操作。A rule results either in the discovery of new information or a decision about taking action.。


而Facade或者Manager是完全从技术上考虑的,尤其是Facade,通常处理如下逻辑:1. 与表现层通信工作,把表现层的平面数据(VO)转换为相关联的Domain对象,把Domain对象计算的结果转换成平面数据(VO)返回给表现层;2. 根据Use Case完成商业逻辑(面向事务)的调度,包括其业务关联的Domain Object和Domain Service调度,其可能调度了多个Domain Service。在简化的情况下,Facade有时和service可以等同。


如果把一个系统看作是一个Mechanical组件的话,那么Domain Object就是其Structure,相当于人的骨架;而流程逻辑就是Power,相当于骨架上的肌肉;那么控制逻辑就是Control,相当于肌肉中的神经。

Domain应用的可能  
JavaBean方案,采用SQL mapping,只映射字段,不支持关联关系的映射,没有多态。但随着ORM框架的发展,如hibernate,提供支持关联关系,继承多态的能力,Domain Object成为可能。


Cooperation Object:Domain Model之外
Domain Model描述了一个面向问题领域的对象组织结构,和行为逻辑。而同时系统还需要另一些称为协作对象的,来帮助我们处理特定的技术问题。


应用domain 的体系结构
      Domain Service  |           Cooperation Object
      Domain Object   | (包括了Dao在内的处理技术问题的辅助对象)

其它
Domain Model存在于系统的各个地方,不过在不同地方有不同的映射实现。在通常的开发过程中,该映射存在于文档和开发人员的脑海中的。当要向客户展示时,就面临一个映射的关系。比如要允许客户可以在线编辑页面呈现的显示元素,在规则定义里使用对象系统时。
另外Domain在不同视图下导致不同的内容。比如一个代理人Agent对象,在party的视图下只拥有基本属性,而在Sale channel视图下就保存了一些额外信息如:考核记录,优秀率等。
http://blog.csdn.net/onlyzhangqin/archive/2008/03/27/2222477.aspx
为了补大家的遗憾,在此总结下ROBBIN的领域模型的一些观点和大家的补充,在网站和演讲中,robbin将领域模型初步分为4大类:
1、失血模型
2、贫血模型
3、充血模型
4、胀血模型

那么让我们看看究竟有这些领域模型的具体内容,以及他们的优缺点:

一、失血模型

失血模型简单来说,就是domain object只有属性的getter/setter方法的纯数据类,所有的业务逻辑完全由business object来完成(又称

TransactionScript),这种模型下的domain object被Martin Fowler称之为“贫血的domain object”。下面用举一个具体的代码来说明,代码

来自Hibernate的caveatemptor,但经过我的改写:

一个实体类叫做Item,指的是一个拍卖项目 
一个DAO接口类叫做ItemDao 
一个DAO接口实现类叫做ItemDaoHibernateImpl 
一个业务逻辑类叫做ItemManager(或者叫做ItemService)

java代码: 

public class Item implements Serializable { 
    private Long id = null; 
    private int version; 
    private String name; 
    private User seller; 
    private String description; 
    private MonetaryAmount initialPrice; 
    private MonetaryAmount reservePrice; 
    private Date startDate; 
    private Date endDate; 
    private Set categorizedItems = new HashSet(); 
    private Collection bids = new ArrayList(); 
    private Bid successfulBid; 
    private ItemState state; 
    private User approvedBy; 
    private Date approvalDatetime; 
    private Date created = new Date(); 
    //  getter/setter方法省略不写,避免篇幅太长 
}

java代码: 

public interface ItemDao { 
    public Item getItemById(Long id); 
    public Collection findAll(); 
    public void updateItem(Item item); 
}

ItemDao定义持久化操作的接口,用于隔离持久化代码。

java代码: 

public class ItemDaoHibernateImpl implements ItemDao extends HibernateDaoSupport { 
    public Item getItemById(Long id) { 
        return (Item) getHibernateTemplate().load(Item.class, id); 
    } 
    public Collection findAll() { 
        return (List) getHibernateTemplate().find("from Item"); 
    } 
    public void updateItem(Item item) { 
        getHibernateTemplate().update(item); 
    } 
}


ItemDaoHibernateImpl完成具体的持久化工作,请注意,数据库资源的获取和释放是在ItemDaoHibernateImpl里面处理的,每个DAO方法调用之

前打开Session,DAO方法调用之后,关闭Session。(Session放在ThreadLocal中,保证一次调用只打开关闭一次)

java代码: 

public class ItemManager { 
    private ItemDao itemDao; 
    public void setItemDao(ItemDao itemDao) { this.itemDao = itemDao;} 
    public Bid loadItemById(Long id) { 
        itemDao.loadItemById(id); 
    } 
    public Collection listAllItems() { 
        return  itemDao.findAll(); 
    } 
    public Bid placeBid(Item item, User bidder, MonetaryAmount bidAmount, 
                            Bid currentMaxBid, Bid currentMinBid) throws BusinessException { 
            if (currentMaxBid != null && currentMaxBid.getAmount().compareTo(bidAmount) > 0) { 
            throw new BusinessException("Bid too low."); 
    } 
    
    // Auction is active 
    if ( !state.equals(ItemState.ACTIVE) ) 
            throw new BusinessException("Auction is not active yet."); 
    
    // Auction still valid 
    if ( item.getEndDate().before( new Date() ) ) 
            throw new BusinessException("Can't place new bid, auction already ended."); 
    
    // Create new Bid 
    Bid newBid = new Bid(bidAmount, item, bidder); 
    
    // Place bid for this Item 
    item.getBids().add(newBid); 
    itemDao.update(item);     //  调用DAO完成持久化操作 
    return newBid; 
    } 
}


事务的管理是在ItemManger这一层完成的,ItemManager实现具体的业务逻辑。除了常见的和CRUD有关的简单逻辑之外,这里还有一个placeBid

的逻辑,即项目的竞标。

以上是一个完整的第一种模型的示例代码。在这个示例中,placeBid,loadItemById,findAll等等业务逻辑统统放在ItemManager中实现,而

Item只有getter/setter方法。

二、贫血模型

简单来说,就是domain ojbect包含了不依赖于持久化的领域逻辑,而那些依赖持久化的领域逻辑被分离到Service层。 
Service(业务逻辑,事务封装) --> DAO ---> domain object 
这也就是Martin Fowler指的rich domain object

一个带有业务逻辑的实体类,即domain object是Item 
一个DAO接口ItemDao 
一个DAO实现ItemDaoHibernateImpl 
一个业务逻辑对象ItemManager

java代码: 

public class Item implements Serializable { 
    //  所有的属性和getter/setter方法同上,省略 
    public Bid placeBid(User bidder, MonetaryAmount bidAmount, 
                        Bid currentMaxBid, Bid currentMinBid) 
            throws BusinessException { 
    
            // Check highest bid (can also be a different Strategy (pattern)) 
            if (currentMaxBid != null && currentMaxBid.getAmount().compareTo(bidAmount) > 0) { 
                    throw new BusinessException("Bid too low."); 
            } 
    
            // Auction is active 
            if ( !state.equals(ItemState.ACTIVE) ) 
                    throw new BusinessException("Auction is not active yet."); 
    
            // Auction still valid 
            if ( this.getEndDate().before( new Date() ) ) 
                    throw new BusinessException("Can't place new bid, auction already ended."); 
    
            // Create new Bid 
            Bid newBid = new Bid(bidAmount, this, bidder); 
    
            // Place bid for this Item 
            this.getBids.add(newBid);  // 请注意这一句,透明的进行了持久化,但是不能在这里调用ItemDao,Item不能对ItemDao产生

依赖! 
    
            return newBid; 
    } 
}

竞标这个业务逻辑被放入到Item中来。请注意this.getBids.add(newBid); 如果没有Hibernate或者JDO这种O/R Mapping的支持,我们是无法实

现这种透明的持久化行为的。但是请注意,Item里面不能去调用ItemDAO,对ItemDAO产生依赖!

ItemDao和ItemDaoHibernateImpl的代码同上,省略。

java代码: 

public class ItemManager { 
    private ItemDao itemDao; 
    public void setItemDao(ItemDao itemDao) { this.itemDao = itemDao;} 
    public Bid loadItemById(Long id) { 
        itemDao.loadItemById(id); 
    } 
    public Collection listAllItems() { 
        return  itemDao.findAll(); 
    } 
    public Bid placeBid(Item item, User bidder, MonetaryAmount bidAmount, 
                            Bid currentMaxBid, Bid currentMinBid) throws BusinessException { 
        item.placeBid(bidder, bidAmount, currentMaxBid, currentMinBid); 
        itemDao.update(item);    // 必须显式的调用DAO,保持持久化 
    } 
}

 

在第二种模型中,placeBid业务逻辑是放在Item中实现的,而loadItemById和findAll业务逻辑是放在ItemManager中实现的。不过值得注意的

是,即使placeBid业务逻辑放在Item中,你仍然需要在ItemManager中简单的封装一层,以保证对placeBid业务逻辑进行事务的管理和持久化的

触发。

这种模型是Martin Fowler所指的真正的domain model。在这种模型中,有三个业务逻辑方法:placeBid,loadItemById和findAll,现在的问

题是哪个逻辑应该放在Item中,哪个逻辑应该放在ItemManager中。在我们这个例子中,placeBid放在Item中(但是ItemManager也需要对它进行

简单的封装),loadItemById和findAll是放在ItemManager中的。

切分的原则是什么呢? Rod Johnson提出原则是“case by case”,可重用度高的,和domain object状态密切关联的放在Item中,可重用度低

的,和domain object状态没有密切关联的放在ItemManager中。

经过上面的讨论,如何区分domain logic和business logic,我想提出一个改进的区分原则:

domain logic只应该和这一个domain object的实例状态有关,而不应该和一批domain object的状态有关;

当你把一个logic放到domain object中以后,这个domain object应该仍然独立于持久层框架之外(Hibernate,JDO),这个domain object仍然可

以脱离持久层框架进行单元测试,这个domain object仍然是一个完备的,自包含的,不依赖于外部环境的领域对象,这种情况下,这个logic

才是domain logic。 
这里有一个很确定的原则:logic是否只和这个object的状态有关,如果只和这个object有关,就是domain logic;如果logic是和一批domain

object的状态有关,就不是domain logic,而是business logic。


Item的placeBid这个业务逻辑方法没有显式的对持久化ItemDao接口产生依赖,所以要放在Item中。请注意,如果脱离了Hibernate这个持久化

框架,Item这个domain object是可以进行单元测试的,他不依赖于Hibernate的持久化机制。它是一个独立的,可移植的,完整的,自包含的

域对象。

而loadItemById和findAll这两个业务逻辑方法是必须显式的对持久化ItemDao接口产生依赖,否则这个业务逻辑就无法完成。如果你要把这两

个方法放在Item中,那么Item就无法脱离Hibernate框架,无法在Hibernate框架之外独立存在。

这种模型的优点: 
1、各层单向依赖,结构清楚,易于实现和维护 
2、设计简单易行,底层模型非常稳定 
这种模型的缺点: 
1、domain object的部分比较紧密依赖的持久化domain logic被分离到Service层,显得不够OO 
2、Service层过于厚重

三、充血模型

充血模型和第二种模型差不多,所不同的就是如何划分业务逻辑,即认为,绝大多业务逻辑都应该被放在domain object里面(包括持久化逻辑)

,而Service层应该是很薄的一层,仅仅封装事务和少量逻辑,不和DAO层打交道。 
Service(事务封装) ---> domain object <---> DAO 
这种模型就是把第二种模型的domain object和business object合二为一了。所以ItemManager就不需要了,在这种模型下面,只有三个类,他

们分别是:

Item:包含了实体类信息,也包含了所有的业务逻辑 
ItemDao:持久化DAO接口类 
ItemDaoHibernateImpl:DAO接口的实现类

由于ItemDao和ItemDaoHibernateImpl和上面完全相同,就省略了。

java代码: 

public class Item implements Serializable { 
    //  所有的属性和getter/setter方法都省略 
   private static ItemDao itemDao; 
    public void setItemDao(ItemDao itemDao) {this.itemDao = itemDao;} 
    
    public static Item loadItemById(Long id) { 
        return (Item) itemDao.loadItemById(id); 
    } 
    public static Collection findAll() { 
        return (List) itemDao.findAll(); 
    }

    public Bid placeBid(User bidder, MonetaryAmount bidAmount, 
                    Bid currentMaxBid, Bid currentMinBid) 
    throws BusinessException { 
    
        // Check highest bid (can also be a different Strategy (pattern)) 
        if (currentMaxBid != null && currentMaxBid.getAmount().compareTo(bidAmount) > 0) { 
                throw new BusinessException("Bid too low."); 
        } 
        
        // Auction is active 
        if ( !state.equals(ItemState.ACTIVE) ) 
                throw new BusinessException("Auction is not active yet."); 
        
        // Auction still valid 
        if ( this.getEndDate().before( new Date() ) ) 
                throw new BusinessException("Can't place new bid, auction already ended."); 
        
        // Create new Bid 
        Bid newBid = new Bid(bidAmount, this, bidder); 
        
        // Place bid for this Item 
        this.addBid(newBid); 
        itemDao.update(this);      //  调用DAO进行显式持久化 
        return newBid; 
    } 
}

在这种模型中,所有的业务逻辑全部都在Item中,事务管理也在Item中实现。

这种模型的优点: 
1、更加符合OO的原则 
2、Service层很薄,只充当Facade的角色,不和DAO打交道。 
这种模型的缺点: 
1、DAO和domain object形成了双向依赖,复杂的双向依赖会导致很多潜在的问题。 
2、如何划分Service层逻辑和domain层逻辑是非常含混的,在实际项目中,由于设计和开发人员的水平差异,可能导致整个结构的混乱无序。 
3、考虑到Service层的事务封装特性,Service层必须对所有的domain object的逻辑提供相应的事务封装方法,其结果就是Service完全重定义一遍所有的domain logic,非常烦琐,而且Service的事务化封装其意义就等于把OO的domain logic转换为过程的Service TransactionScript 。

该充血模型辛辛苦苦在domain层实现的OO在Service层又变成了过程式,对于Web层程序员的角度来看,和贫血模型没有什么区别了。

1.事务我是不希望由Item管理的,而是由容器或更高一层的业务类来管理。

2.如果Item不脱离持久层的管理,如JDO的pm,那么itemDao.update(this); 是不需要的,也就是说Item是在事务过程中从数据库拿出来的,并且声明周期不超出当前事务的范围。

3.如果Item是脱离持久层,也就是在Item的生命周期超出了事务的范围,那就要必须显示调用update或attach之类的持久化方法的,这种时候就应该是按robbin所说的第2种模型来做。

四、胀血模型

基于充血模型的第三个缺点,有同学提出,干脆取消Service层,只剩下domain object和DAO两层,在domain object的domain logic上面封装事务。 
domain object(事务封装,业务逻辑) <---> DAO 
似乎ruby on rails就是这种模型,他甚至把domain object和DAO都合并了。

该模型优点: 
1、简化了分层 
2、也算符合OO

该模型缺点: 
1、很多不是domain logic的service逻辑也被强行放入domain object ,引起了domain ojbect模型的不稳定 
2、domain object暴露给web层过多的信息,可能引起意想不到的副作用。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值