Spring Boot 实战:DDD 分层架构落地全解析

文章目录

项目架构概览

在基于 DDD 分层架构与 Spring Boot 构建的项目中,主要包含以下几个关键 module,通过这些模块实现功能的模块化与职责清晰划分。

domain(领域层)

这一层是整个项目的核心,包含领域模型、领域服务、聚合根等核心业务概念。以电商系统为例,订单(Order)聚合根及其相关领域服务(如计算订单总价、订单商品等)就存在于此模块。

application(应用层)

应用层负责协调领域层的服务来完成应用的用例。它本身不包含具体业务逻辑,而是对业务逻辑进行编排。比如,接收来自外部的订单创建请求,调用领域层的订单服务来创建订单。

infrastructure(基础设施层)

该层提供技术相关的支持,涵盖数据库访问、消息队列、文件存储等功能。在订单服务中,访问数据库来存储订单信息就属于这一层的范畴。

interface(接口层)

接口层负责与外部交互 ,包括接收 HTTP 请求(如 Controller)、消息监听 等。它将外部请求转换为应用层可处理的命令或事件。例如,接收用户下单的 HTTP 请求,并将其转换为应用层创建订单的指令。

common(公共层)

common 层在项目中**扮演着提供通用功能和工具的角色。**它包含一些可以被其他各层复用的代码,比如通用的工具类(如日期处理工具、字符串处理工具)、通用的异常处理机制、常量定义等。以电商项目为例,可能会有一个OrderMathUtils类,其中包含计算商品折扣金额的通用方法,这个方法可以在domain层的OrderDiscountService中被调用,也可能在application层的一些业务编排逻辑中使用。

父级 pom 核心标签作用

在 Maven 项目中,父级 pom 起着关键的管理作用,它负责管理整个项目的依赖、插件以及版本信息等。下面介绍几个核心标签的作用。

groupId、artifactId、version

groupId定义项目所属的组织或公司,通常采用反向域名形式,例如com.example。

artifactId是项目的唯一标识符,比如my - ddd - project。

version指定项目的版本号,如1.0.0。这三个标签共同确定了项目在 Maven 仓库中的坐标,当其他模块或项目依赖此项目时,需要使用这些坐标进行引用。

dependencyManagement

此标签用于管理项目的依赖版本,****主要用于声明依赖,它本身并不直接下载依赖。在父级 pom 中声明依赖的版本号,子 module 可以直接引用这些依赖,而无需再次指定版本。例如:

xml 复制代码
<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
      <version>2.6.3</version>
    </dependency>
  </dependencies>
</dependencyManagement>

这样在子 module 中引入spring-boot-starter-web依赖时,只需:

xml 复制代码
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>

这种方式确保了整个项目依赖版本的一致性,极大地方便了版本升级和管理工作。

modules

此标签用于声明项目包含的子 module。例如:

xml 复制代码
<modules>
  <module>domain</module>
  <module>application</module>
  <module>infrastructure</module>
  <module>interface</module>
  <module>common</module>
</modules>

通过这种声明,Maven 能够统一管理子 module 的构建、测试、部署等操作。当在父级 pom 执行mvn install时,Maven 会按照modules中声明的顺序依次构建每个子 module。

properties

properties标签用于定义项目中的一些通用属性,方便在整个项目中复用。例如:

xml 复制代码
<properties>
    <java.version>11</java.version>
    <spring.boot.version>2.6.3</spring.boot.version>
</properties>

在定义了这些属性后,在依赖或者插件中就可以通过${属性名}的方式引用。如:

xml 复制代码
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
  <version>${spring.boot.version}</version>
</dependency>

这样做的好处是,如果需要修改 Spring Boot 的版本,只需要在标签中修改spring.boot.version的值,而不需要在每个依赖中逐个修改版本号,提高了项目配置的可维护性。

父级 pom 与子 module 的关系

