领域驱动设计实践中的几点思考

1.  要尽量建立充血的entity/aggregate模型,只有不适合放在entity/aggregate的行为才需要提升到Service层去实现,一般这是由DDD的一些设计准则导致的。(1) 要保持小的aggregate设计,保证事务执行的粒度 (2)要避免从aggregate直接navigate到其他相关aggregate的集合,即使Lazy Loading DDD也不提倡,而这样导致一些业务方法在没有repository/service注入的情况下没办法实现相关业务逻辑。有人认为这点多少是与传统的面向对象设计有冲突的,然而确实在实践中大多数情况下是应该遵守的,比如Category.getArticles(), 有人认为这是Category应该提供的行为接口,然而在DDD设计中是极力反对Category提供getArticles的访问方法的,反而用ArticleRepository.findByCategory来实现,这样的访问是可控并且有意义的,比如加入分页参数对于repository的query方法来说是有意义的,反之,如果在Category.getArticles加入分页信息是有点滑稽的,底层的分页概念侵入到了业务模型,如果不加,那么当某个category分类下面包含很多acticles时,Category.getArticles这么一个普通的可能是业务代码无意间的调用就会造成极大的系统内存抖动。

2.  Service层应该是无状态的,这里有个有趣的问题国内外的架构师一直在争论——Service层方法应该接受aggregate的实例,还是相关aggregate对应的ID?如果是前者,那么在application层应该负责保证生成正确的aggregate实例,这可能会带来一些不必要的查询,因为有的方法只需要ID即可,不需要实例。有些架构师会设计一个ServiceFacade的层,来处理DTO层传过来的包括ID等各种简单类型的String,Long值,而Service层只针对aggregate实例来做业务逻辑的计算。对我而言,我更喜欢这种设计,即service层针对实例来实现方法,虽然会带来一些潜在的不必要的查询,但是当entity/aggregate是充血建模的时候,绝大多数场景仅仅ID都是不够的,当然这也取决于你的service方法是否需要。总体来说在这一点上DDD的原则和传统的OOP设计是有部分冲突的。DDD也认识到没有类型信息的简单类型Long,String是非常容易误用和不友好的,所以一个常用的技巧就是DDD用一个值对象(Value Object)来包装ID,即使这个ID就是一个Long类型,比如用class ArticleId来包装article的ID,这样在service层统一使用aggregate的ID而不是实例,由service方法来决定是否要构造这个实例,这也是一种非常常见的方案。

Leave a Reply

Your email address will not be published. Required fields are marked *