- 浏览: 51187 次
- 性别:
- 来自: 上海
-
最新评论
-
jack547155187:
不错。学习了
Java Volatile transient -
夜枫舞影:
一样的 order by 就可以了
你把 主题表与回帖表建立 ...
hibernate -
devil_javaMode:
一个主题表 ztid primary key,zttitle, ...
hibernate -
afcn0:
可选择很多,红的也很多,jQ已经稳定了,但是Prototype ...
jquery新手练习--------点table,form也跟着变 -
夜枫舞影:
afcn0 写道很多人就是喜欢.click .mouseout ...
jquery新手练习--------点table,form也跟着变
内容转自ROBBIN的 http://forum.iteye.com
为了补大家的遗憾,在此总结下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、参考充血模型第三个缺点,由于暴露给web层程序拿到的还是Service Transaction Script,对于web层程序员来说,底层OO意义丧失了。
2、参考充血模型第三个缺点,为了事务封装,Service层要给每个domain logic提供一个过程化封装,这对于编程来说,做了多余的工作,非
常烦琐。
3、domain object和DAO的双向依赖在做大项目中,考虑到团队成员的水平差异,很容易引入不可预知的潜在bug。
4、如何划分domain logic和service logic的标准是不确定的,往往要根据个人经验,有些人就是觉得某个业务他更加贴近domain,也有人认
为这个业务是贴近service的。由于划分标准的不确定性,带来的后果就是实际项目中会产生很多这样的争议和纠纷,不同的人会有不同的划分
方法,最后就会造成整个项目的逻辑分层混乱。这不像贫血模型中我提出的按照是否依赖持久化进行划分,这种标准是非常确定的,不会引起
争议,因此团队开发中,不会产生此类问题。
5、贫血模型的domain object确实不够rich,但是我们是做项目,不是做研究,好用就行了,管它是不是那么纯的OO呢?其实我不同意
firebody认为的贫血模型在设计模型和实现代码中有很大跨越的说法。一个设计模型到实现的时候,你直接得到两个类:一个实体类,一个控
制类就行了,没有什么跨越。
简单评价一下:
第一种模型绝大多数人都反对,因此反对理由我也不多讲了。但遗憾的是,我观察到的实际情形是,很多使用Hibernate的公司最后都是这种模
型,这里面有很大的原因是很多公司的技术水平没有达到这种层次,所以导致了这种贫血模型的出现。从这一点来说,Martin Fowler的批评声
音不是太响了,而是太弱了,还需要再继续呐喊。
第二种模型就是Martin Fowler一直主张的模型,实际上也是我一直在实际项目中采用这种模型。我没有看过Martin的POEAA,之所以能够自己
摸索到这种模型,也是因为从02年我已经开始思考这个问题并且寻求解决方案了,但是当时没有看到Hibernate,那时候做的一个小型项目我已
经按照这种模型来做了,但是由于没有O/R Mapping的支持,写到后来又不得不全部改成贫血的domain object,项目做完以后再继续找,随后
就发现了Hibernate。当然,现在很多人一开始就是用Hibernate做项目,没有经历过我经历的那个阶段。
不过我觉得这种模型仍然不够完美,因为你还是需要一个业务逻辑层来封装所有的domain logic,这显得非常罗嗦,并且业务逻辑对象的接口
也不够稳定。如果不考虑业务逻辑对象的重用性的话(业务逻辑对象的可重用性也不可能好),很多人干脆就去掉了xxxManager这一层,在Web层
的Action代码直接调用xxxDao,同时容器事务管理配置到Action这一层上来。Hibernate的caveatemptor就是这样架构的一个典型应用。
第三种模型是我很反对的一种模型,这种模型下面,Domain Object和DAO形成了双向依赖关系,无法脱离框架测试,并且业务逻辑层的服务也
和持久层对象的状态耦合到了一起,会造成程序的高度的复杂性,很差的灵活性和糟糕的可维护性。也许将来技术进步导致的O/R Mapping管理
下的domain object发展到足够的动态持久透明化的话,这种模型才会成为一个理想的选择。就像O/R Mapping的流行使得第二种模型成为了可
能
Martin Fowler的Domain Model,或者说我们的第二种模型难道是完美无缺的吗?当然不是,接下来我就要分析一下它的不足,以及可能的解决
办法,而这些都来源于我个人的实践探索。
在第二种模型中,我们可以清楚的把这4个类分为三层:
1、实体类层,即Item,带有domain logic的domain object
2、DAO层,即ItemDao和ItemDaoHibernateImpl,抽象持久化操作的接口和实现类
3、业务逻辑层,即ItemManager,接受容器事务控制,向Web层提供统一的服务调用
在这三层中我们大家可以看到,domain object和DAO都是非常稳定的层,其实原因也很简单,因为domain object是映射数据库字段的,数据库
字段不会频繁变动,所以domain object也相对稳定,而面向数据库持久化编程的DAO层也不过就是CRUD而已,不会有更多的花样,所以也很稳
定。
问题就在于这个充当business workflow facade的业务逻辑对象,它的变动是相当频繁的。业务逻辑对象通常都是无状态的、受事务控制的、
Singleton类,我们可以考察一下业务逻辑对象都有哪几类业务逻辑方法:
第一类:DAO接口方法的代理,就是上面例子中的loadItemById方法和findAll方法。
ItemManager之所以要代理这种类,目的有两个:向Web层提供统一的服务调用入口点和给持久化方法增加事务控制功能。这两点都很容易理解
,你不能既给Web层程序员提供xxxManager,也给他提供xxxDao,所以你需要用xxxManager封装xxxDao,在这里,充当了一个简单代理功能;而
事务控制也是持久化方法必须的,事务可能需要跨越多个DAO方法调用,所以必须放在业务逻辑层,而不能放在DAO层。
但是必须看到,对于一个典型的web应用来说,绝大多数的业务逻辑都是简单的CRUD逻辑,所以这种情况下,针对每个DAO方法,xxxManager都
需要提供一个对应的封装方法,这不但是非常枯燥的,也是令人感觉非常不好的。
第二类:domain logic的方法代理。就是上面例子中placeBid方法。虽然Item已经有了placeBid方法,但是ItemManager仍然需要封装一下Item
的placeBid,然后再提供一个简单封装之后的代理方法。
这和第一种情况类似,其原因也一样,也是为了给Web层提供一个统一的服务调用入口点和给隐式的持久化动作提供事务控制。
同样,和第一种情况一样,针对每个domain logic方法,xxxManager都需要提供一个对应的封装方法,同样是枯燥的,令人不爽的。
第三类:需要多个domain object和DAO参与协作的business workflow。这种情况是业务逻辑对象真正应该完成的职责。
在这个简单的例子中,没有涉及到这种情况,不过大家都可以想像的出来这种应用场景,因此不必举例说明了。
通过上面的分析可以看出,只有第三类业务逻辑方法才是业务逻辑对象真正应该承担的职责,而前两类业务逻辑方法都是“无奈之举”,不得
不为之的事情,不但枯燥,而且令人沮丧。
分析完了业务逻辑对象,我们再回头看一下domain object,我们要仔细考察一下domain logic的话,会发现domain logic也分为两类:
第一类:需要持久层框架隐式的实现透明持久化的domain logic,例如Item的placeBid方法中的这一句:
java代码:
this.getBids().add(newBid);
上面已经着重提到,虽然这仅仅只是一个Java集合的添加新元素的操作,但是实际上通过事务的控制,会潜在的触发两条SQL:一条是insert一
条记录到bid表,一条是更新item表相应的记录。如果我们让Item脱离Hibernate进行单元测试,它就是一个单纯的Java集合操作,如果我们把
他加入到Hibernate框架中,他就会潜在的触发两条SQL,这就是隐式的依赖于持久化的domain logic。
特别请注意的一点是:在没有Hibernate/JDO这类可以实现“透明的持久化”工具出现之前,这类domain logic是无法实现的。
对于这一类domain logic,业务逻辑对象必须提供相应的封装方法,以实现事务控制。
第二类:完全不依赖持久化的domain logic,例如readonly例子中的Topic,如下:
java代码:
class Topic {
boolean isAllowReply() {
Calendar dueDate = Calendar.getInstance();
dueDate.setTime(lastUpdatedTime);
dueDate.add(Calendar.DATE, forum.timeToLive);
Date now = new Date();
return now.after(dueDate.getTime());
}
}
注意这个isAllowReply方法,他和持久化完全不发生一丁点关系。在实际的开发中,我们同样会遇到很多这种不需要持久化的业务逻辑(主要发
生在日期运算、数值运算和枚举运算方面),这种domain logic不管脱离不脱离所在的框架,它的行为都是一致的。对于这种domain logic,业
务逻辑层并不需要提供封装方法,它可以适用于任何场合。
概括说:action做为控制器 ,service面向use case,domain object是中间稳定的一层,dao是作为下层的服务,提供持久化服务,可以被
domain object所使用。
针对上面帖子中分析的业务逻辑对象的方法有三类的情况,我们在实际的项目中会遇到一些困扰。主要的困扰就是业务逻辑对象的方法会变动
的相当频繁,并且业务逻辑对象的方法数量会非常庞大。针对这个问题,我所知道的有两种解决方案,我姑且称之为第二种模型的两类变种:
第一类变种就是partech的那种模型,简单的来说,就是把业务逻辑对象层和DAO层合二为一;第二类变种就是干脆取消业务逻辑层,把事务控
制前推至Web层的Action层来处理,下面分别分析一下两类变种的优缺点:
第一类变种是合并业务逻辑对象和DAO层,这种设计代码简化为3个类,如下所示:
一个domain object:Item(同第二种模型的代码,省略)
一个业务层接口:ItemManager(合并原来的ItemManager方法签名和ItemDao接口而来)
一个业务层实现类:ItemManagerHibernateImpl(合并原来的ItemManager方法实现和ItemDaoHibernateImpl)
java代码:
public interface ItemManager {
public Item loadItemById(Long id);
public Collection findAll();
public void updateItem(Item item);
public Bid placeBid(Item item, User bidder, MonetaryAmount bidAmount, Bid currentMaxBid, Bid currentMinBid) throws
BusinessException;
}
java代码:
public class ItemManagerHibernateImpl implements ItemManager extends HibernateDaoSupport {
public Item loadItemById(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);
}
public Bid placeBid(Item item, User bidder, MonetaryAmount bidAmount, Bid currentMaxBid, Bid currentMinBid) throws
BusinessException {
item.placeBid(bidder, bidAmount, currentMaxBid, currentMinBid);
updateItem(item); // 确保持久化item
}
}
第二种模型的第一类变种把业务逻辑对象和DAO层合并到了一起。
考虑到典型的web应用中,简单的CRUD操作占据了业务逻辑的绝大多数比例,因此第一类变种的优点是:避免了业务逻辑不得不大量封装DAO接
口的问题,简化了软件架构设计,节省了大量的业务层代码量。
这种方案的缺点是:把DAO接口方法和业务逻辑方法混合到了一起,显得职责不够单一化,软件分层结构不够清晰;此外这种方案仍然不得不对
隐式依赖持久化的domain logic提供封装方法,未能做到彻底的简化。
总体而言,个人认为这种变种各方面权衡下来,是目前相对最为合理方案,这也是我目前项目中采用的架构
第二种模型的第二类变种就是干脆取消ItemManager,保留原来的Item,ItemDao,ItemDaoHibernateImpl这3个类。在这种情况下把事务控制前
推至Web层的Action去控制,具体来说,就是直接对Action的execute()方法进行容器事务声明。
这种方式的优点是:极大的简化了业务逻辑层,避免了业务逻辑对象不得不大量封装DAO接口方法和大量封装domain logic的问题。对于业务逻
辑非常简单的项目,采用这种方案是一个非常合适的选择。
这种方式的缺点主要有3个:
1) 由于彻底取消了业务逻辑对象层,对于那些有重用需要的、多个domain object和多个DAO参与的、复杂业务逻辑流程来说,你不得不在
Action中一遍又一遍的重复实现这部分代码,效率既低,也不利于软件重用。
2) Web层程序员需要对持久层机制有相当高程度的了解和掌握,必须知道什么时候应该调用什么DAO方法进行必要的持久化。
3) 事务的范围被扩大了。假设你在一个Action中,首先需要插入一条记录,然后再需要查询数据库,显示一个记录列表,对于这种情况,事务
的作用范围应该是在插入记录的前后,但是现在扩大到了整个execute执行期间。如果插入动作完毕,查询动作过程中出现通往数据库服务器的
网络异常,那么前面的插入动作将回滚,但是实际上我们期望的是插入应该被提交。
总体而言,这种变种的缺陷比较大,只适合在业务逻辑非常简单的小型项目中,值得一提的是Hibernate的caveatemptor就是采用这种变种的架
构,大家可以参考一下。
综上所述,在采用Rich Domain Object模型的三种解决方案中(第二模型,第二模型第一变种,第二模型第二变种),我认为权衡下来,第二模
型的第一变种是相对最好的解决方案,不过它仍然有一定的不足,在这里我也希望大家能够提出更好的解决方案。
,partech 提出了 实体控制对象 和 实体对象 两种不同层次的 Domain Object ,由于 Domain Object 可以依赖于 XXXFinderDAO,因此,也
就不存在“大数据量问题”,因此,整个 Domain 体系,对于实际业务表述的更为完整,更为一体化。我非常倾向这种方式。
一般是这样的顺序:
Client-->Service-->D Object-->DAO-->DB
至于哪些该放在哪里,基本有这样的原则:(就是robbin的第二种了)
DO封装内在的业务逻辑
Service 封装外在于DO的业务逻辑
当然如果业务逻辑简单或者没有的话也可以:
Client-->D Object-->DAO-->DB
对于第二种的第一个变种固然是个好办法,但如Robbin所说也有缺陷如果有多个Servcie要调用DAO的话,就有问题了。合并也意味中不能很好
的重用
说到底就是粒度的问题,分得细重用好,但类多、结构复杂、繁琐。分得粗(干脆用一个类干所有的事)重用差,但类少、结构简单。
为了补大家的遗憾,在此总结下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、参考充血模型第三个缺点,由于暴露给web层程序拿到的还是Service Transaction Script,对于web层程序员来说,底层OO意义丧失了。
2、参考充血模型第三个缺点,为了事务封装,Service层要给每个domain logic提供一个过程化封装,这对于编程来说,做了多余的工作,非
常烦琐。
3、domain object和DAO的双向依赖在做大项目中,考虑到团队成员的水平差异,很容易引入不可预知的潜在bug。
4、如何划分domain logic和service logic的标准是不确定的,往往要根据个人经验,有些人就是觉得某个业务他更加贴近domain,也有人认
为这个业务是贴近service的。由于划分标准的不确定性,带来的后果就是实际项目中会产生很多这样的争议和纠纷,不同的人会有不同的划分
方法,最后就会造成整个项目的逻辑分层混乱。这不像贫血模型中我提出的按照是否依赖持久化进行划分,这种标准是非常确定的,不会引起
争议,因此团队开发中,不会产生此类问题。
5、贫血模型的domain object确实不够rich,但是我们是做项目,不是做研究,好用就行了,管它是不是那么纯的OO呢?其实我不同意
firebody认为的贫血模型在设计模型和实现代码中有很大跨越的说法。一个设计模型到实现的时候,你直接得到两个类:一个实体类,一个控
制类就行了,没有什么跨越。
简单评价一下:
第一种模型绝大多数人都反对,因此反对理由我也不多讲了。但遗憾的是,我观察到的实际情形是,很多使用Hibernate的公司最后都是这种模
型,这里面有很大的原因是很多公司的技术水平没有达到这种层次,所以导致了这种贫血模型的出现。从这一点来说,Martin Fowler的批评声
音不是太响了,而是太弱了,还需要再继续呐喊。
第二种模型就是Martin Fowler一直主张的模型,实际上也是我一直在实际项目中采用这种模型。我没有看过Martin的POEAA,之所以能够自己
摸索到这种模型,也是因为从02年我已经开始思考这个问题并且寻求解决方案了,但是当时没有看到Hibernate,那时候做的一个小型项目我已
经按照这种模型来做了,但是由于没有O/R Mapping的支持,写到后来又不得不全部改成贫血的domain object,项目做完以后再继续找,随后
就发现了Hibernate。当然,现在很多人一开始就是用Hibernate做项目,没有经历过我经历的那个阶段。
不过我觉得这种模型仍然不够完美,因为你还是需要一个业务逻辑层来封装所有的domain logic,这显得非常罗嗦,并且业务逻辑对象的接口
也不够稳定。如果不考虑业务逻辑对象的重用性的话(业务逻辑对象的可重用性也不可能好),很多人干脆就去掉了xxxManager这一层,在Web层
的Action代码直接调用xxxDao,同时容器事务管理配置到Action这一层上来。Hibernate的caveatemptor就是这样架构的一个典型应用。
第三种模型是我很反对的一种模型,这种模型下面,Domain Object和DAO形成了双向依赖关系,无法脱离框架测试,并且业务逻辑层的服务也
和持久层对象的状态耦合到了一起,会造成程序的高度的复杂性,很差的灵活性和糟糕的可维护性。也许将来技术进步导致的O/R Mapping管理
下的domain object发展到足够的动态持久透明化的话,这种模型才会成为一个理想的选择。就像O/R Mapping的流行使得第二种模型成为了可
能
Martin Fowler的Domain Model,或者说我们的第二种模型难道是完美无缺的吗?当然不是,接下来我就要分析一下它的不足,以及可能的解决
办法,而这些都来源于我个人的实践探索。
在第二种模型中,我们可以清楚的把这4个类分为三层:
1、实体类层,即Item,带有domain logic的domain object
2、DAO层,即ItemDao和ItemDaoHibernateImpl,抽象持久化操作的接口和实现类
3、业务逻辑层,即ItemManager,接受容器事务控制,向Web层提供统一的服务调用
在这三层中我们大家可以看到,domain object和DAO都是非常稳定的层,其实原因也很简单,因为domain object是映射数据库字段的,数据库
字段不会频繁变动,所以domain object也相对稳定,而面向数据库持久化编程的DAO层也不过就是CRUD而已,不会有更多的花样,所以也很稳
定。
问题就在于这个充当business workflow facade的业务逻辑对象,它的变动是相当频繁的。业务逻辑对象通常都是无状态的、受事务控制的、
Singleton类,我们可以考察一下业务逻辑对象都有哪几类业务逻辑方法:
第一类:DAO接口方法的代理,就是上面例子中的loadItemById方法和findAll方法。
ItemManager之所以要代理这种类,目的有两个:向Web层提供统一的服务调用入口点和给持久化方法增加事务控制功能。这两点都很容易理解
,你不能既给Web层程序员提供xxxManager,也给他提供xxxDao,所以你需要用xxxManager封装xxxDao,在这里,充当了一个简单代理功能;而
事务控制也是持久化方法必须的,事务可能需要跨越多个DAO方法调用,所以必须放在业务逻辑层,而不能放在DAO层。
但是必须看到,对于一个典型的web应用来说,绝大多数的业务逻辑都是简单的CRUD逻辑,所以这种情况下,针对每个DAO方法,xxxManager都
需要提供一个对应的封装方法,这不但是非常枯燥的,也是令人感觉非常不好的。
第二类:domain logic的方法代理。就是上面例子中placeBid方法。虽然Item已经有了placeBid方法,但是ItemManager仍然需要封装一下Item
的placeBid,然后再提供一个简单封装之后的代理方法。
这和第一种情况类似,其原因也一样,也是为了给Web层提供一个统一的服务调用入口点和给隐式的持久化动作提供事务控制。
同样,和第一种情况一样,针对每个domain logic方法,xxxManager都需要提供一个对应的封装方法,同样是枯燥的,令人不爽的。
第三类:需要多个domain object和DAO参与协作的business workflow。这种情况是业务逻辑对象真正应该完成的职责。
在这个简单的例子中,没有涉及到这种情况,不过大家都可以想像的出来这种应用场景,因此不必举例说明了。
通过上面的分析可以看出,只有第三类业务逻辑方法才是业务逻辑对象真正应该承担的职责,而前两类业务逻辑方法都是“无奈之举”,不得
不为之的事情,不但枯燥,而且令人沮丧。
分析完了业务逻辑对象,我们再回头看一下domain object,我们要仔细考察一下domain logic的话,会发现domain logic也分为两类:
第一类:需要持久层框架隐式的实现透明持久化的domain logic,例如Item的placeBid方法中的这一句:
java代码:
this.getBids().add(newBid);
上面已经着重提到,虽然这仅仅只是一个Java集合的添加新元素的操作,但是实际上通过事务的控制,会潜在的触发两条SQL:一条是insert一
条记录到bid表,一条是更新item表相应的记录。如果我们让Item脱离Hibernate进行单元测试,它就是一个单纯的Java集合操作,如果我们把
他加入到Hibernate框架中,他就会潜在的触发两条SQL,这就是隐式的依赖于持久化的domain logic。
特别请注意的一点是:在没有Hibernate/JDO这类可以实现“透明的持久化”工具出现之前,这类domain logic是无法实现的。
对于这一类domain logic,业务逻辑对象必须提供相应的封装方法,以实现事务控制。
第二类:完全不依赖持久化的domain logic,例如readonly例子中的Topic,如下:
java代码:
class Topic {
boolean isAllowReply() {
Calendar dueDate = Calendar.getInstance();
dueDate.setTime(lastUpdatedTime);
dueDate.add(Calendar.DATE, forum.timeToLive);
Date now = new Date();
return now.after(dueDate.getTime());
}
}
注意这个isAllowReply方法,他和持久化完全不发生一丁点关系。在实际的开发中,我们同样会遇到很多这种不需要持久化的业务逻辑(主要发
生在日期运算、数值运算和枚举运算方面),这种domain logic不管脱离不脱离所在的框架,它的行为都是一致的。对于这种domain logic,业
务逻辑层并不需要提供封装方法,它可以适用于任何场合。
概括说:action做为控制器 ,service面向use case,domain object是中间稳定的一层,dao是作为下层的服务,提供持久化服务,可以被
domain object所使用。
针对上面帖子中分析的业务逻辑对象的方法有三类的情况,我们在实际的项目中会遇到一些困扰。主要的困扰就是业务逻辑对象的方法会变动
的相当频繁,并且业务逻辑对象的方法数量会非常庞大。针对这个问题,我所知道的有两种解决方案,我姑且称之为第二种模型的两类变种:
第一类变种就是partech的那种模型,简单的来说,就是把业务逻辑对象层和DAO层合二为一;第二类变种就是干脆取消业务逻辑层,把事务控
制前推至Web层的Action层来处理,下面分别分析一下两类变种的优缺点:
第一类变种是合并业务逻辑对象和DAO层,这种设计代码简化为3个类,如下所示:
一个domain object:Item(同第二种模型的代码,省略)
一个业务层接口:ItemManager(合并原来的ItemManager方法签名和ItemDao接口而来)
一个业务层实现类:ItemManagerHibernateImpl(合并原来的ItemManager方法实现和ItemDaoHibernateImpl)
java代码:
public interface ItemManager {
public Item loadItemById(Long id);
public Collection findAll();
public void updateItem(Item item);
public Bid placeBid(Item item, User bidder, MonetaryAmount bidAmount, Bid currentMaxBid, Bid currentMinBid) throws
BusinessException;
}
java代码:
public class ItemManagerHibernateImpl implements ItemManager extends HibernateDaoSupport {
public Item loadItemById(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);
}
public Bid placeBid(Item item, User bidder, MonetaryAmount bidAmount, Bid currentMaxBid, Bid currentMinBid) throws
BusinessException {
item.placeBid(bidder, bidAmount, currentMaxBid, currentMinBid);
updateItem(item); // 确保持久化item
}
}
第二种模型的第一类变种把业务逻辑对象和DAO层合并到了一起。
考虑到典型的web应用中,简单的CRUD操作占据了业务逻辑的绝大多数比例,因此第一类变种的优点是:避免了业务逻辑不得不大量封装DAO接
口的问题,简化了软件架构设计,节省了大量的业务层代码量。
这种方案的缺点是:把DAO接口方法和业务逻辑方法混合到了一起,显得职责不够单一化,软件分层结构不够清晰;此外这种方案仍然不得不对
隐式依赖持久化的domain logic提供封装方法,未能做到彻底的简化。
总体而言,个人认为这种变种各方面权衡下来,是目前相对最为合理方案,这也是我目前项目中采用的架构
第二种模型的第二类变种就是干脆取消ItemManager,保留原来的Item,ItemDao,ItemDaoHibernateImpl这3个类。在这种情况下把事务控制前
推至Web层的Action去控制,具体来说,就是直接对Action的execute()方法进行容器事务声明。
这种方式的优点是:极大的简化了业务逻辑层,避免了业务逻辑对象不得不大量封装DAO接口方法和大量封装domain logic的问题。对于业务逻
辑非常简单的项目,采用这种方案是一个非常合适的选择。
这种方式的缺点主要有3个:
1) 由于彻底取消了业务逻辑对象层,对于那些有重用需要的、多个domain object和多个DAO参与的、复杂业务逻辑流程来说,你不得不在
Action中一遍又一遍的重复实现这部分代码,效率既低,也不利于软件重用。
2) Web层程序员需要对持久层机制有相当高程度的了解和掌握,必须知道什么时候应该调用什么DAO方法进行必要的持久化。
3) 事务的范围被扩大了。假设你在一个Action中,首先需要插入一条记录,然后再需要查询数据库,显示一个记录列表,对于这种情况,事务
的作用范围应该是在插入记录的前后,但是现在扩大到了整个execute执行期间。如果插入动作完毕,查询动作过程中出现通往数据库服务器的
网络异常,那么前面的插入动作将回滚,但是实际上我们期望的是插入应该被提交。
总体而言,这种变种的缺陷比较大,只适合在业务逻辑非常简单的小型项目中,值得一提的是Hibernate的caveatemptor就是采用这种变种的架
构,大家可以参考一下。
综上所述,在采用Rich Domain Object模型的三种解决方案中(第二模型,第二模型第一变种,第二模型第二变种),我认为权衡下来,第二模
型的第一变种是相对最好的解决方案,不过它仍然有一定的不足,在这里我也希望大家能够提出更好的解决方案。
,partech 提出了 实体控制对象 和 实体对象 两种不同层次的 Domain Object ,由于 Domain Object 可以依赖于 XXXFinderDAO,因此,也
就不存在“大数据量问题”,因此,整个 Domain 体系,对于实际业务表述的更为完整,更为一体化。我非常倾向这种方式。
一般是这样的顺序:
Client-->Service-->D Object-->DAO-->DB
至于哪些该放在哪里,基本有这样的原则:(就是robbin的第二种了)
DO封装内在的业务逻辑
Service 封装外在于DO的业务逻辑
当然如果业务逻辑简单或者没有的话也可以:
Client-->D Object-->DAO-->DB
对于第二种的第一个变种固然是个好办法,但如Robbin所说也有缺陷如果有多个Servcie要调用DAO的话,就有问题了。合并也意味中不能很好
的重用
说到底就是粒度的问题,分得细重用好,但类多、结构复杂、繁琐。分得粗(干脆用一个类干所有的事)重用差,但类少、结构简单。
相关推荐
RUP 是在敏捷开发理念盛行之前,软件工程领域广泛采用的流程模型。在本文中,我们将深入探讨 RUP 的核心概念、主要阶段以及它如何应用于实际项目中。 一、RUP 概述 RUP 是一种基于组件的、迭代的和风险管理驱动的...
在游戏领域,尤其是沙盒建造类游戏中,Minecraft(我的世界)凭借其独特的像素风格和无限的创造可能,深受玩家们的喜爱。在这个虚拟世界中,玩家可以通过修改游戏内的纹理来改变游戏视觉体验,这就是我们今天要探讨...
【项目资源】: 适用于从基础到高级的各种项目,特别是在性能要求较高的场景中,比如操作系统开发、嵌入式编程和底层系统编程。如果您是初学者,可以从简单的控制台程序开始练习;如果是进阶开发者,可以尝试涉及硬件或网络的项目。 【项目质量】: 所有源码都经过严格测试,可以直接运行。 功能在确认正常工作后才上传。 【适用人群】: 适用于希望学习不同技术领域的小白或进阶学习者。 可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】: 项目具有较高的学习借鉴价值,也可直接拿来修改复刻。 对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】: 有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 鼓励下载和使用,并欢迎大家互相学习,共同进步。 # 注意 1. 本资源仅用于开源学习和技术交流。不可商用等,一切后果由使用者承担。 2. 部分字体以及插图等来自网络,若是侵权请联系删除。
# 基于CC++的简易聊天室系统 ## 项目简介 这是一个简单易用的聊天室练手项目,主要用于提高开发者对CC++与网络编程的理解。虽然该聊天室是运行在shell上的命令行程序,但项目致力于提升其易用性和用户体验,帮助CC++初学者和使用者摆脱命令行界面简陋、交互体验差的固有印象。此程序客户端和服务端一体,服务端对环境有数据库相关要求,客户端可能需安装dl库,同时引入了jsoncpp、sqlite3等第三方库。 ## 项目的主要特性和功能 ### 特性 客户端和服务端一体设计。 尽可能简化客户端操作,提高易用性。 运用菜单形式,减少用户手动输入操作。 对用户密码进行不可逆加密,保障信息安全。 ### 功能 支持用户注册、登录,可选择保存账号密码实现免密登录。 提供全局广播模式,支持私聊、群聊功能。 允许用户添加、删除好友,设置特别关心和黑名单。 能够创建群组、加入群组,并对群员进行管理。
ITIL 术语和缩写中文
【项目资源】: 物联网项目适用于从基础到高级的各种项目,特别是在性能要求较高的场景中,比如操作系统开发、嵌入式编程和底层系统编程。如果您是初学者,可以从简单的控制台程序开始练习;如果是进阶开发者,可以尝试涉及硬件或网络的项目。 【项目质量】: 所有源码都经过严格测试,可以直接运行。 功能在确认正常工作后才上传。 【适用人群】: 适用于希望学习不同技术领域的小白或进阶学习者。 可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】: 项目具有较高的学习借鉴价值,也可直接拿来修改复刻。 对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】: 有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 鼓励下载和使用,并欢迎大家互相学习,共同进步。 # 注意 1. 本资源仅用于开源学习和技术交流。不可商用等,一切后果由使用者承担。 2. 部分字体以及插图等来自网络,若是侵权请联系删除。
【项目资源】: 单片机项目适用于从基础到高级的各种项目,特别是在性能要求较高的场景中,比如操作系统开发、嵌入式编程和底层系统编程。如果您是初学者,可以从简单的控制台程序开始练习;如果是进阶开发者,可以尝试涉及硬件或网络的项目。 【项目质量】: 所有源码都经过严格测试,可以直接运行。 功能在确认正常工作后才上传。 【适用人群】: 适用于希望学习不同技术领域的小白或进阶学习者。 可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】: 项目具有较高的学习借鉴价值,也可直接拿来修改复刻。 对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】: 有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 鼓励下载和使用,并欢迎大家互相学习,共同进步。 # 注意 1. 本资源仅用于开源学习和技术交流。不可商用等,一切后果由使用者承担。 2. 部分字体以及插图等来自网络,若是侵权请联系删除。
《人工智能在智能客服领域的应用方案》:在当今数字化时代,企业与客户之间的互动日益频繁,客户服务的质量和效率成为企业竞争的关键因素之一。传统的客服模式面临着诸多挑战,如人工客服成本高昂、工作时间受限、服务质量参差不齐、难以应对大量并发的客户咨询等问题。随着人工智能技术的飞速发展,智能客服应运而生,它能够为企业提供高效、便捷、低成本的客户服务解决方案,极大地提升客户体验和企业运营效率。无论是电商、金融、电信、教育等行业,都可以通过对客服数据的分析,优化自身的业务流程和服务质量,提升企业的竞争力。
【项目资源】: 物联网项目适用于从基础到高级的各种项目,特别是在性能要求较高的场景中,比如操作系统开发、嵌入式编程和底层系统编程。如果您是初学者,可以从简单的控制台程序开始练习;如果是进阶开发者,可以尝试涉及硬件或网络的项目。 【项目质量】: 所有源码都经过严格测试,可以直接运行。 功能在确认正常工作后才上传。 【适用人群】: 适用于希望学习不同技术领域的小白或进阶学习者。 可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】: 项目具有较高的学习借鉴价值,也可直接拿来修改复刻。 对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】: 有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 鼓励下载和使用,并欢迎大家互相学习,共同进步。 # 注意 1. 本资源仅用于开源学习和技术交流。不可商用等,一切后果由使用者承担。 2. 部分字体以及插图等来自网络,若是侵权请联系删除。
内容概要:本文档是上海理工大学光电信息与计算机工程学院学生周文龙撰写的《光电融合集成电路路技术》设计报告,指导教师为隋国荣。报告分为两个部分:一是音乐梦幻灯设计,二是USB转接器仿真设计。音乐梦幻灯设计部分,以单片机为核心,通过硬件电路和软件编程实现简易电子琴,能够自动播放音乐并在电源接通时显示LED灯,详细介绍了硬件组成、原理图、元件清单及调试过程;USB转接器仿真设计部分,旨在搭建USB转接器电路,熟悉AD和嘉立创EDA等仿真平台的操作,绘制并验证电路原理图和PCB制版图,掌握焊接工艺和电路测试,为未来从事电工电子技术行业打下基础。 适合人群:电气工程、自动化、计算机等相关专业的大专院校学生,以及对单片机应用和电子电路设计感兴趣的初学者。 使用场景及目标:①学习单片机控制电子琴的原理和实现方法,包括硬件设计和软件编程;②掌握USB转接器电路的设计流程,包括原理图绘制、仿真、PCB制版图设计和电路板焊接;③提升实际动手能力和解决实际问题的能力,为未来从事相关行业打下基础。 阅读建议:本报告详细记录了设计过程中的每一个环节,包括理论知识的应用和实际操作的经验,建议读者在阅读过程中结合实际操作,逐步理解和掌握每个步骤的具体实现方法。同时,可以参考报告中提到的相关文献和工具,加深对单片机和电子电路设计的理解。
【项目资源】: 单片机项目适用于从基础到高级的各种项目,特别是在性能要求较高的场景中,比如操作系统开发、嵌入式编程和底层系统编程。如果您是初学者,可以从简单的控制台程序开始练习;如果是进阶开发者,可以尝试涉及硬件或网络的项目。 【项目质量】: 所有源码都经过严格测试,可以直接运行。 功能在确认正常工作后才上传。 【适用人群】: 适用于希望学习不同技术领域的小白或进阶学习者。 可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】: 项目具有较高的学习借鉴价值,也可直接拿来修改复刻。 对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】: 有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 鼓励下载和使用,并欢迎大家互相学习,共同进步。 # 注意 1. 本资源仅用于开源学习和技术交流。不可商用等,一切后果由使用者承担。 2. 部分字体以及插图等来自网络,若是侵权请联系删除。
【项目资源】: 物联网项目适用于从基础到高级的各种项目,特别是在性能要求较高的场景中,比如操作系统开发、嵌入式编程和底层系统编程。如果您是初学者,可以从简单的控制台程序开始练习;如果是进阶开发者,可以尝试涉及硬件或网络的项目。 【项目质量】: 所有源码都经过严格测试,可以直接运行。 功能在确认正常工作后才上传。 【适用人群】: 适用于希望学习不同技术领域的小白或进阶学习者。 可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】: 项目具有较高的学习借鉴价值,也可直接拿来修改复刻。 对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】: 有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 鼓励下载和使用,并欢迎大家互相学习,共同进步。 # 注意 1. 本资源仅用于开源学习和技术交流。不可商用等,一切后果由使用者承担。 2. 部分字体以及插图等来自网络,若是侵权请联系删除。
# 基于C语言的简单计算器 ## 项目简介 这是一个基于C语言的简单计算器项目,借助命令行界面为用户提供基本数学运算功能。项目运用标准C库,无需额外依赖。 ## 项目的主要特性和功能 1. 具备命令行界面,用户可在命令行输入数字和运算符,程序负责解析并执行。 2. 支持加法、减法、乘法和除法等基本数学运算。 3. 能进行错误处理,遇到不合法输入(如非数字字符或错误运算符)时,会提示用户重新输入。 4. 采用交互式设计,用户可随时退出程序或继续计算新表达式。 ## 安装使用步骤 假设用户已下载并解压了项目的源码文件,按以下步骤操作 1. 编译源代码使用C编译器(如GCC)编译项目中的 calculator.c 文件,命令为 gcc calculator.c o calculator。 2. 运行程序在终端或命令行界面中,输入 .calculator 运行程序。 3. 输入表达式按照提示输入表达式,例如 5 + 3,然后按回车键。
摘 要 面对信息时代的机遇与挑战,利用高科技手段来提高企业的管理水平无疑是一条行之有效的途径。利用计算机管理可以最大限度的发挥准确、快捷、高效等作用, 在越来越激烈的珠宝行业中,计算机管理技术对珠宝首饰公司的服务管理提供强有力的支持。因此,利用全新的计算机网络和珠宝首饰管理系统,已成为提高珠宝首饰公司的管理效率,改进服务水准的重要手段之一。本系统应用Visual Basic 6.0 中文版开发前台,用Microsoft Access 作后台服务器,采用客户机/服务器(C/S)管理思想来对珠宝首饰进销存管理。 关键词:管理水平, 管理效率,服务水准,珠宝首饰管理系统,客户机/服务器,管理思想
# 基于C语言的调试终端及格式化输出系统 ## 项目简介 本项目是一个基于C语言的调试终端及格式化输出系统,专为嵌入式系统或其他资源受限的环境设计。它提供了类似C标准库中printf函数的功能,支持格式化输出字符串、整数、浮点数等数据类型,适用于TI的C2000 MCU tms320f280049,使用CCS V8.1 IDE进行开发。 ## 项目的主要特性和功能 1. 调试终端初始化通过DebugTerminalInit函数初始化调试终端,配置GPIO引脚和SCIA模块,实现数据回显。 2. 格式化输出提供printf、vsprintf、vsnprintf和vscnprintf函数,支持格式化输出字符串、整数、浮点数等数据类型。 3. 数字输出number函数支持多种进制和标志位的数字格式化输出。 4. 指针地址输出pointer函数支持不同类型的指针地址格式化输出。
内容概要:PT5000汽轮机滑动轴承系统模拟试验台是一个类似于电厂汽轮机发电机的缩小模型,旨在帮助用户获取汽轮机转子动态行为和滑动轴承油膜现象的实际经验,并研究振动控制方法。该试验台模拟两级涡轮机(低压和中压),每级转子两侧各有8个叶片,共计16个叶片。通过电机驱动而非涡轮发电机,可以进行启停机测试,识别共振现象。试验台还支持多种实验,如不平衡/现场动平衡、轴不对中实验、摩擦实验、油膜故障试验、轴颈轴承实验以及根据油压和温度进行的转子动力学试验。试验台配备了多种传感器和控制系统,包括电涡流传感器、温度传感器、压力传感器等,用于监测和记录实验数据。 适合人群:从事汽轮机设计、制造、维护的技术人员,以及相关专业的高校师生和研究人员。 使用场景及目标:①研究汽轮机转子的动态行为和滑动轴承的油膜现象;②进行振动控制方法的研究;③模拟再现油膜涡动转和油膜震荡,研究其控制条件;④进行不平衡、不对中、摩擦等常见故障的模拟和分析;⑤通过调整油压、温度和预加载力,研究轴的行为变化。 其他说明:该试验台不仅适用于教学和科研,还可用于工业领域的培训和技术验证。试验台具有丰富的配置和可选配件,可以根据具体需求进行定制。试验台的机械和电气参数详细列出,确保用户能够全面了解设备性能。
【更新至2023年】2000-2023年中国气候政策不确定性指数数据(全国、省、市三个层面) 1.时间:2000-2023年 2.来源:使用人工审计和深度学习算法MacBERT模型,基于中国《人民日报》《光明日报》《经济日报》《环球时报》《科技日报》《中国新闻社》等6家主流报纸中的1,755,826篇文章,构建了2000年1月至2023年12月的中国全国、省份和主要城市层面的CCPU指数。研究框架包括六个部分:数据收集、清洗数据、人工审计、模型构建、指数计算与标准化以及技术验证。 3.范围:中国、省、市三个层次 4.参考文献:Ma, Y. R., Liu, Z., Ma, D., Zhai, P., Guo, K., Zhang, D., & Ji, Q. (2023). A news-based climate policy uncertainty index for China. Scientific Data, 10(1), 881. 5.时间跨度:全国层面:日度、月度、年度;省级层面:月度、年度;地级市层面:月度、年度
【项目资源】: 适用于从基础到高级的各种项目,特别是在性能要求较高的场景中,比如操作系统开发、嵌入式编程和底层系统编程。如果您是初学者,可以从简单的控制台程序开始练习;如果是进阶开发者,可以尝试涉及硬件或网络的项目。 【项目质量】: 所有源码都经过严格测试,可以直接运行。 功能在确认正常工作后才上传。 【适用人群】: 适用于希望学习不同技术领域的小白或进阶学习者。 可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】: 项目具有较高的学习借鉴价值,也可直接拿来修改复刻。 对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】: 有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 鼓励下载和使用,并欢迎大家互相学习,共同进步。 # 注意 1. 本资源仅用于开源学习和技术交流。不可商用等,一切后果由使用者承担。 2. 部分字体以及插图等来自网络,若是侵权请联系删除。
【telesky旗舰店】ACS712 5-30A通用.zip
# 基于GD32F30x微控制器的USB设备驱动程序 ## 项目简介 本项目聚焦于GD32F30x微控制器,开发了一套USB设备驱动程序,可实现USB设备的初始化、配置、数据传输以及中断处理等功能,涵盖主机模式下的枚举过程管理与设备模式的数据传输及中断处理。 ## 项目的主要特性和功能 1. USB设备驱动支持GD32F30x微控制器的USB设备模式,能完成设备初始化、端点配置、数据传输和中断处理等操作。 2. 枚举过程管理在主机模式下,对USB设备的枚举过程进行管理,包含获取设备描述符、设置地址和配置等步骤。 3. 数据传输支持USB设备的数据接收和发送。 4. 中断处理处理USB设备的各类中断事件,如传输完成、挂起、唤醒等。 5. 电源管理具备设备的挂起和唤醒等电源管理功能。 ## 安装使用步骤 1. 环境准备确保已安装GD32F30x微控制器的开发环境,包含IDE和必要的驱动库。 2. 代码下载从指定路径下载USB设备驱动代码。