尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Spring Data Relational 聚合根到底该多大?一次并发事故换来的 DDD 落地教训

Spring Data Relational 聚合根到底该多大?一次并发事故换来的 DDD 落地教训 Spring Data Relational 聚合根到底该多大一次并发事故换来的 DDD 落地教训【免费下载链接】spring-data-relationalSpring Data Relational. Home of Spring Data JDBC and Spring Data R2DBC.项目地址: https://gitcode.com/gh_mirrors/sp/spring-data-relational一、事故现场所有数据都塞进一张表之后想象你是某电商团队的新人。接手的第一天就发现订单、订单明细、收货地址、甚至买家昵称全挤在同一张t_order大表里。改地址要锁整行订单加一条明细要让整行数据重新写入两个客服同时处理同一笔订单时后保存的那个人会把前一个人的改动整个覆盖掉。线上工单一个接一个DBA 看着这张表直摇头。这不是表设计问题这是领域建模问题。你缺少一个叫聚合根Aggregate Root的东西——Spring Data RelationalSpring Data JDBC 与 Spring Data R2DBC 的共同底座恰恰是围绕它构建的。读完这篇文章你会知道聚合根为什么能把这类事故挡在门外以及怎么用框架自带的机制把它落地。二、聚合像什么像部门对外只有经理说了算把聚合想成一个部门订单明细是员工收货地址是行政专员而订单是经理。部门内部怎么协作地址改了要同步运费、明细变了要重算总额是内部事务外人无权干预外面要办任何事只能找经理。翻译成数据库语言就是订单表、明细表、地址表属于一个聚合订单是这个聚合的根。Spring Data Relational 的整套持久化逻辑都以这个根为入口——你在仓库Repository里操作的永远是根实体框架替你把这个根下所有实体Embedded嵌入式对象、MappedCollection集合的增删改查组织成一组动作一起执行保证要么全成功、要么全回滚。你可以在这段代码里看到它的较真spring-data-relational/.../conversion/DefaultRootAggregateChange.java会校验操作目标必须是聚合根类型并抛出不匹配的提示。也就是说框架从一开始就假设——不存在脱离聚合根的裸实体操作。三、对照实验错误设计与正确设计先看一个典型错误——跨聚合直接持有对象引用public class Order { private Customer customer; // ❌ 订单里直接塞了整个客户对象 private ListOrderItem items; }问题在哪保存订单时框架会尝试级联保存customer两个聚合的边界瞬间被击穿改客户地址会连带订单一起变脏锁冲突、数据漂移随之而来。正确做法是只保存对方的 ID用AggregateReference表达我认识它但不管理它public class Order { private AggregateReferenceCustomer, Long customerId; // ✅ 只存 ID private ListOrderItem items; // 这才是聚合内部成员 }一个聚合一个仓库、聚合之间只认 ID——这两条规则就是 DDD 里高内聚、低耦合的落地形态。想验证自己画对没有可以看仓库里JdbcRepositoryEmbeddedNotInAggregateRootIntegrationTests这类测试它专门验证不属于聚合根的嵌入式对象会被怎样处理是很好的对照样本。四、如何正确划分聚合边界划分边界没有银弹但有一个可操作的判据问一句这两件事是否必须同时成功、同时失败必须就放进同一个聚合可以稍后再说就拆成两个聚合用领域事件异步对齐。订单和明细必须同时落库 → 同一聚合明细用MappedCollection声明。订单和库存可以各自成功再对账 → 两个聚合库存只暴露 ID。订单和买家买家改了昵称订单不该跟着被锁 → 两个聚合订单存customer_id即可。五、用 Version 乐观锁管住并发回到开头的事故两个人同时改同一订单。聚合根救得了结构问题救不了并发问题这需要乐观锁。给根实体加一个Version字段public class Order { Version private Long version; }之后每次更新框架都会带上WHERE version ?并让version 1如果版本对不上直接抛异常。你在JdbcRepositoryConcurrencyIntegrationTests里能看到完整的并发演练。简单一行注解就把后写覆盖先写变成了后写直接失败、由你决定重试还是提示用户。六、领域事件让聚合之间优雅地说话聚合越独立越需要一套隔空传话机制这就是领域事件。在聚合根上用DomainEvents标注一个返回事件集合的方法再配合AfterDomainEventPublication清空已发布事件框架会在保存聚合后自动把事件发出去别的聚合自己订阅处理。事件在仓库层发布JdbcRepositoryFactory就承担了这块装配工作。需要提醒的是 JDBC 与 R2DBC 的差异JDBC 是同步模型保存完顺手发事件心智负担很小R2DBC 是响应式模型没有同步方法返回后再回调这回事所以它把生命周期拆成了显式的实体回调比如BeforeConvertCallback、AfterConvertCallback在spring-data-r2dbc/.../mapping/event/下。你用 R2DBC 时要在这些回调里做转换和补充逻辑而不是指望保存方法顺便做点别的。七、常见误区与避坑清单把外键当对象引用实体里直接写Long customerId没问题但写Customer customer就要警觉——那通常意味着越界。跨聚合直接持有实体需要别人的数据时用AggregateReference持有 ID需要数据时再查一次。聚合越划越大一个聚合管八张表锁的粒度、事务的跨度全失控。宁可多拆几个小聚合用事件串联。一张表装下全世界这不是聚合是事故的温床。八、FAQ你可能还会问Q聚合根一定要实现什么接口吗不必。Spring Data Relational 里根是概念上的——只要它是仓库操作的实体类型框架就把它当根处理。QR2DBC 下聚合根思想和 JDBC 完全一样吗边界划分、ID 引用、乐观锁的写法一致差别在事件与回调的时机响应式模型下要显式使用回调。Q想先跑通一个例子从哪开始直接看AbstractJdbcAggregateTemplateIntegrationTests里的saveAndDeleteAllByAggregateRootsWithVersion()这类方法一个方法就是一次完整的聚合生命周期演示。下一步行动建议别急着重构线上系统。先拿一个订单模块画聚合边界图标出哪些对象同生共死然后按聚合内Embedded/MappedCollection、聚合间AggregateReference改一版领域模型补上Version再把JdbcRepositoryConcurrencyIntegrationTests跑通作为验收标准。你会发现事故里那张大表终于可以安心拆掉了。【免费下载链接】spring-data-relationalSpring Data Relational. Home of Spring Data JDBC and Spring Data R2DBC.项目地址: https://gitcode.com/gh_mirrors/sp/spring-data-relational创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表