概述
学习一下DDD
什么是DDD
DDD是领域驱动设计(Domain Driven Design)的缩写,是一种软件开发方法论。它强调将业务领域划分为多个紧凑的、自包含的领域,并通过强调领域模型的重要性来建立一个通用的语言和理解。
DDD鼓励开发者们更多地关注业务本身,而不是纯粹的技术实现。在DDD中,开发者将应用程序分为多个相互依赖但相对独立的领域,并通过领域模型来描述这些领域的内部结构和行为。通过使用聚合、实体、值对象、服务等概念,开发者可以更好地表示领域的真实世界,并将其映射到软件系统中。
DDD还提供了许多有用的设计模式,例如事件溯源、领域事件、限界上下文等,以帮助开发者更好地组织代码、管理复杂性,并提高系统的可维护性和可扩展性。同时,DDD还强调团队协作和沟通的重要性,通过建立通用的语言和理解来消除偏差和误解,从而推动项目的成功。
总之,DDD是一种注重业务本质、强调领域建模、提高设计品质和代码质量的软件开发方法论,其目标是构建高质量、易维护、适应变化的软件系统。
DDD的springboot工程目录结构
在使用DDD的情况下,一个Spring Boot项目的目录结构应该如下:
├── src
│ ├── main
│ │ ├── java
│ │ │ ├── com.example.myapp
│ │ │ │ ├── config
│ │ │ │ │ └── AppConfig.java
│ │ │ │ ├── domain
│ │ │ │ │ ├── model
│ │ │ │ │ │ ├── AggregateRoot.java
│ │ │ │ │ │ ├── Entity.java
│ │ │ │ │ │ ├── ValueObject.java
│ │ │ │ │ │ └── ...
│ │ │ │ │ ├── repository
│ │ │ │ │ │ ├── UserRepository.java
│ │ │ │ │ │ └── ...
│ │ │ │ │ ├── service
│ │ │ │ │ │ ├── UserService.java
│ │ │ │ │ │ └── ...
│ │ │ │ │ └── ...
│ │ │ │ ├── infrastructure
│ │ │ │ │ ├── persistence
│ │ │ │ │ │ ├── UserRepositoryImpl.java
│ │ │ │ │ │ └── ...
│ │ │ │ │ └── messaging
│ │ │ │ │ ├── MessagePublisher.java
│ │ │ │ │ └── ...
│ │ │ │ └── MyApplication.java
│ │ │ └── ...
│ │ ├── resources
│ │ │ ├── application.yml
│ │ │ └── ...
│ │ └── ...
│ └── test
│ ├── java
│ │ └── ...
│ └── resources
│ └── ...
└── ...
在这个结构中,主要包含了以下几个部分:
config
:用于存放应用程序的配置类;domain
:用于存放领域层相关的代码,包括实体、聚合根、值对象等;infrastructure
:用于存放基础设施层相关的代码,包括数据访问、消息发送等;MyApplication
:应用程序的入口文件。
此外,在domain
和infrastructure
目录下还会有对应的单元测试和集成测试代码。
另外值得注意的是,在DDD中,我们通常会将代码组织成基于业务领域(bounded context)的模块,因此上述目录结构并不是固定的,具体的组织方式应该根据应用程序的需求进行调整。
DDD与常用的Controller、Service、Dao分层有什么不同
常用的 Controller、Service、Dao 分层是一种基于三层架构的分层模式,主要关注技术实现方面的分层,以提高代码的可读性、可维护性和可扩展性等目标。在这种分层模式下,通常会将应用程序按照控制层、服务层、数据访问层进行划分,每个层次都有特殊的职责和工作。
而 DDD 则是一种面向领域的设计方法,它强调将业务逻辑与底层技术实现分离,并通过领域模型来描述和理解业务和系统的本质。DDD 认为应用程序开发的核心是领域模型的设计和实现,因此需要将软件设计和实现过程中的精力放在对领域的深入了解上,同时尽可能地消除技术实现带来的干扰。
DDD 与常用的 Controller、Service、Dao 分层的不同之处在于 DDD 更加注重领域的建模和设计,而常用的分层则更加注重技术实现的分层。在 DDD 中,核心思想是将具体的业务逻辑和领域知识进行建模,通过分析业务场景和业务需求,抽象出领域模型和相应的业务规则。这样可以将业务逻辑嵌入到领域模型中,从而更加清晰地表达业务需求和业务流程,同时也使得系统更加容易扩展和维护。
Controller,Service,Dao分层相比于DDD有什么优缺点
常用的 Controller、Service、Dao 分层是一种基于三层架构思想的分层设计模式,具体如下:
-
Controller 层:主要负责处理用户请求,并将请求转发给 Service 层进行处理。通常包含 URL 映射、参数校验、控制事务、数据格式转换等功能。
-
Service 层:主要负责业务逻辑的处理和实现,接收来自 Controller 层的请求,并根据业务需求调用 Dao 层提供的数据访问接口进行 CRUD 操作。同时,Service 层也负责对业务逻辑进行封装,提供接口给外部系统或内部其他系统使用。
-
Dao 层:主要负责数据访问,通过与数据库连接,实现对数据的增删改查等操作,并提供与业务逻辑相对应的接口给 Service 层使用。
相比于 DDD 的目录结构,常用的 Controller、Service、Dao 分层更侧重于技术实现方面,而不关注业务场景中的领域划分和领域专家参与。其优缺点如下:
优点:
-
设计简单易懂:常用的 Controller、Service、Dao 分层虽然简单,但能够清晰地把职责分离,代码易于理解和维护。
-
灵活性强:常用的 Controller、Service、Dao 分层基于技术实现,因此在开发过程中可以灵活应对变化,快速迭代产品需求。
-
适用范围广:常用的 Controller、Service、Dao 分层可以适用于大部分业务场景,通用性较强。
缺点:
-
难以应对复杂业务场景:常用的 Controller、Service、Dao 分层没有强调业务逻辑建模,无法支撑大规模高复杂度业务的开发。
-
分层划分不够精细:常用的 Controller、Service、Dao 分层划分较为简单,无法有效地解决复杂业务场景下的问题。例如,在传统分层结构下,往往会存在因过多重复或耦合导致的代码臃肿和可维护性差等问题。
-
开发过程中需要考虑数据和业务之间的映射关系:常用的 Controller、Service、Dao 分层虽然创建了三个分层,但开发过程中仍需要考虑数据和业务之间的映射关系,增加了开发难度。
综上所述,虽然常用的 Controller、Service、Dao 分层与 DDD 的目录结构差异较大,但在不同场景下都有各自适用的情况。
具体场景对比
假设我们要开发一个电商平台,需要实现一个下单功能。我们来比较一下常用的分层与 DDD 的实现方式。
常用的分层实现
在常用的分层实现中,我们可能会按照控制层、服务层、数据访问层进行划分,大概的流程如下:
- 控制器接收到用户的请求,验证参数合法性。
- 服务层负责根据业务逻辑进行相应的处理,例如查询商品信息、检测库存情况等等。
- DAO层通过数据库操作,对库存情况进行更新。
- 服务层将订单信息写入数据库中,并返回成功状态给控制器。
- 控制器最终向用户返回成功信息或者错误信息。
这种实现方式主要关注技术实现上的分层,使用了标准的 CRUD 操作(Create、Retrieve、Update、Delete),从而提高了系统的可读性、可维护性和可扩展性。但是,这种实现方式通常比较注重系统的技术方面,而没有很好地考虑到业务逻辑和领域模型。
具体代码:
下面是一个使用常规分层实现编写的订单服务:
@RestController
@RequestMapping("/order")
public class OrderController {@Autowiredprivate ProductService productService;@Autowiredprivate OrderService orderService;@PostMapping("/add")public Result addOrder(@RequestBody OrderRequest request) {// 验证参数合法性if (request.getProductId() == null || request.getUserId() == null || request.getNum() == null) {return Result.error(ErrorCode.PARAMETER_ERROR);}// 查询商品信息Product product = productService.getProductById(request.getProductId());if (product == null) {return Result.error(ErrorCode.PRODUCT_NOT_EXIST);}// 检查库存情况if (product.getStock() < request.getNum()) {return Result.error(ErrorCode.STOCK_NOT_ENOUGH);}// 创建订单Order order = new Order();order.setUserId(request.getUserId());order.setProductId(request.getProductId());order.setNum(request.getNum());order.setTotalFee(product.getPrice().multiply(new BigDecimal(request.getNum())));// 更新库存productService.decreaseStock(request.getProductId(), request.getNum());// 写入数据库orderService.addOrder(order);return Result.success(order);}}
这个服务主要包含了控制器层、业务逻辑层和数据访问层三个部分。其中,控制器层负责接收请求并进行参数验证,业务逻辑层负责处理各种业务逻辑,数据访问层则负责与数据库打交道。
DDD 实现
在 DDD 实现中,我们可能会按照以下步骤进行:
- 分析业务场景和业务需求,根据实际情况设计相应的业务模型和领域模型。
- 领域模型包含了商品、订单、客户等相关信息,每个模型都有特定的属性和行为。
- 根据订单需求,编写相应的方法,例如创建订单、检查库存、更新订单状态等等。
- 使用业务逻辑进行订单的创建和处理,通过领域模型来描述和理解业务和系统的本质。
- 最后通过前端页面/接口等方式,完成下单操作。
在这种实现方式中,DDD 强调的是业务逻辑和领域模型的建模和设计,更加注重领域专家的意见和业务需求。因此,这种方式能够更加贴近业务需求,具有更好的可扩展性和可维护性。
总之,常用的分层实现方式注重于技术实现和体系结构的设计,DDD 则更注重于业务逻辑和领域模型的设计,让开发人员更容易理解和解决复杂业务问题。
具体代码:
@RestController
@RequestMapping("/order")
public class OrderController {@Autowiredprivate OrderApplicationService orderApplicationService;// 新增订单@PostMapping("/add")public Result addOrder(@RequestBody OrderRequest request) {OrderDto orderDto = orderApplicationService.createOrder(request);return Result.success(orderDto);}}@Service
public class OrderApplicationService {@Autowiredprivate OrderFactory orderFactory;@Autowiredprivate OrderRepository orderRepository;@Autowiredprivate ProductRepository productRepository;// 创建订单并保存订单信息public OrderDto createOrder(OrderRequest request) {// 使用工厂类创建订单对象Order order = orderFactory.createOrder(request);// 检查商品库存是否充足Product product = productRepository.findProductById(request.getProductId());if (product == null) {throw new RuntimeException("Product not exist!");}if (product.getStock() < request.getNum()) {throw new RuntimeException("Stock not enough!");}// 扣减商品库存量product.decreaseStock(request.getNum());// 保存订单信息到数据库orderRepository.saveOrder(order);// 返回订单DTOreturn order.toOrderDto();}}// 订单工厂类,用于创建订单对象
public class OrderFactory {public Order createOrder(OrderRequest request) {// 创建商品对象Product product = new Product(request.getProductId(), request.getNum());// 计算订单总价BigDecimal totalFee = product.getPrice().multiply(new BigDecimal(request.getNum()));// 创建订单对象return new Order(request.getUserId(), product, request.getNum(), totalFee);}}// 订单仓储接口,用于保存订单信息到数据库
public interface OrderRepository {void saveOrder(Order order);}// 商品仓储接口,用于查询商品信息
public interface ProductRepository {Product findProductById(Long productId);}
在这个实现中,我们把业务逻辑和领域模型的设计放到了最重要的位置。 OrderApplicationService 负责调用 OrderFactory 来创建订单对象,并对订单进行一些操作,例如检查库存和扣减库存等事项。OrderFactory 负责创建 Order 对象。而 OrderRepository 和 ProductRepository 则负责数据持久化操作。
可以看出,DDD 的实现方式更加注重领域模型和业务逻辑的设计,尽可能把业务逻辑封装到领域模型里面,提高了代码的可读性和可维护性。同时也让我们更好地理解和解决复杂业务问题。
是怎么把业务逻辑封装到领域模型里面的?
在上面的例子中,订单对象 Order
是一个领域模型。它封装了订单相关的业务逻辑,例如:
- 计算订单总价(根据商品单价和数量计算得到)。
- 支付订单(将订单状态修改为已支付)。
- 取消订单(将订单状态修改为已取消,恢复商品库存等)。
在领域驱动设计中,是将业务逻辑尽可能地放到领域模型里。这样可以让业务逻辑和数据尽量内聚,降低模块之间的耦合度,提高代码的可维护性和可扩展性。
在上面的例子中,通过调用 orderFactory.createOrder(request)
方法创建订单对象;通过调用 order.pay()
和 order.cancel()
方法完成订单的支付和取消操作。
此外,在 OrderApplicationService
服务类中还完成了扣减商品库存、保存订单信息等操作。这些业务逻辑也可以进一步封装到 Order
类的方法或其他领域模型中,以达到领域驱动设计的目标。
总结
粗略的了解一下DDD。可能之前没怎么用过,所以感觉整体还是比较抽象。