父级 pom 就像项目的管理者,子 module 是具体的执行者。父级 pom 管理着子 module 的依赖、插件等公共配置,子 module 继承父级 pom 的配置,并可根据自身需求进行覆盖或扩展。

继承关系

子 module 通过标签继承父级 pom 的配置。例如,domain模块的 pom 文件示例如下:

xml 复制代码
<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema - instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven - 4.0.0.xsd">
  <parent>
    <groupId>com.example</groupId>
    <artifactId>my-ddd-project</artifactId>
    <version>1.0.0</version>
  </parent>
  <modelVersion>4.0.0</modelVersion>
  <artifactId>domain</artifactId>
</project>

通过这种继承,domain模块继承了父级 pom 的依赖管理、插件管理等配置,减少了重复配置,提升了项目的一致性和可维护性。其他模块如application、infrastructure、interface、common的 pom 文件也类似,只是标签的值分别为各自模块的名称。

依赖传递

父级 pom 中声明的依赖会传递到子 module 中。若父级 pom 中声明了spring-boot-starter-web依赖,子 module 无需再次声明就可使用相关的类和功能。不过,子 module 也能根据自身需要添加额外的依赖,或者排除父级 pom 中传递过来的不需要的依赖。例如,interface模块可能需要添加spring-fox依赖来生成 API 文档,其 pom 文件中可以这样添加:

xml 复制代码
<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema - instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven - 4.0.0.xsd">
  <parent>
    <groupId>com.example</groupId>
    <artifactId>my-ddd-project</artifactId>
    <version>1.0.0</version>
  </parent>
  <modelVersion>4.0.0</modelVersion>
  <artifactId>interface</artifactId>
  <dependencies>
    <dependency>
      <groupId>io.springfox</groupId>
      <artifactId>springfox - swagger2</artifactId>
      <version>2.9.2</version>
    </dependency>
    <dependency>
      <groupId>io.springfox</groupId>
      <artifactId>springfox - swagger - ui</artifactId>
      <version>2.9.2</version>
    </dependency>
  </dependencies>
</project>

构建关系

当在父级 pom 执行构建命令(如mvn install)时,Maven 会依据标签中声明的顺序依次构建每个子 module。这确保了整个项目按照正确的顺序进行构建,例如先构建domain模块,因为其他模块可能依赖domain模块中的领域模型和服务。

父级 pom 与子 module 的关系

父级 pom 在整个项目中扮演着管理者的角色,它掌控着子 module 的依赖、插件等公共配置,子 module 则继承父级 pom 的配置,并可依据自身业务需求进行个性化调整。各子模块在dependency部分引入其他模块的情况,深刻反映了模块间的协作关系。

domain 模块

domain模块是项目的业务核心,承载着领域模型、领域服务和聚合根等关键元素。在其 pom 文件中,配置如下:

xml 复制代码
<dependencies>
  <dependency>
    <groupId>com.example</groupId>
    <artifactId>common</artifactId>
  </dependency>
</dependencies>

这里引入com.example组织下的my-ddd-project-common模块,借助其中的通用工具类、常量等,像前文提及的Util类,可用于计算折扣金额,为领域模型的构建和业务规则的执行提供便利。

application 模块

application模块主要负责协调业务用例,将外部请求转化为领域层可处理的操作。其 pom 文件为:

xml 复制代码
<dependencies>
  <dependency>
    <groupId>com.example</groupId>
    <artifactId>domain</artifactId>
    <version>1.0.0</version>
  </dependency>
  <dependency>
    <groupId>com.example</groupId>
    <artifactId>infrastructure</artifactId>
    <version>1.0.0</version>
  </dependency>
  <dependency>
    <groupId>com.example</groupId>
    <artifactId>common</artifactId>
    <version>1.0.0</version>
  </dependency>
</dependencies>

