如何实现DDD实践中类目树管理的丝滑操作?

摘要:在上次反思DDD实践之后,在类目树管理项目中再次实践DDD。从需求分析到建模和具体的落地,结合个人体会,都是干货。
背景 距离DDD实践反思写完已经过去一年,期间发生了很多事情,比如换了工作,细节按下不表;新团队的技术负责人对DDD在团队里的落地很关心,问最近有没有什么进展?这就很尴尬了:之前我接手并主要负责的XXX服务在现阶段是不太适合用DDD的,自身和外部其他几个服务的边界并不清楚(其中包含了一些历史技术债),而且当前处于一个变化比较快的阶段,也没有什么业务输入,不太适合贸然重构,所以并没有在XXX服务中搞DDD。 做技术总要有点追求嘛,虽然现阶段工作最高优先级还是保证业务快速发展,还是想继续实践下DDD的。 这时正巧一个老应用要做重构,在这个基础上一个新的类目管理功能,虽然是一个新的领域,但是产品文档确定的业务规则已经非常清晰,并且后续变化不会很大。美中不足的是需求对应的功能此时已经用传统的CRUD的方式写完了大半了,纠结了半天,还是决定:搞! 本文对于DDD的基础术语就不再单独讲解了,下面直接进入正题。 原则问题 关于DDD,我一年前观点基本没有变化,这里再总结归纳一下。要先确定是否满足以下条件,再考虑是不是要用DDD,不要为了DDD而DDD。永远记住:没有银弹! 实践时,你就会发现DDD在项目落地时做了很多折中,不能教条化地照搬。 业务规则要有一定的复杂性和稳定性。如果一个业务通过CRUD就能轻易的搞定且以后也不会变得很复杂,或者业务还一直在快速变化(这也意味着经常有很强的的项目时间节点要求和临时性的规则),不要用DDD。 域的划分是清晰的,建模是准确的,领域方法是可以梳理的且足够丰富的,是考虑使用DDD先决条件。域的划分不等于将一个应用强行拆成很多个应用,人为地提升系统复杂性。 不要带来过多的额外成本,不要舍本逐末。如果因为DDD导致一个应用的开发、测试、运维成本翻倍,甚至引入了更多的bug,那么就要反思下这次实践是否成功了。 需求分析 这里概括一下需求要点,已刨除掉需求具体的背景以及和本文无关的其他项目需求内容。 本次需要实现一个管理如下图的类目树结构的功能: (图源:https://t.cj.sina.com.cn/articles/view/7321552158/1b466051e001010bfc) 具体的规则和支持的操作: 类目节点组织成一棵或多颗树,每个类目节点下可以有一个或多个子类目节点 1.1 子类目节点是有序的,可以进行重排序 1.2 最顶层的类目节点是根 1.3 类目节点上可以关联多个同种类型的内容实体 类目节点可以新增、删除、重命名、上架、下架 2.1 上架和下架是类目节点的状态。如果类目节点下没有关联内容,或者它其下没有上架的子类目节点,无法上架。 2.1 删除节点时,其下的子节点和子节点关联的内容需要一并删除 建模 象征性地画一下限界上下文和ER图,因为隐藏了很多细节所以看上去很简单。ER图里并没有聚合根,要问为什么请继续往后看。 再实践——落地 怎么用代码表示领域对象:故弄玄虚还是打牢地基? DDD只在脑中有概念是不够的,为了将概念转化为代码,第一步就是把这些概念变成代码,这样才能指导后续的编写。 实际上,这就可以看做是折中的开始了,因为DDD本身是不关心具体存储的,但是做模型设计,你必须考虑如何持久化。 值对象 本文中为了实现类目树本身并不会用到继承以下值对象的类,为了完整性考虑才写出来的。 点击查看代码 /** * 值对象抽象类 * */ public abstract class ValueObject { } /** * 字段型值对象 * * 表示这个值对象会使用表字段来存储。 * * 并不总是表示一个单一的字段, 可能是多个字段组合而成。 * * 你可以把枚举也看做值对象,但enum是没法继承这个类的。 */ public abstract class FieldValueObject extends ValueObject { } /** * 对象关系映射型值对象 * * 表示这个值对象会使用关系数据库映射的方式来存储。
阅读全文