支付DDD建模
建模目的
在 MVC 的分层结构就像家里所有人的衣服放一个大衣柜、所有人的裤子放一个大库柜。衣服裤子(对象),很少的时候很节省空间,因为你的裤子别人可能也拿去穿,复用一下开发速度很快。但时间一长,就越来越乱了。🤨 一条裤子被加肥加大,所有人都穿。
而 DDD 架构的模型分层,则是以人为视角,一个人就是一个领域,一个领域内包括他所需的衣服、裤子、袜子、鞋子。虽然刚开始有点浪费空间,但随着软件的长周期发展,后续的维护成本就会降低。
简单来说就是之前的mvc适用于小型的项目,直接设计直接编码,后期迭代的压力会越来越大,项目会越来越臃肿。DDD提供的专门建模方法和统一的名词进行设计。DDD 的统一建模语言不涉及具体的技术编码,具有较强的通用性,因此可以让产品、研发、测试、架构师等人员一起参与讨论建模过程,促进跨职能的沟通和协作。如:领域、领域模型(实体、聚合、值对象)、领域服务、端口适配器、仓储、界限上下文、领域编排等名词。目的是为了我们做工程开发时提供指导方案,就像一栋大楼的设计蓝图一样,也像一个超市中会有不同品类的货架,需要提前规划好。所以你需要在工程开发时所需的各类核心内容,都会在建模中体现,如:分几个包、有哪些核心对象、要串联什么流程、有哪些核心业务要实现、过程中与外部服务的交互。
建模过程
以一个用户为起点,通过行为命令,发起行为动作,串联整个业务
- 蓝色 - 决策命令,是用户发起的行为动作,如:开始签到、开始抽奖、查看额度等。
- 黄色 - 领域事件,过去时态描述。如:签到完成、抽奖完成、奖品发放完成。它所阐述的都是这个领域要完成的终态。
- 粉色 - 外部系统,如你的系统需要调用外部的接口完成流程。
- 红色 - 业务流程,用于串联决策命令到领域事件,所实现的业务流程。一些简单的场景则直接由决策命令到领域事件就可以了。
- 绿色 - 只读模型,做一些读取数据的动作,没有写库的操作。
- 棕色 - 领域对象,每个决策命令的发起,都是含有一个对应的领域对象
用例图
用户与系统交互的最简表示形式,展现了用户和与他相关的用例之间的关系。通过用例图,人们可以获知系统不同种类的用户和用例。用例图也经常和其他图表配合使用。
领域事件
整理结果事件,可能会出现的结果。
连接事件
通过决策命令串联领域事件,并填充上所需要的领域对象。
划定领域
有了识别出来的领域角色的流程,就可以非常容易的划分出领域边界了。先在事件风暴图上圈出领域边界,之后在单独提供领域划分。
架构设计
MVC架构
在这个架构里面,很多东西都会堆砌在service层里面。
DDD架构
在 DDD 分层架构下,以支撑 domain 核心领域实现拆分出基础设施(infrastructure),来承接对外部资源的调用。触发器(trigger)向外部提供服务。之后 app 为应用启动、api 为接口定义、types 为通用信息、case 为编排。在这样一套结构下,用于开发工程的各项科目也可以被优雅的分配到各个分层结构了。相对于 Service + 数据模型的贫血模型结构,现在就主要以 domain 为核心开发业务功能,不会在 domain 工程模块下,引入其他各类外部组件了,这样就可以更加关心业务功能开发
分包方式
然大家用的都是 DDD,也都有对应的模块和分包,但在细节之处还是会有一些差异。这块是有差异的。另外这东西没有绝对的好和坏,就像厨房里的碗筷是是放一起的,卫生间的马桶也是共用的,这说明分包也是需要按照最符合自己所需来设定。
架构分层
接口定义 - xfg-frame-api:
因为微服务中引用的 RPC 需要对外提供接口的描述信息,也就是调用方在使用的时候,需要引入 Jar 包,让调用方好能依赖接口的定义做代理。为了让别人(服务调用方)能正确地调用你的服务,你必须提前给他一份详细的“说明书”。这个“说明书”就是一个包含了所有接口、方法、参数定义的Jar包。
// 在 xfg-frame-api 中
public interface OrderService {// DTO也在api模块中,用于参数传输Response<OrderDTO> createOrder(CreateOrderRequest request);
}public class CreateOrderRequest implements Serializable {private Long userId;private Long productId;// ... getter, setter
}
应用封装 - xfg-frame-app:
这是应用启动和配置的一层,如一些 aop 切面或者 config 配置,以及打包镜像都是在这一层处理。你可以把它理解为专门为了启动服务而存在的。应用的启动入口和全局配置容器。它是所有层的“粘合剂”和“地基”。
包含内容:
Spring Boot的启动类:
Application.java
(带有@SpringBootApplication
)。全局配置:
ApplicationConfig
。AOP切面:如全局异常处理、日志记录、权限验证。
Starter配置:集成第三方组件的配置。
Dockerfile:用于打包镜像。
领域封装 - xfg-frame-domain:
领域模型服务,是一个非常重要的模块。无论怎么做DDD的分层架构,domain 都是肯定存在的。在一层中会有一个个细分的领域服务,在每个服务包中会有【模型、仓库、服务】这样3部分。实现核心业务逻辑和业务规则。这是软件的心脏。
核心结构(DDD战术设计):
模型(Model):通常是实体(Entity) 或值对象(Value Object)。如
Order
(订单实体)、Money
(值对象)。仓库接口(Repository):定义如何持久化/获取模型的接口。如
OrderRepository
。服务(Service):处理那些不适合放在实体中的业务逻辑(通常涉及多个实体交互)。
// 在 xfg-frame-domain 中
@Data
public class Order { // 模型 - 实体private Long orderId;private OrderStatusEnum status;private Money totalAmount;// 核心业务方法可以放在实体里public void pay() {if (this.status != OrderStatusEnum.UNPAID) {throw new BusinessException("订单状态异常");}this.status = OrderStatusEnum.PAID;}
}public interface OrderRepository { // 仓库接口Order findById(Long orderId);void save(Order order);
}public interface OrderDomainService { // 领域服务Order createOrder(Long userId, Long productId);
}
仓储服务 - xfg-frame-infrastructure:
基础层依赖于 domain 领域层,因为在 domain 层定义了仓储接口需要在基础层实现。这是依赖倒置的一种设计方式。实现domain
层定义的接口,并处理所有技术细节。
// 在 xfg-frame-infrastructure 中
@Repository // Spring的注解
public class OrderRepositoryImpl implements OrderRepository { // 实现Domain层的接口@Autowiredprivate OrderMapper orderMapper; // MyBatis的Mapper@Overridepublic Order findById(Long orderId) {OrderDO orderDO = orderMapper.selectById(orderId);// 将数据对象DO转换为领域实体Entityreturn convertToEntity(orderDO);}
}
领域封装 - xfg-frame-trigger:
触发器层,一般也被叫做 adapter 适配器层。用于提供接口实现、消息接收、任务执行等。所以对于这样的操作,小傅哥把它叫做触发器层。系统的输入/输出适配器。负责与外部世界(HTTP、RPC、消息、定时任务)交互,并将外部请求转换为内部调用。
// 在 xfg-frame-trigger 中
@RestController
public class OrderController {@Autowiredprivate OrderCreateCase orderCreateCase;@PostMapping("/order/create")public Response<OrderDTO> createOrder(@RequestBody CreateOrderRequest request) {// 1. 基本参数校验 (@Valid)// 2. 调用Case层(或直接调DomainService)完成业务OrderDTO orderDTO = orderCreateCase.createOrder(request);// 3. 返回统一格式return Response.success(orderDTO);}
}
类型定义 - xfg-frame-types:
通用类型定义层,在我们的系统开发中,会有很多类型的定义,包括;基本的 Response、Constants 和枚举。它会被其他的层进行引用使用。存放整个项目(或项目群)通用的、与业务无关的数据结构定义。
// 在 xfg-frame-types 中
public enum OrderStatusEnum {UNPAID(0, "未支付"),PAID(1, "已支付");// ... code, desc, getter
}public class Response<T> {private String code;private String msg;private T data;// ... getter, setter
}
领域编排【可选】 - xfg-frame-case:
领域编排层,一般对于较大且复杂的的项目,为了更好的防腐和提供通用的服务,一般会添加 case/application 层,用于对 domain 领域的逻辑进行封装组合处理
// 在 xfg-frame-case 中
@Service
public class OrderCreateCase {@Autowiredprivate UserDomainService userService;@Autowiredprivate ProductDomainService productService;@Autowiredprivate OrderDomainService orderService;@Transactional // 事务管理在这里public OrderDTO createOrder(CreateOrderRequest request) {// 1. 编排流程:校验用户User user = userService.validateUser(request.getUserId());// 2. 编排流程:锁定库存Product product = productService.lockStock(request.getProductId());// 3. 编排流程:创建订单(调用核心领域逻辑)Order order = orderService.createOrder(user, product);// 4. 返回DTOreturn convertToDTO(order);}
}