由于业务协调的需要,它依赖domain模块,调用其中的领域服务,如OrderApplicationService调用OrderDiscountService来计算订单折扣。同时依赖infrastructure模块,通过OrderRepository接口与数据库交互,实现订单数据的持久化。引入common模块,利用其通用功能辅助业务编排逻辑,例如使用通用的日期处理工具类来处理订单时间相关业务。

infrastructure 模块

infrastructure模块为整个项目提供技术支撑,涵盖数据库访问、消息队列等功能。其 pom 文件如下:

xml 复制代码
<dependencies>
  <dependency>
    <groupId>com.example</groupId>
    <artifactId>domain</artifactId>
    <version>1.0.0</version>
  </dependency>
</dependencies>

这里是否引入要看团队规范,都可以。以实际项目中为例OrderRepository为例,具体将OrderRepository放在哪个模块,需要根据项目的规模、复杂性、团队的技术偏好以及对架构设计原则的理解和权衡来决定。有些项目可能会采用更严格的分层架构,将OrderRepository及其实现放在infrastructure模块,以实现更彻底的分离;而在一些强调领域模型核心地位和遵循 DDD 原则的项目中,则可能会将OrderRepository定义在domain模块,以更好地体现领域驱动的设计思想。

interface 模块

interface模块负责与外部交互,接收 HTTP 请求、消息监听等。其 pom 文件配置如下:

xml 复制代码
<dependency>
  <groupId>com.example</groupId>
  <artifactId>application</artifactId>
  <version>1.0.0</version>
</dependency>

为实现与外部的交互,它依赖application模块,调用应用层服务,如OrderController调用OrderApplicationService来处理用户下单请求。common模块不用引入,application模块里已经有了,使用其通用的异常处理机制,当外部请求出现异常时,能够进行统一、规范的处理,提升系统的稳定性和用户体验。

common 模块

common模块主要提供通用功能,供其他模块复用。其 pom 文件为:

xml 复制代码
<dependencies>
  <!-- 可能引入一些通用的第三方库,如常用的日志库等 -->
  <dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
  </dependency>
</dependencies>

该模块通常不依赖其他模块的业务逻辑,而是依赖一些通用的第三方库,如引入slf4j-api用于日志记录。通过这种方式,为其他各模块提供统一的日志记录功能,在项目的调试与运维过程中,便于集中管理和分析日志信息,提高项目的可维护性。

电商中订单服务的 DDD 实战案例

领域层(domain)

聚合根与实体

订单(Order)作为一个聚合根,包含多个订单行(OrderItem)实体。订单聚合根维护着订单的整体状态和业务规则,如订单的创建、取消、支付等操作。订单行实体则表示订单中的具体商品信息,包括商品名称、数量、价格等。相关代码如下:

领域服务

在订单领域中,有些复杂的业务逻辑无法简单地归属到某个实体中,这时就需要领域服务。例如,计算订单的折扣金额可能涉及多种折扣规则(如满减、会员折扣等),可以将此逻辑封装在一个OrderDiscountService领域服务中。代码实现如下:

java 复制代码
@Service
public class OrderDiscountService {
    public BigDecimal calculateDiscount(Order order) {
        // 复杂的折扣计算逻辑
        BigDecimal totalPrice = order.calculateTotalPrice();
        if (order.getCustomerId()!= null) {
            // 根据会员等级计算折扣
        }
        // 其他折扣规则
        return discountAmount;
    }
}

应用层(application)

应用层负责处理用户的用例。以创建订单的用例为例,可以在OrderApplicationService中实现。它接收来自接口层的创建订单请求,调用领域层的服务来创建订单。代码如下:

java 复制代码
@Service
public class OrderApplicationService {
    private final OrderRepository orderRepository;
    private final OrderDiscountService orderDiscountService;
    public OrderApplicationService(OrderRepository orderRepository,
                                   OrderDiscountService orderDiscountService) {
        this.orderRepository = orderRepository;
        this.orderDiscountService = orderDiscountService;
    }
    
