理解聚合聚合是一组业务对象,总是需要保持一致。因此,我们将聚合作为一个整体进行保存和更新,以保证业务逻辑的一致性。聚合是DDD中最重要的概念。即使不使用DDD写代码,也需要理解这个重要的概念——一些对象的生命周期可以看作一个整体,从而简化编程。通常,我们需要为聚合中的对象使用ACID属性的事务。最简单的例子是订单和订单项。订单商品的更新必须伴随订单的更新,否则会出现总价不一致等问题。订单项目需要遵循订单的生命周期。我们称该订单为聚合根,它就像一个导航器。classOrder{privateCollectionorderItems;privateinttotalPrice;}classOrderItem{privateStringproductId;privateintprice;privateintcount;}Order的totalPrice必须是OrderItem的价格总和,还要考虑折扣等其他问题。总之,对象的变化需要整体更新。理想情况下,最好的方法是将聚合根作为一个整体进行持久化,但问题并没有那么简单。聚合持久化问题如果使用MySQL等关系型数据库,集合的持久化是一件比较麻烦的事情:关系的映射不好处理,层次更深的对象不容易转换。数据转换成聚合会有n+1个问题,关系型数据库的jointable特性不好用。全量更新数据库事务量大,性能低。其他问题聚合的持续存在是DDD美好愿景实现的最大障碍。这些问题有些可以解决,有些则必须权衡取舍。将聚合持久化到关系数据库的问题本质上是一个计算机科学模型问题。聚合持久化是面向对象模型和关系模型的转换,这也是为什么MongoDB不存在这个问题,但是它没有利用关系数据库的特性和能力。面向对象模型关注承载业务能力,而关系模型关注数据一致性和低冗余。描述关系模型的理论基础是范式理论,范式越低,越容易转换为对象模型。理论指导实践,然后我们分析这些问题:“关系的映射不好处理”如果不使用多对多关系,将数据设计成第三范式,我们可以将关系网络退化为一颗树。△网络状关系△树状关系“将数据转化为聚合会出现n+1个问题”使用聚合的时候使用聚合的能力并不容易。列表查询可以使用读取模型直接获取结果集,或者TakeAdvantageofAggregationoverCaching使用缓存来缓解n+1问题。“全量数据更新数据库的事务比较大”设计小聚合,这是业务一致性的代价,基本无法避免,但是对于一般的应用来说,写入和更新数据库的频率并不高.使用读写分离可以解决这个问题。自己实现一个Repository层如果你是用Mybatis或者用原生SQL写程序,你可以自己抽象一个Repository层。这一层只为聚合根提供,所有对象都需要使用聚合根来完成持久化。一种方式是使用MybatisMapper重新封装Mapper。classOrderRepository{privateOrderMapperorderMapper;privateOrderItemMapperorderItemMapper;publicOrderget(StringorderId){Orderorder=orderMapper.findById(orderId);order.setOrderItems(orderItemMapper.findAllByOrderId(orderId))returnorder;}}这个方法有个小问题,领域对象OrderItems这个属性,但是数据库中不可能有项目。有些开发者会认为这里的Order和数据库使用的OrderEntity不是一个类型的对象,所以进行繁琐的类型转换。类型转换和额外的抽象层增加了工作量。如果使用Mybatis,更好的方式是直接使用Mapper作为Repository层,使用XML中的动态SQL来实现上面的代码。另一个问题是,如何处理一对多关系中的移除操作?更简单的方法是直接删除,然后存储到一个新的数组中。也可以比较对象并有选择地实施删除。并增加。完成这些之后,恭喜你,你有了一个完整的ORM,比如Hibernate。使用SpringDataJPA,我们可以使用JPA的级联更新来实现聚合根的持久化。大家在实际操作中发现JPA并不好用。其实这不是JPA的问题,因为JPA做的太多了。JPA不仅有各种状态转换,还有多对多的关系。如果你保持克制,你可以使用JPA来实现DDD,尽量遵循以下规则:不要使用@ManyToMany属性只为聚合根配置Repository对象。避免创建网状关系读写分离。关联、读写分离等复杂的查询,不要对JPA做。JPA仅对单个对象执行查询。在这些基本规则下,您可以使用@OneToMany的级联属性来自动保存和更新聚合。classOrder{@Id@GeneratedValue(strategy=GenerationType.AUTO)privateStringid;@OneToMany(cascade=CascadeType.ALL,fetch=FetchType.LAZY)@JoinColumn(name="order_id")privateCollectionorderItems;privateinttotalPrice;}classOrderItem{@Id@GeneratedValue(strategy=GenerationType.AUTO)privateStringid;privateStringproductId;privateintprice;privateintcount;}OneToMany中的级联有不同的属性。如果需要使update和delete生效,可以设置为ALL。使用SpringDatJDBCMybatis是一个SQL模板引擎,JPA做的太多了。是否有适度的ORM来持久化聚合?SpringDataJDBC是人们为持久化聚合而设计的。从名字上看,它不是JDBC,而是JPA规范的一部分是使用JDBC实现的,这样可以继续沿用SpringData的编程习惯。SpringDatJDBC的一些特点:没有Hibernate中session的概念,没有对象的各种状态,没有懒加载,保持对象的完整性除了SpringData的基本功能,保持简单,只保存方法,事务、审计注解、简单的查询方法等。可以结合JOOQ或者Mybatis实现复杂的查询能力。SpringDatJDBC的使用和JPA几乎一样,不用浪费时间贴代码了。如果使用SpringBoot,可以直接使用spring-boot-starter-data-jdbc完成配置:spring-boot-starter-data-jdbc但是需要注意的是SpringDataJDBC的逻辑:如果聚合root是一个新的对象,SpringDataJDBC会递归保存所有关联的对象。如果聚合根是旧对象,SpringDataJDBC会删除聚合根以外的旧对象再插入,聚合根会更新。因为没有对象的先前状态,所以这是必须要做的事情。你也可以根据自己的策略重写相关方法。使用DomainServiceworkarounds正是因为在使用ORM时有各种限制,抽象一个Repository层会带来很大的成本,所以有一个workaround。这种方式没有使用拥塞模型,也没有Repository保证聚合的一致性,而是使用领域服务来实现相关逻辑,但会被诟病为DDDlite或者不是“pureDDD”。这种编程范式有如下规则:按照DDD四层模型,ApplicationService和DomainService是分离的,ApplicationService负责业务编排,不是必须的层,但也可以由UI层来执行.一个聚合使用DomainService来维护业务的一致性,一个聚合只有一个DomainService。在域服务中使用ORM的各种持久化技术。除了域服务不允许使用ORM在其他地方之间更新数据。当不陷入拥塞模型时,问题变得更加清晰。DDD只是手段,不是目的。对于一般的业务系统,拥塞模型不是必需的。我们的目的是让编码和业务清晰。这里引入两个概念:业务主题。操作领域模型的拟人化对象,用于承载业务规则,即DomainService。例如,订单聚合可以由服务管理以确保业务一致性。我们可以将它命名为:OrderManager。业务对象。聚合和域对象,用于承载业务属性和数据。这些对象需要有状态和自己的生命周期,比如Order、OrderItem。回归最初的编程哲学:程序=数据结构+算法。业务主体负责业务规则(算法),业务对象负责业务属性和数据(数据结构)。这样无论有没有DDD,代码都可以清晰、易懂且易于处理。【本文为专栏作者“ThoughtWorks”原创稿件,微信公众号:Thinkworker,转载请联系原作者】点此查看该作者更多好文