UserRepository接收到CreateUserCommand之后可能会产生很多操作,比如调用UserDAO,调用UserAccountDAO,调用验证或者日志类等,这些操作必须保持一致性(发生异常时全部回滚),而UserRepository就是维护一致性的存在。

UserRepository可以调用User领域内的DAO,也可以调用CrossCutting Layer的方法,并不针对某一个具体的数据表。

PassportService就是传说中的领域服务,负责调用UserRepository,BBSUserRepository等,并对UI层提供API接口。

这是完整的调用路径:
           UI
           ↓
        PassportService
     ↓             ↓
  UserRepository      BBSUserRepository
  ↓      ↓
UserDAO  UserAccountDAO

如果有不涉及数据库的业务计算,比如更新缓存、计算积分等,可以由UserRepository调用相关的BO处理。

2012-12-21 21:49 "@SpeedVan"的内容
一个领域一种语言一种逻辑。领域模型思考的是业务逻辑,通过领域语言直接描述实现有效扩展,而我们使用DAO的时候,是在不断地想如何拼对sql。 ...

领域模型思考的是业务逻辑,DAO方式也需要思考业务逻辑呀,在Service中就有业务逻辑存在,所以我没有明白你的领域模型怎么体现业务逻辑,在哪体现? 通过领域语言直接描述实现有效扩展,如何通过领域语言实现有效扩展,能不能举例说明?太抽象

2012-12-21 21:49 "@SpeedVan"的内容
额外地:实际上领域化是逻辑化的走向,Repository像DAO主要受语言局限性。其实最终走向,Repository最终会演变成一个集合,通过高阶函数替换所谓的查询。因为内存不能容纳整个数据库,所以Repository会以实现集合接口而存在 ...

整个这段话,我没有明白Respository到底是用来干什么的?为什么要演变成集合,能否说出根本所在

2012-12-22 10:52 "@cloudstack"的内容
领域模型思考的是业务逻辑,DAO方式也需要思考业务逻辑呀,在Service中就有业务逻辑存在 ...

你的这个问题很好,关键就在这里,DAO/Service中的业务逻辑和对象是分离的。见Match这个案例这种经典做法:

Match和MatchService两者是分离的。

而DDD/CQRS是将这两者合在一个Match中,再看看Match案例最后代码:

你能看出这两者区别吗?我不想用我的语言解释,可能你还是不理解,你先说出你的理解,我再在你的思路上解释下去。大概很多人都有你这种想法,所以,也是相当于对Match这个案例详细说明吧。
[该贴被banq于2012-12-22 11:06修改过]

2012-12-22 10:50 "@gameboyLV"的内容
UserRepository接收到CreateUserCommand之后可能会产生很多操作,比如调用UserDAO,调用UserAccountDAO,调用验证或者日志类等,这些操作必须保持一致性(发生异常时全部回滚),而UserReposi ...

感谢gameboyLV,你的整个一大段描述,实际上还是没有讲清楚,CQRS引入Repository是什么概念,从你的描述中,我只知道Repository就是为了维护一致性而存在,这在传统WEB开发中遍地都是,总体感觉这些概念像什么领域服务,Repository等在普通WEB开发中(不用CQRS的情况)在也存在,没有描述出CQRS的设计价值呀

2012-12-22 10:59 "@cloudstack"的内容
整个这段话,我没有明白Respository到底是用来干什么的?为什么要演变成集合,能否说出根本所在 ...

不知道你有没有用过NoSql。部分NoSql是将数据保持在内存中的,你可以像使用Sql一样往NoSql里添加、修改、读取数据,只是NoSql操作速度更快而已。
当内存占满时,NoSql才会使用磁盘缓存。

你可以想象,UserRespository里存储的都是UserEntity,换句话说UserRespository等于List<UserEntity>,如果使用NoSql作为数据库,所有的用户实体是不是都保持在内存里了?

2012-12-22 11:10 "@cloudstack"的内容
从你的描述中,我只知道Repository就是为了维护一致性而存在 ...

要维护一致性,也必然要对外部隐藏UserDAO吧,如果UI能随意调用UserDAO,而不通过Repository,那后果可以想象。

既然UserDAO外部调用不了了,那想取用户信息怎么办?就得通过UserRepository.Get()操作返回一个UserEntity

//用户实体
public class UserEntity
{
//基本信息
public UserInfo {get;set;}
//账户信息
public UserAccount {get;set;}
}

调用UserDAO返回的是UserInfo,UserInfo里存储的是属性
调用UserRepository返回的是UserEntity,UserEntity里存储的是结构化的数据

2012-12-22 11:06 "@banq"的内容
你的这个问题很好,关键就在这里,DAO/Service中的业务逻辑和对象是分离的。见Match这个案例这种经典做法:
Match和MatchService两者是分离的。
而DDD/CQRS是将这两者合在一个Match中,再看 ...

看了你的代码,很清楚的展示出了DAO方式和CQRS两种架构的区别,DAO方式模型一般都是失血的,而CQRS中模型是充血,DAO方式的业务在Service层中体现,CQRS的业务AggregateRoot中体现,AggregateRoot通过生产相应的事件触发handle方法调用来处理业务逻辑,这要感觉更OO化,DAO方式似乎不太OO,但是层次很清晰,J2EE的N层架构都是这么做的,层次很清唽,区别我看清楚了,这两种方式一个是失血,一个是充血+事件驱动,可是问题来了,通过充血+事件驱动又能给我们带来什么优点呢?它的缺点是什么呢?这个能不能举例说明一下呀

