DDD分层架构
DDD落地面临的挑战
前言:
DDD分层的目的是为了将技术复杂度和业务复杂度分离,让领域层负责业务复杂度,让应用层和基础设施层负责技术复杂度。
应用层的挑战:
-
应用层需要解决的技术问题:控制整个请求的处理流程,包括调用领域层来执行业务活动,同时也需要处理好并发、幂等、事务等技术问题。
-
方案:使用责任链的设计模式来应对挑战,责任链模式的优点:可以非常灵活地对处理过程进行拆分,比如可以将参数校验、幂等处理、事务处理、并发控制等拆分到独立的模块里,这些模块很容易被抽取为通用的模块进行复用,同时也实现了这些技术能力的可拔插性,大大降低了代码的复杂度。
领域层的挑战:
如何创建好的聚合?
设计小聚合:
-
大部分的聚合都可以只包含根实体(Aggregate Root),而无需包含其他实体。
-
即使一定要包含,可以考虑将其创建为值对象。
-
聚合根大小的考虑:
-
聚合根大点有利于逻辑的内聚,但是可能存在性能问题。
-
聚合根小点可以解决性能问题,但是可能导致逻辑分散。
-
通过唯一标识来引用其他聚合或实体:
-
当存在对象之间的关联时,建议引用其唯一标识而非引用其整体对象,或将需要的属性构造值对象。
-
边界内的内容具有一致性:如果你发现边界内很难接受强一致,不管是出于性能或产品需求的考虑,应该考虑剥离出独立的聚合,采用最终一致的方式。
-
常见场景:一个主记录对应多条明细记录的场景,方案如下:
-
若可以有效控制明细数量,且明细数量较小时:主记录和明细记录可以设计为一个聚合。
-
若明细数量可能很大时:考虑到性能的问题,我们应该将主记录和明细记录设计为两个聚合,当明细记录更新后发布领域事件,主记录根据明细更新事件做出相应的修改,这样就可以保证主记录和明细记录之间互不依赖。
-
基础设施层中Repository的挑战:
-
过依赖倒置实现各层对基础资源的解耦:
-
Repository分为两部分:仓储接口和仓储实现。仓储接口放在领域层中,仓储实现放在基础层。
-
-
Repository的实现一般需要具备以下特性,以便解放领域层和应用层的复杂性:
-
保证entity的唯一性
-
支持缓存读写:将entity的唯一标识作为key,在本地对entity进行缓存。
-
缓存读:提高读的性能。
-
缓存写:用来支持延迟更新
-
-
支持延迟更新
-
每次操作都直接去更新数据库存在的问题:请求耗时增加、数据库压力倍增。
-
eg:一个请求要批量更新多个实体的信息,如果每个实体信息修改后都立即去更新数据库,那么每次请求都会对应着大量的数据库写操作,这样不但会导致请求的耗时显著增加,同时也给数据带来了巨大的压力。
-
-
方案:采用unit of work的设计模式,将所有的DB写操作都先缓存起来,最后一并进行更新。
-
-
并发控制:多个请求对一个实体进行操作,如果解决并发问题?
-
通过版本号来实现,在实体中添加版本号字段,更新时判断当前实体的版本号和DB中的版本号是否一致,一致则更新,否则抛异常。
-
-