DDD Implementation Steps in FP
流程概要
Stage | Process | Tool | Git |
---|---|---|---|
准备 | 虚拟用户角色,撰写系统描述 | System Narrative | git init |
发现业务目标Why | Impact Mapping | ||
挖掘用例及其关系 | Use Case Diagram | ||
准备开发与部署环境 | Deploy Diagram | git checkout -b develop | |
设计 | 逐步找出Who(Actor)-How(Impact)-What(Feature/Deliverable) | Impact Mapping | |
对应Impact Mapping撰写Key Specification | Gherkin (Given-When-Then) | ||
从Specification提取统一语言UL术语表 | Glossary Table | ||
尝试划分子域 | Class Diagram | ||
根据Key Specification和子域划分,编写验收测试 | Spock Framework | ||
实现 | 从Key Specification中提取Key Example驱动编码 | Gherkin | git checkout -b feature-xxx |
每完成一个Key Example提交一次验收测试 | Spock Framework | git merge feature-xxx | |
回溯以上,完成全部Key Specification | Live Document | ||
实现UI | Kanban | ||
实现日志、权限管理等模块 | |||
创建数据库脚本 | |||
完成自动化集成测试 | CI,Selenium | git merge develop | |
部署 | 打包 | Gradle/Maven | git checkout -b release |
发布 | CI | git merge release; git tag version master |
编码要义
Service
根据Key Example中描绘的场景Scenario,勾勒出需要向用户提供的Domain Service(以下从编码的角度称其为领域API),以业务动作和逻辑驱动构建相应的数据结构,组建为领域ADT,由外向内进行编码实现。
辅助工具
- Sequence Diagram
- Data Flow Diagram
- Gherkin
最佳实践
- 使用trait与纯函数定义领域API模板,使用object定义API的具体实现,确保API定义与实现的脱耦。
- 函数名称、变量名称等均采用陈述性的设计,使API成为自我阐述目的的接口。
- 坚持以函数作为API返回值,使用模式Kleisli[M, A, B](即A=>M[B]的代数结构)进行封装,方便实现for推演。
- 使用Try等结构面向失败建模,避免异常对业务逻辑的干扰。
- 利用Functor/Applicative Functor/Monad模式,提高领域模型的可复用度。
- 使用影子类型Phantom配合Kleisli模式,定义API的状态迁移,避免出现非法状态。
值对象/实体/聚合
根据领域API的需要,定义值对象。再根据生命周期不同,将部分值对象提升为实体,赋与唯一标识ID。最后根据实体与值对象之间的关系,定义聚合。
辅助工具
- Sequence Diagram
- Data Flow Diagram
- Class Diagram
- Glossary Table
最佳实践
- 使用case class定义领域数据。
- 使用智能构造器创建聚合实例。
- 聚合的ID是全局的,实体的ID则仅限于聚合内部。
- 聚合的本质是关联,坚持用ID在实体间建立单一方向的关联,在需要时才构建或者加载其实例。
- 为聚合中每个需要向外暴露的属性定义一个Lens,以有效控制聚合的更新操作。
- 使用State Monad管理聚合的状态改变。
- 使用copy,避免直接暴露聚合内部的List/Map等数据结构。
Repository
根据领域API的需要,定义Repository的存Store、取Fetch和查询Query操作,为聚合提供生活的空间。
辅助工具
- Dependence Injection
- Class Diagram
最佳实践
- Repository仅供Service使用,避免暴露给外部。
- Repository通常以一次会话为其生存期。
- 使用Reader Monad实现持久化的依赖注入。
Bounded Context与Domain Event
子域与BC没有完全的一一对应关系,所以根据区分不同服务或区分同名却不同义之领域概念的需要,定义BC。在BC内部,定义Domain Event,作为BC内部不同聚合之间通信的载体;在BC外部,定义Command和Message,作为Application Service协调各BC的载体。
最佳实践
- 建立BC与Namespace的对应关系,实现代码的模块化。
- 使用UTC统一所有Domain Event的时间戳。
- 使用扁平化的数据传输对象DTO,作为Domain Event、Command和Message的载体。
- 使用模式Free Monad,把领域行为抽象为表达式树,再定义独立的解释程序,实现早抽象、晚执行。
CQRS与Event Sourcing
采用CQRS模式,配合Event Sourcing,在Event Store支持下,确保模型在读写分离条件下实现最终一致性Eventually Consistency。
最佳实践
- 建立快照Snapshot,提高重塑聚合的效率。
- 每个聚合在同一时间内只能处理一条Command。
- 在聚合变化后,借助写模型的持久化事件的事务,确保Domain Event正确存入Event Store。
- 在Event Store内部,借助推送消息的事务,确保聚合变化事件被压入消息队列等基础设施,从而传递到外部。
转载请注明出处及作者,谢谢!