有个疑问困扰了很多年了~

12-10-12 snow0613
我们知道,在各类业务系统,都会存在查询的情况。我们设计一个实体或者叫模型,这个实体肯定有很多字段,一开始的时候系统支持一个或两个字段的查询,比方说客户实体,有name、alias、createTime等多种属性,一开始的需求可能只需要根据name进行查询,后来客户发现这样的查询不充分,也需要查询alias字段。我不知道其他人是什么情况,不过我碰到的大多数的做法是,一开始查询name,那么就支持name查询,等客户发现需要查询alias字段的时候,再改代码,再升级。这有个问题,需要至少动两方面的代码:1、视图层;2、模型层。由于多数人使用的框架以SSH居多,且多数是action->service->dao,那么就会存在一个问题,action要改、service要改,甚至dao也要改。那么这样一个小小的功能,需要修改多处的地方。我认为这样对代码的维护不是个很好的事情。

我曾经参考ibatis的生成工具搞了一个叫example的东东,针对每个实体去设计可扩展的查询。可是我发现这种完全是针对数据库的方式来做的,查单表的时候不错,但我不认为是个好主意。我希望这个设计能脱胎于数据库去实现。

不知道各位有没有好的方式?

8
gameboyLV
2012-10-12 19:51
我记得有些框架可以把查询配置在XML里,当查询变更的时候只要改视图和XML配置就行了

banq
2012-10-17 08:47
采取CQRS读写分离架构,查询读取直接基于SQL,这时查询不一定要用三层,直接action中发出SQL即可。

snow0613
2012-10-18 15:22
哎,其实我的矛盾是在于,用数据库的思维方式和对象设计的思维方式的冲突,如果用IBatis的话,基本上不会出现这个问题,因为一个model对象对应的就是一张表,它支持的是单表查询,可以支持全字段的查询,这是贫血模型;而用充血模型,则在实体中会包含其他实体,如Order中会包含OrderItem实体,这样就无法作到全字段的查询,或者可能是用component组合起来的属性映射到数据库可能是一个个字段,而这些对应到实体可能变成是一个对象而不是一个个的字段。这是本质上的矛盾。

banq
2012-10-18 16:30
2012-10-18 15:22 "@snow0613"的内容
用数据库的思维方式和对象设计的思维方式的冲突 ...


使用CQRS将两者并存在不同领域中:读领域使用贫血模型,写领域使用充血模型,而非CQRS系统中,要么使用贫血模型贯穿读写,要么使用充血模型贯穿读写,都不方便。

在CQRS的读领域,你使用扁平的DTO来表达数据对象,无需Order里面含有OrderItem对象这么复杂,直接Order里面含有OrderItem的Id即可。

而在CQRS写领域,良好的Order包含OrderItem对象结构是一种聚合关系,一致对外,对内保持不变性,使用DDD领域事件等等。

snow0613
2012-10-19 10:08
使用CQRS将两者并存在不同领域中:读领域使用贫血模型,写领域使用充血模型,而非CQRS系统中,要么使用贫血模型贯穿读写,要么使用充血模型贯穿读写,都不方便。

在CQRS的读领域,你使用扁平的DTO来表达数据对象,无需Order里面含有OrderItem对象这么复杂,直接Order里面含有OrderItem的Id即可。

而在CQRS写领域,良好的Order包含OrderItem对象结构是一种聚合关系,一致对外,对内保持不变性,使用DDD领域事件等等。
===============================================================
我发现的确是这个问题,如果只用一个类是无法实现所有的设计组合,我碰到的一个真实的应用场景:
我们是一个互联网应用,图片、音频、视频为核心资源,为了方便用户使用,分别为这些资源进行分类,同时也给这些资源设计标签。
当时我的设计是这样的:

class Image {
private List<Label> labels;
}

class Label {
}

后来发现,这种方式是非常错误的,因为会有从标签查找图片的需求。那么,我应该设计成以下这样吗?

class Image {
}

clss Label {
private List<Image> images;
}

发现还是不对,因为可能还有分页的需求。

那这样的设计肯定是有问题的。所以还是不对。

banq
2012-10-19 10:21
2012-10-19 10:08 "@snow0613"的内容
那这样的设计肯定是有问题的。所以还是不对。 ...


这两种设计其实代表两个方向的单向关联导航,前者可以根据Image查到Label,后者可以根据Label查到Image。

在写入这个场景中,比如修改,需要前者,那么Image一般是聚合根实体,而Label表示值对象,从DDD这个角度认识就自然肯定它们的主次导航关系。

而在读取场景,需要根据Label获得Image,是不是也要设计这种导航关系呢?当然不必,首先你是需要读取,可以直接通过仓储Repository获取,如果使用CQRS架构,直接通过SQL查询即可,比如根据Label的Id到Image数据表中查询(LabelId是Image的外键)。


[该贴被banq于2012-10-19 10:27修改过]

snow0613
2012-10-19 10:31
2012-10-19 10:21 "@banq"的内容
这两种设计其实代表两个方向的单向关联导航,前者可以根据Image查到Label,后者可以根据Label查到Image。在写入这个领域中,比如修改,需要前者,那么Image一般是聚合根实体,而Label表示值对象,从DDD这个角度认识就自然肯 ...


嗯,明白了,谢谢板桥的答复。

goolover
2012-10-19 15:53
采取CQRS读写分离架构,查询读取直接基于SQL。如果关联查询中有原本属于领域层的逻辑,这样不可避免的将业务逻辑写在了sql中,是不是又有点回到数据库驱动开发的思维上了?

[该贴被goolover于2012-10-19 15:54修改过]

[该贴被goolover于2012-10-19 15:55修改过]

banq
2012-10-19 17:27
2012-10-19 15:53 "@goolover"的内容
如果关联查询中有原本属于领域层的逻辑,这样不可避免的将业务逻辑写在了sql中 ...


晕倒,读取的意思是各种报表分析,数据挖掘,或BI商业智能分析等等,这些都是针对现有业务逻辑产生的数据基础上再查询分析,因此和原来的业务逻辑不搭架的。

原来的业务逻辑通过CPU的算法都产生了数据,这些数据就是CQRS中Query用来分析的数据。

我把业务逻辑分解为:
场景 - 事件 - 状态 <===> 实体 - 聚合 - 领域

数据库中的数据就是状态,状态是事件产生的结果,也是实体的结果,这些都代表业务逻辑。

见:事件、契约设计与BDD

[该贴被banq于2012-10-19 17:28修改过]

SpeedVan
2012-10-20 15:14
2012-10-19 15:53 "@goolover"的内容
采取CQRS读写分离架构,查询读取直接基于SQL。如果关联查询中有原本属于领域层的逻辑,这样不可避免的将业务逻辑写在了sql中,是不是又有点回到数据库驱动开发的思维上了? ...


这不是CRUD,CQRS中的Q是组织状态数据进行返回,就相当于“瞥一眼”就走了,而不是找谁谁谁来参与啥啥啥。这就很好区别“找实体集合”与“找数据集合”。因为我们运算基于业务逻辑,所以需要实体,但获取信息往往不是一整个实体,可能是部分字段,可能是字段变换值,甚至可能是统计值或期望值(与实体无关),其实就是一个“旁观者”(与“参与者”区别)。

猜你喜欢