    public Long createOrder(CreateOrderCommand command) {
        List<OrderItem> orderItems = command.getOrderItems();
        Order order = new Order();
        order.createOrder(command.getCustomerId(), orderLines);
        BigDecimal discount = orderDiscountService.calculateDiscount(order);
        order.applyDiscount(discount);
        orderRepository.save(order);
        return order.getId();
    }
}

基础设施层(infrastructure)

数据持久化

在基础设施层实现订单数据的持久化。

java 复制代码
@Servcie
public interface OrderRepository{}

消息队列(可选)

如果订单服务需要与其他系统进行异步通信,例如在订单创建成功后发送消息通知库存系统更新库存,可以使用消息队列。以 Kafka 为例,配置 Kafka 生产者和消费者:

java 复制代码
@Configuration
public class KafkaConfig {
    @Bean
    public ProducerFactory<String, String> producerFactory() {
        Map<String, Object> configProps = new HashMap<>();
        configProps.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
        configProps.put(ConsumerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
        configProps.put(ConsumerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
        return new DefaultKafkaProducerFactory<>(configProps);
    }
    @Bean
    public KafkaTemplate<String, String> kafkaTemplate() {
        return new KafkaTemplate<>(producerFactory());
    }
}

接口层(interface)

接口层使用 Spring MVC 接收 HTTP 请求。创建OrderController来处理与订单相关的请求,如创建订单。代码如下:

java 复制代码
@RestController
@RequestMapping("/orders")
public class OrderController {
     private final OrderApplicationService orderApplicationService;
     public OrderController(OrderApplicationService orderApplicationService) {
     this.orderApplicationService = orderApplicationService;
     }
    
     @PostMapping
     public ResponseEntity<Long> createOrder(@RequestBody CreateOrderCommand command) {
         Long orderId = orderApplicationService.createOrder(command);
         return ResponseEntity.ok(orderId);
     }
}

在实际应用中,接口层除了简单地调用应用层服务,还需处理请求参数的校验、响应数据的格式转换以及异常处理等工作。例如,对CreateOrderCommand中的参数进行合法性校验,确保订单创建请求包含必要且正确的信息,像客户 ID 不能为 null,订单行列表不能为空等。如果校验失败,应返回合适的 HTTP 状态码及错误信息,以提升系统的健壮性和用户体验。

在响应数据方面,可能需要将领域模型中的数据结构转换为适合前端展示的格式。比如,订单状态在领域模型中可能是一个枚举类型,但在前端展示时,可能需要转换为更友好的字符串描述。此外,接口层还需考虑安全性问题,如对敏感信息进行脱敏处理,防止数据泄露。

相关推荐
smileNicky6 小时前
SpringBoot系列之从繁琐配置到一键启动之旅
java·spring boot·后端
柏油9 小时前
Spring @TransactionalEventListener 解读
spring boot·后端·spring
小小工匠10 小时前
Maven - Spring Boot 项目打包本地 jar 的 3 种方法
spring boot·maven·jar·system scope
板板正12 小时前
Spring Boot 整合MongoDB
spring boot·后端·mongodb
泉城老铁13 小时前
在高并发场景下,如何优化线程池参数配置
spring boot·后端·架构
泉城老铁13 小时前
Spring Boot中实现多线程6种方式,提高架构性能
spring boot·后端·spring cloud
hrrrrb14 小时前
【Java Web 快速入门】九、事务管理
java·spring boot·后端
布朗克16815 小时前
Spring Boot项目通过RestTemplate调用三方接口详细教程
java·spring boot·后端·resttemplate
IT毕设实战小研16 小时前
基于Spring Boot校园二手交易平台系统设计与实现 二手交易系统 交易平台小程序
java·数据库·vue.js·spring boot·后端·小程序·课程设计
孤狼程序员17 小时前
【Spring Cloud 微服务】1.Hystrix断路器
java·spring boot·spring·微服务