2012-12-22 11:51 "@cloudstack"的内容
通过充血+事件驱动又能给我们带来什么优点呢?它的缺点是什么呢?这个能不能举例说明一下呀 ...

接楼上:
现在UI通过UserRepository取到了UserEntity,要对UserEntity执行停用账户的操作,将Stop方法放在UserRepository里显然是不合适的,因为Repository实际上是一个List<UserEntity>嘛。

将Stop方法放在UserEntity里也有问题啊?UserAccountDAO应该被UserRepository调用而不是UserEntity。

解决问题的办法就是引入领域事件,UserEntity的Stop方法中发送StopUserAccountCommand命令给消息总线,消息总线再将消息转发给UserService。这样就实现了UserEntity与其他对象的解耦。

2012-12-22 11:11 "@gameboyLV"的内容
不知道你有没有用过NoSQL。部分NoSQL是将数据保持在内存中的,你可以像使用Sql一样往NoSQL里添加、修改、读取数据,只是NoSQL操作速度更快而已。
当内存占满时,NoSql才会使用磁盘缓存。
你可以想象,UserResp ...

从你的描述中只能看出Respository是一个内存List,这样访问速度更快,CQRS引入Respository仅仅是为了性能考虑吗?

2012-12-22 11:35 "@gameboyLV"的内容
要维护一致性,也必然要对外部隐藏UserDAO吧,如果UI能随意调用UserDAO,而不通过Repository,那后果可以想象。

既然UserDAO外部调用不了了,那想取用户信息怎么办?就得通过UserRepository.Get( ...


我总结一下你的两个贴的结论,CQRS进入Respository是为了两个目标:
1. 将Respository设计为一个内存List,以提高性能
2. Respository负责维护数据的一致性,里面包含所有相关的信息,这些信息只能通过Respository维护
不知道还有没有别的意见?

2012-12-22 12:32 "@cloudstack"的内容
我总结一下你的两个贴的结论,CQRS进入Respository是为了两个目标: ...

对,再加上我最后一个贴的内容,总共有三条:

1. 将Respository设计为一个内存List,以提高性能
2. Respository负责维护数据的一致性,里面包含所有相关的信息,这些信息只能通过Respository维护
3. 领域事件负责解耦领域对象之间直接的调用关系

2012-12-22 11:51 "@cloudstack"的内容
可是问题来了,通过充血+事件驱动又能给我们带来什么优点呢? ...

你可能还是没有从你自己角度认识这两段代码的区别,比赛Match的开始结束动作被你的DAO绑架到了Service里了,而充血模型不过让其回归正常,从日常概念来讲,开始 结束这个行为当然应该是比赛的行为,凭什么跑到Service里?因为用了数据库?因为SOA吗?那这不是被数据库绑架了吗?打个比喻:

人是母亲生的,但是母亲不能替代你吃饭大小便,母亲也不能以爱的名义指挥你干这干那,如果是这样,你被母爱绑架了。

OO不是为了OO而OO,而是让一切回归正常。如果把本属于一个的胳膊腿都截断,放到以爱的名义的DAO/Service中,这是摧残,这是黑客Crack。

所以,充血模型只是让模型正常一点,而不是被锯断被黑了。其他都是次要。不能因为用了计算机,就让业务模型支离破碎。不能因为计算机太伟大,而摧残搞破业务模型,让业务模型首肢分离。我们更不能对这种畸形熟视无睹,而且被洗脑,看到正常反而不舒服。

开始和结束的动作放在Entity里是肯定的,但是动作的内容放在哪里好呢?

直接写在Entity里感觉不优雅,而且耦合度太高。写在仓储或者服务里更加不靠谱了,理论上Entity应该在仓储或者服务的下层才对。

想来想去,还是写在独立的BO里吧。消息总线接收到消息之后通过IOC容器获取消息的执行类,执行完之后将处理结果再通过消息传递到Entity。

在我印象中Entity应该仅仅是申明了Entiy所包含的数据结构,和Entiy所能提供的操作才对。操作的内容(Command Handler)可能随时会有变化,而操作和数据结构是不会变化的。

比如handle(MatchStartedEvent event)方法中1.0版需要判断天气情况,2.0版需要判断场地情况,因为业务变化而一直修改Entity申明,总感觉不合适。

2012-12-22 15:10 "@banq"的内容
你可能还是没有从你自己角度认识这两段代码的区别,比赛Match的开始结束动作被你的DAO绑架到了Service里了,而充血模型不过让其回归正常,从日常概念来讲,开始 结束这个行为当然应该是比赛的行为,凭什么跑到Service里?因为用了数据 ...

也就是说CQRS是为了贯彻正常的逻辑思维,模型应该如何设计,什么样的模型是正常化设计,当然OO的思想只是为使模型正常化,所以当你使用CQRS思维编程时,你会发现程序更加OO,更化正常,胳膊就是胳膊,腿就是腿,分别有他的作用,儿子不能替代老子,老子也不能替代儿子,各有各的行为,这是不是就是DDD的思想呢?