一款开箱即用的Spring Boot 4 DDD工程脚手架

Springboot4DDD 脚手架使用指南

一个开箱即用的 DDD(领域驱动设计)工程脚手架,基于 Spring Boot 4.0.1 和 Java 21

🎯 这是什么?

Springboot4DDD 是一个生产就绪的 DDD 工程脚手架,帮助开发者快速搭建符合领域驱动设计原则的企业级应用。 源码地址:github.com/microwind/d...

核心特点

严格的 DDD 四层架构 - 领域层、应用层、基础设施层、接口层分离清晰

事件驱动架构 - 集成 RocketMQ,支持领域事件发布和消费

多数据源支持 - 开箱支持 MySQL + PostgreSQL 双数据源

双持久化方案 - JdbcTemplate 和 Spring Data JDBC 两种方式可选

API 签名验证 - 内置完整的接口安全认证机制

统一响应格式 - 标准化的 API 响应结构

全局异常处理 - 优雅的错误捕获和响应

参数校验 - 基于 Jakarta Validation 的数据验证

生产就绪 - 完整的日志、配置、事务管理

技术栈

技术 版本 说明
Spring Boot 4.0.1 最新稳定版
Java 21 LTS 版本
MySQL 8.0+ 用户数据存储
PostgreSQL 14+ 订单数据存储
Redis 6.0+ 缓存(可选)
RocketMQ 5.3+ 消息队列(事件驱动)
Lombok - 简化代码
Maven 3.8+ 构建工具

💡 为什么选择这个脚手架?

1. 节省时间

无需从零搭建项目架构,克隆即用,专注业务开发。

对比

  • ❌ 传统方式:1-2周搭建基础架构
  • ✅ 使用脚手架:10分钟完成初始化

2. 架构规范

严格遵循 DDD 分层原则,避免代码混乱。

收益

  • 业务逻辑内聚在领域对象中
  • 各层职责清晰,易于维护
  • 支持单元测试和集成测试

3. 最佳实践

集成了企业级开发的最佳实践。

包含

  • 统一响应格式
  • 全局异常处理
  • API 签名验证
  • 多数据源管理
  • 跨数据库查询

4. 易于扩展

模块化设计,轻松添加新功能。

示例

  • 5分钟添加新的业务实体
  • 10分钟集成 Redis 缓存
  • 15分钟接入 RocketMQ 消息队列

📁 工程结构详解

bash 复制代码
springboot4ddd/
├── src/main/java/com/github/microwind/springboot4ddd/
│   │
│   ├── Application.java                              # Spring Boot 启动类
│   │
│   ├── domain/                                       # 【领域层】核心业务逻辑
│   │   ├── model/                                    # 领域模型
│   │   │   ├── order/
│   │   │   │   └── Order.java                        # 订单聚合根(包含业务方法)
│   │   │   └── user/
│   │   │       └── User.java                         # 用户实体
│   │   │
│   │   ├── repository/                               # 仓储接口(由基础设施层实现)
│   │   │   ├── order/
│   │   │   │   └── OrderRepository.java              # 订单仓储接口
│   │   │   └── user/
│   │   │       └── UserRepository.java               # 用户仓储接口
│   │   │
│   │   ├── event/                                    # 🆕 领域事件定义
│   │   │   ├── DomainEvent.java                      # 领域事件接口
│   │   │   ├── OrderEvent.java                       # 订单事件
│   │   │   ├── UserEvent.java                        # 用户事件
│   │   │   └── EventPublisher.java                   # 事件发布器接口
│   │   │
│   │   └── service/                                  # 领域服务(跨聚合的业务逻辑)
│   │       └── [领域服务类]
│   │
│   ├── application/                                  # 【应用层】业务编排
│   │   ├── dto/                                      # 数据传输对象
│   │   │   └── order/
│   │   │       ├── OrderDTO.java                     # 订单 DTO
│   │   │       └── OrderMapper.java                  # 实体与DTO转换
│   │   │
│   │   └── service/                                  # 应用服务(编排领域对象)
│   │       ├── order/
│   │       │   └── OrderService.java                 # 订单应用服务
│   │       └── user/
│   │           └── UserService.java                  # 用户应用服务
│   │
│   ├── infrastructure/                               # 【基础设施层】技术支撑
│   │   ├── common/                                   # 通用组件
│   │   │   ├── ApiResponse.java                      # 统一 API 响应
│   │   │   └── GlobalExceptionHandler.java           # 全局异常处理
│   │   │
│   │   ├── config/                                   # 配置类
│   │   │   ├── DataSourceConfig.java                 # 多数据源配置
│   │   │   ├── OrderJdbcConfig.java                  # Spring Data JDBC 配置
│   │   │   ├── SignConfig.java                       # 签名配置
│   │   │   ├── WebConfig.java                        # Web 配置
│   │   │   ├── JacksonConfig.java                    # JSON 序列化配置
│   │   │   └── ApiAuthConfig.java                    # API 认证配置
│   │   │
│   │   ├── constants/                                # 常量定义
│   │   │   └── ErrorCode.java                        # 错误码定义
│   │   │
│   │   ├── exception/                                # 自定义异常
│   │   │   ├── BusinessException.java                # 业务异常
│   │   │   └── ResourceNotFoundException.java        # 资源不存在异常
│   │   │
│   │   ├── middleware/                               # 中间件
│   │   │   ├── SignatureInterceptor.java             # 签名拦截器
│   │   │   ├── CachedBodyFilter.java                 # 请求体缓存过滤器
│   │   │   └── CachedBodyHttpServletRequest.java     # 请求体缓存包装类
│   │   │
│   │   ├── repository/                               # 仓储实现
│   │   │   ├── order/
│   │   │   │   └── OrderRepositoryImpl.java          # 订单仓储实现
│   │   │   ├── user/
│   │   │   │   └── UserRepositoryImpl.java           # 用户仓储实现
│   │   │   └── jdbc/
│   │   │       └── OrderJdbcRepository.java          # Spring Data JDBC 接口
│   │   │
│   │   ├── messaging/                              # 🆕 RocketMQ 消息处理
│   │   │   ├── config/
│   │   │   │   └── RocketMQConfig.java             # RocketMQ 配置
│   │   │   ├── producer/
│   │   │   │   └── OrderEventProducer.java         # 订单事件生产者
│   │   │   ├── consumer/
│   │   │   │   └── OrderEventConsumer.java         # 订单事件消费者
│   │   │   ├── message/
│   │   │   │   ├── OrderCreatedMessage.java        # 订单创建消息
│   │   │   │   ├── OrderPaidMessage.java           # 订单支付消息
│   │   │   │   ├── OrderCancelledMessage.java      # 订单取消消息
│   │   │   │   └── OrderCompletedMessage.java      # 订单完成消息
│   │   │   └── converter/
│   │   │       └── OrderEventMessageMapper.java    # 事件到消息转换器
│   │   │
│   │   └── util/                                     # 工具类
│   │       └── SignatureUtil.java                    # 签名工具
│   │
│   └── interfaces/                                   # 【接口层】对外暴露
│       ├── annotation/                               # 自定义注解
│       │   ├── RequireSign.java                      # 需要签名验证注解
│       │   ├── IgnoreSignHeader.java                 # 忽略签名验证注解
│       │   └── WithParams.java                       # 签名参数选项
│       │
│       ├── controller/                               # REST 控制器
│       │   ├── HealthController.java                 # 健康检查
│       │   ├── order/
│       │   │   └── OrderController.java              # 订单接口
│       │   └── user/
│       │       └── UserController.java               # 用户接口
│       │
│       └── vo/                                       # 视图对象(请求/响应)
│           ├── order/
│           │   ├── CreateOrderRequest.java           # 创建订单请求
│           │   ├── UpdateOrderRequest.java           # 更新订单请求
│           │   ├── OrderResponse.java                # 订单响应
│           │   └── OrderListResponse.java            # 订单列表响应
│           └── user/
│               ├── CreateUserRequest.java            # 创建用户请求
│               ├── UpdateUserRequest.java            # 更新用户请求
│               └── UserResponse.java                 # 用户响应
│
├── src/main/resources/
│   ├── application.yml                               # 主配置文件
│   ├── application-dev.yml                           # 开发环境配置
│   ├── application-prod.yml                          # 生产环境配置
│   ├── apiauth-config.yaml                           # API 认证配置
│   │
│   └── db/                                           # 数据库脚本
│       ├── mysql/
│       │   └── init_users.sql                        # MySQL 初始化脚本
│       └── postgresql/
│           └── init_orders.sql                       # PostgreSQL 初始化脚本
│
└── src/test/java/                                    # 测试代码
    └── com/github/microwind/springboot4ddd/
        └── ApplicationTests.java                     # 应用测试

各层职责说明

层级 位置 职责 关键原则
领域层 domain/ 核心业务逻辑、领域模型 独立于框架,业务规则内聚
应用层 application/ 编排领域对象,管理事务 薄薄一层,不包含业务逻辑
基础设施层 infrastructure/ 技术实现、持久化、配置 实现技术细节,对上层透明
接口层 interfaces/ REST API、请求响应处理 处理外部交互,不含业务逻辑

🚀 快速开始

1. 环境准备

确保已安装:

  • JDK 21
  • Maven 3.8+
  • MySQL 8.0+
  • PostgreSQL 14+
  • RocketMQ 5.3+ (用于事件驱动)
  • Git

2. 克隆项目

bash 复制代码
git clone https://github.com/microwind/design-patterns.git
cd design-patterns/practice-projects/springboot4ddd

3. 启动 RocketMQ(可选,用于事件驱动)

bash 复制代码
# 启动 NameServer
cd rocketmq-all-5.3.2-bin-release
sh bin/mqnamesrv

# 启动 Broker
sh bin/mqbroker -n localhost:9876

# 检查集群状态
sh bin/mqadmin clusterList -n localhost:9876

4. 初始化数据库

MySQL(用户数据):

bash 复制代码
mysql -u root -p
CREATE DATABASE frog CHARACTER SET utf8mb4;
CREATE USER 'frog_admin'@'localhost' IDENTIFIED BY 'frog_password';
GRANT ALL PRIVILEGES ON frog.* TO 'frog_admin'@'localhost';
USE frog;
source src/main/resources/db/mysql/init_users.sql;

PostgreSQL(订单数据):

bash 复制代码
psql -U postgres
CREATE DATABASE seed ENCODING 'UTF8';
\c seed
\i src/main/resources/db/postgresql/init_orders.sql

5. 配置应用

编辑 src/main/resources/application-dev.yml

yaml 复制代码
spring:
  datasource:
    user:
      jdbc-url: jdbc:mysql://localhost:3306/frog
      username: frog_admin
      password: frog_password  # 修改为你的密码

    order:
      jdbc-url: jdbc:postgresql://localhost:5432/seed
      username: postgres
      password: postgres_password  # 修改为你的密码

# RocketMQ 配置(可选)
rocketmq:
  name-server: localhost:9876
  producer:
    group: springboot4ddd-producer
    send-message-timeout: 3000
    retry-times-when-send-failed: 2

6. 启动应用

bash 复制代码
# 编译
./mvnw clean compile

# 运行
./mvnw spring-boot:run

7. 验证安装

bash 复制代码
# 健康检查
curl http://localhost:8080/api/health

# 查询用户列表
curl http://localhost:8080/api/users

# 查询订单列表
curl http://localhost:8080/api/orders/list

📝 如何基于脚手架开发新功能

场景:添加一个"商品管理"功能

步骤 1:创建领域模型

domain/model/product/ 下创建 Product.java

java 复制代码
package com.github.microwind.springboot4ddd.domain.model.product;

import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.relational.core.mapping.Column;
import org.springframework.data.relational.core.mapping.Table;
import java.math.BigDecimal;
import java.time.LocalDateTime;

@Data
@Table("products")
public class Product {

    @Id
    private Long id;

    @Column("product_name")
    private String productName;

    private BigDecimal price;

    private Integer stock;

    @Column("created_at")
    private LocalDateTime createdAt;

    // 领域行为:减库存
    public void decreaseStock(Integer quantity) {
        if (this.stock < quantity) {
            throw new BusinessException("库存不足");
        }
        this.stock -= quantity;
    }

    // 领域行为:增加库存
    public void increaseStock(Integer quantity) {
        this.stock += quantity;
    }
}
步骤 2:定义仓储接口

domain/repository/product/ 下创建 ProductRepository.java

java 复制代码
package com.github.microwind.springboot4ddd.domain.repository.product;

import com.github.microwind.springboot4ddd.domain.model.product.Product;
import java.util.List;
import java.util.Optional;

public interface ProductRepository {
    Product save(Product product);
    Optional<Product> findById(Long id);
    List<Product> findAll();
    void deleteById(Long id);
}
步骤 3:实现仓储

infrastructure/repository/product/ 下创建 ProductRepositoryImpl.java

java 复制代码
package com.github.microwind.springboot4ddd.infrastructure.repository.product;

import com.github.microwind.springboot4ddd.domain.model.product.Product;
import com.github.microwind.springboot4ddd.domain.repository.product.ProductRepository;
import com.github.microwind.springboot4ddd.infrastructure.repository.jdbc.ProductJdbcRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;

@Repository
@RequiredArgsConstructor
public class ProductRepositoryImpl implements ProductRepository {

    private final ProductJdbcRepository jdbcRepository;

    @Override
    public Product save(Product product) {
        return jdbcRepository.save(product);
    }

    @Override
    public Optional<Product> findById(Long id) {
        return jdbcRepository.findById(id);
    }

    @Override
    public List<Product> findAll() {
        return (List<Product>) jdbcRepository.findAll();
    }

    @Override
    public void deleteById(Long id) {
        jdbcRepository.deleteById(id);
    }
}

infrastructure/repository/jdbc/ 下创建 ProductJdbcRepository.java

java 复制代码
package com.github.microwind.springboot4ddd.infrastructure.repository.jdbc;

import com.github.microwind.springboot4ddd.domain.model.product.Product;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface ProductJdbcRepository extends CrudRepository<Product, Long> {
}
步骤 4:创建应用服务

application/service/product/ 下创建 ProductService.java

java 复制代码
package com.github.microwind.springboot4ddd.application.service.product;

import com.github.microwind.springboot4ddd.domain.model.product.Product;
import com.github.microwind.springboot4ddd.domain.repository.product.ProductRepository;
import com.github.microwind.springboot4ddd.infrastructure.exception.ResourceNotFoundException;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;

@Service
@RequiredArgsConstructor
@Transactional
public class ProductService {

    private final ProductRepository productRepository;

    public Product createProduct(String name, BigDecimal price, Integer stock) {
        Product product = new Product();
        product.setProductName(name);
        product.setPrice(price);
        product.setStock(stock);
        product.setCreatedAt(LocalDateTime.now());

        return productRepository.save(product);
    }

    @Transactional(readOnly = true)
    public Product getProductById(Long id) {
        return productRepository.findById(id)
            .orElseThrow(() -> new ResourceNotFoundException("商品不存在"));
    }

    @Transactional(readOnly = true)
    public List<Product> getAllProducts() {
        return productRepository.findAll();
    }

    public void decreaseStock(Long productId, Integer quantity) {
        Product product = getProductById(productId);
        product.decreaseStock(quantity);  // 调用领域行为
        productRepository.save(product);
    }
}
步骤 5:创建 REST 接口

interfaces/controller/product/ 下创建 ProductController.java

java 复制代码
package com.github.microwind.springboot4ddd.interfaces.controller.product;

import com.github.microwind.springboot4ddd.application.service.product.ProductService;
import com.github.microwind.springboot4ddd.domain.model.product.Product;
import com.github.microwind.springboot4ddd.infrastructure.common.ApiResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;

@RestController
@RequestMapping("/api/products")
@RequiredArgsConstructor
public class ProductController {

    private final ProductService productService;

    @PostMapping
    public ApiResponse<Product> createProduct(@RequestBody CreateProductRequest request) {
        Product product = productService.createProduct(
            request.getName(),
            request.getPrice(),
            request.getStock()
        );
        return ApiResponse.success(product);
    }

    @GetMapping("/{id}")
    public ApiResponse<Product> getProduct(@PathVariable Long id) {
        Product product = productService.getProductById(id);
        return ApiResponse.success(product);
    }

    @GetMapping
    public ApiResponse<List<Product>> getAllProducts() {
        List<Product> products = productService.getAllProducts();
        return ApiResponse.success(products);
    }
}
步骤 6:创建数据库表

src/main/resources/db/postgresql/ 下添加:

sql 复制代码
CREATE TABLE IF NOT EXISTS products (
    id BIGSERIAL PRIMARY KEY,
    product_name VARCHAR(100) NOT NULL,
    price DECIMAL(10, 2) NOT NULL,
    stock INTEGER NOT NULL DEFAULT 0,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 插入测试数据
INSERT INTO products (product_name, price, stock) VALUES
    ('iPhone 15 Pro', 7999.00, 100),
    ('MacBook Pro', 15999.00, 50),
    ('AirPods Pro', 1999.00, 200);
步骤 7:测试接口
bash 复制代码
# 查询所有商品
curl http://localhost:8080/api/products

# 查询单个商品
curl http://localhost:8080/api/products/1

# 创建商品
curl -X POST http://localhost:8080/api/products \
  -H "Content-Type: application/json" \
  -d '{"name":"iPad Pro","price":6999.00,"stock":80}'

🎨 开发规范

命名规范

java 复制代码
// 领域对象:名词,使用业务语言
public class Product { }

// 应用服务:XxxService
public class ProductService { }

// 仓储接口:XxxRepository
public interface ProductRepository { }

// 仓储实现:XxxRepositoryImpl
public class ProductRepositoryImpl implements ProductRepository { }

// DTO:XxxDTO
public class ProductDTO { }

// 请求对象:XxxRequest
public class CreateProductRequest { }

// 响应对象:XxxResponse
public class ProductResponse { }

// 控制器:XxxController
public class ProductController { }

分层原则

原则 说明
领域层纯净 不依赖 Spring 注解(除持久化映射)
业务逻辑内聚 业务规则在领域对象中实现
依赖方向正确 接口层 → 应用层 → 领域层 ← 基础设施层
单一职责 每个类只负责一件事
接口隔离 领域层定义接口,基础设施层实现

代码风格

java 复制代码
// ✅ 推荐:业务逻辑在领域对象中
@Data
public class Order {
    public void cancel() {
        if (this.status != OrderStatus.PENDING) {
            throw new BusinessException("只有待支付订单可以取消");
        }
        this.status = OrderStatus.CANCELLED;
    }
}

// ✅ 推荐:应用服务只负责编排
@Service
public class OrderService {
    public void cancelOrder(Long orderId) {
        Order order = orderRepository.findById(orderId).orElseThrow();
        order.cancel();  // 调用领域行为
        orderRepository.save(order);
    }
}

// ❌ 不推荐:业务逻辑在应用服务中
@Service
public class OrderService {
    public void cancelOrder(Long orderId) {
        Order order = orderRepository.findById(orderId).orElseThrow();
        // 业务逻辑不应该在这里
        if (order.getStatus() != OrderStatus.PENDING) {
            throw new BusinessException("只有待支付订单可以取消");
        }
        order.setStatus(OrderStatus.CANCELLED);
        orderRepository.save(order);
    }
}

🔧 常用配置

多数据源配置

如需添加第三个数据源:

java 复制代码
@Configuration
public class DataSourceConfig {

    @Bean(name = "thirdDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.third")
    public DataSource thirdDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "thirdJdbcTemplate")
    public JdbcTemplate thirdJdbcTemplate(
            @Qualifier("thirdDataSource") DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }
}

API 签名配置

编辑 apiauth-config.yaml

yaml 复制代码
api-auth:
  apps:
    - app-code: my-app
      secret-key: my_secret_key_123
      permissions:
        - /api/products/**
        - /api/orders/**

在控制器方法上添加注解:

java 复制代码
@PostMapping
@RequireSign(withParams = WithParams.TRUE)  // 需要签名验证
public ApiResponse<?> createProduct(@RequestBody CreateProductRequest request) {
    // ...
}

Redis 缓存配置

yaml 复制代码
spring:
  data:
    redis:
      host: localhost
      port: 6379

使用缓存:

java 复制代码
@Service
public class ProductService {

    @Cacheable(value = "products", key = "#id")
    public Product getProductById(Long id) {
        return productRepository.findById(id).orElseThrow();
    }

    @CacheEvict(value = "products", key = "#id")
    public void deleteProduct(Long id) {
        productRepository.deleteById(id);
    }
}

🚀 RocketMQ 事件驱动架构

为什么需要事件驱动?

在传统的同步调用模式下,订单创建、支付等操作需要同步调用多个服务(库存、通知、积分等),导致:

  • 耦合度高 - 订单服务依赖其他所有服务
  • 性能差 - 同步等待多个服务响应
  • 可用性低 - 任何一个服务故障都会影响主流程

事件驱动架构通过异步消息解耦,实现

  • ✅ 服务解耦 - 订单服务只需发布事件,不关心谁消费
  • ✅ 高性能 - 异步处理,不阻塞主流程
  • ✅ 高可用 - 消费者故障不影响生产者

消息处理架构

本脚手架采用清晰的职责分离设计:

bash 复制代码
infrastructure/messaging/
├── config/                    # 配置管理
│   └── RocketMQConfig.java   # Topic、Tag、组名等配置
├── producer/                  # 生产者(发送消息)
│   └── OrderEventProducer.java
├── consumer/                  # 消费者(接收消息)
│   └── OrderEventConsumer.java
├── message/                   # 消息对象(传输载体)
│   ├── OrderCreatedMessage.java
│   ├── OrderPaidMessage.java
│   ├── OrderCancelledMessage.java
│   └── OrderCompletedMessage.java
└── converter/                 # 转换器(事件→消息)
    └── OrderEventMessageMapper.java

设计原则

  • 领域事件 (DomainEvent) 在领域层定义
  • 消息对象 (Message) 在基础设施层定义
  • 通过 Converter 解耦领域层和基础设施层

消息流转流程

sql 复制代码
┌─────────────┐      ┌──────────────┐      ┌─────────────┐
│  HTTP 请求   │ ───> │  OrderService │ ───> │  Order 模型  │
└─────────────┘      └──────────────┘      └─────────────┘
                            │                      │
                            │ 1.持久化            │ 2.记录事件
                            ▼                      ▼
                     ┌──────────────┐      ┌─────────────────┐
                     │   数据库      │      │ DomainEvent List │
                     └──────────────┘      └─────────────────┘
                                                   │
                                     3.获取事件并发布
                                                   ▼
                                          ┌───────────────────┐
                                          │ OrderEventProducer │
                                          └───────────────────┘
                                                   │
                                     4.转换为 Message 对象
                                                   ▼
                                          ┌───────────────────┐
                                          │    RocketMQ       │
                                          │ Topic: order-events│
                                          │ Tag: OrderCreated  │
                                          └───────────────────┘
                                                   │
                                     5.消费消息
                                                   ▼
                                          ┌───────────────────┐
                                          │ OrderEventConsumer│
                                          └───────────────────┘
                                                   │
                                     6.业务处理
                                                   ▼
                                     ┌───────────────────────────┐
                                     │ • 扣减库存                │
                                     │ • 发送通知                │
                                     │ • 更新积分                │
                                     │ • 生成报表                │
                                     └───────────────────────────┘

完整实现示例

步骤 1:定义领域事件(Domain 层)

domain/event/ 创建基类和订单事件:

java 复制代码
// DomainEvent.java - 领域事件基类
package com.github.microwind.springboot4ddd.domain.event;

import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.UUID;

@Data
public abstract class DomainEvent implements Serializable {
    private String eventId;
    private Long aggregateId;
    private String aggregateType;
    private LocalDateTime occurredAt;

    protected DomainEvent(Long aggregateId, String aggregateType) {
        this.eventId = UUID.randomUUID().toString();
        this.aggregateId = aggregateId;
        this.aggregateType = aggregateType;
        this.occurredAt = LocalDateTime.now();
    }

    public String getEventType() {
        return this.getClass().getSimpleName();
    }
}

// OrderCreatedEvent.java - 订单创建事件
package com.github.microwind.springboot4ddd.domain.event.order;

import com.github.microwind.springboot4ddd.domain.event.DomainEvent;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.math.BigDecimal;

@Data
@EqualsAndHashCode(callSuper = true)
public class OrderCreatedEvent extends DomainEvent {
    private String orderNo;
    private Long userId;
    private BigDecimal totalAmount;
    private String status;

    public OrderCreatedEvent(Long orderId, String orderNo, Long userId,
                           BigDecimal totalAmount, String status) {
        super(orderId, "Order");
        this.orderNo = orderNo;
        this.userId = userId;
        this.totalAmount = totalAmount;
        this.status = status;
    }
}

步骤 2:在 Order 聚合根中记录事件

java 复制代码
// Order.java
@Data
@Table("orders")
public class Order {
    @Id
    private Long id;
    private String orderNo;
    private Long userId;
    private BigDecimal totalAmount;
    private String status;

    // 领域事件列表(不持久化)
    @Transient
    private List<DomainEvent> domainEvents = new ArrayList<>();

    // 创建订单
    public static Order create(Long userId, BigDecimal totalAmount) {
        Order order = new Order();
        order.setOrderNo("ORD" + System.currentTimeMillis());
        order.setUserId(userId);
        order.setTotalAmount(totalAmount);
        order.setStatus("PENDING");
        order.setCreatedAt(LocalDateTime.now());
        return order;
    }

    // 记录订单创建事件(在保存后调用)
    public void recordCreatedEvent() {
        this.domainEvents.add(new OrderCreatedEvent(
            this.id, this.orderNo, this.userId,
            this.totalAmount, this.status
        ));
    }

    // 支付订单
    public void pay() {
        if (!"PENDING".equals(this.status)) {
            throw new IllegalStateException("只有待支付订单可以支付");
        }
        this.status = "PAID";
        this.domainEvents.add(new OrderPaidEvent(...));
    }

    public List<DomainEvent> getDomainEvents() {
        return new ArrayList<>(this.domainEvents);
    }

    public void clearDomainEvents() {
        this.domainEvents.clear();
    }
}

步骤 3:创建消息对象(Infrastructure 层)

java 复制代码
// OrderCreatedMessage.java
package com.github.microwind.springboot4ddd.infrastructure.messaging.message;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class OrderCreatedMessage implements Serializable {
    private String eventId;
    private Long orderId;
    private String orderNo;
    private Long userId;
    private BigDecimal totalAmount;
    private String status;
    private LocalDateTime occurredAt;
}

步骤 4:创建事件到消息的转换器

java 复制代码
// OrderEventMessageMapper.java
package com.github.microwind.springboot4ddd.infrastructure.messaging.converter;

import com.github.microwind.springboot4ddd.domain.event.order.*;
import com.github.microwind.springboot4ddd.infrastructure.messaging.message.*;
import org.springframework.stereotype.Component;

@Component
public class OrderEventMessageMapper {

    public OrderCreatedMessage toMessage(OrderCreatedEvent event) {
        return OrderCreatedMessage.builder()
                .eventId(event.getEventId())
                .orderId(event.getAggregateId())
                .orderNo(event.getOrderNo())
                .userId(event.getUserId())
                .totalAmount(event.getTotalAmount())
                .status(event.getStatus())
                .occurredAt(event.getOccurredAt())
                .build();
    }

    // 其他事件转换方法...
}

步骤 5:实现消息生产者

java 复制代码
// OrderEventProducer.java
package com.github.microwind.springboot4ddd.infrastructure.messaging.producer;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.microwind.springboot4ddd.domain.event.DomainEvent;
import com.github.microwind.springboot4ddd.domain.event.order.*;
import com.github.microwind.springboot4ddd.infrastructure.messaging.converter.OrderEventMessageMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.stereotype.Component;

@Slf4j
@Component
@RequiredArgsConstructor
public class OrderEventProducer {

    private final RocketMQTemplate rocketMQTemplate;
    private final OrderEventMessageMapper messageMapper;
    private final ObjectMapper objectMapper;

    private static final String TOPIC = "order-events";

    public void publishEvent(DomainEvent event) {
        try {
            // 1. 转换事件为消息对象
            Object message = convertToMessage(event);

            // 2. 序列化为 JSON
            String messageBody = objectMapper.writeValueAsString(message);

            // 3. 发送到 RocketMQ
            String destination = TOPIC + ":" + event.getEventType();
            SendResult result = rocketMQTemplate.syncSend(destination, messageBody);

            log.info("订单事件已发送,eventId={}, eventType={}, msgId={}",
                    event.getEventId(), event.getEventType(), result.getMsgId());
        } catch (Exception e) {
            log.error("发送订单事件失败,eventId={}", event.getEventId(), e);
            throw new RuntimeException("发布订单事件失败", e);
        }
    }

    public void publishEvents(List<DomainEvent> events) {
        for (DomainEvent event : events) {
            publishEvent(event);
        }
    }

    private Object convertToMessage(DomainEvent event) {
        if (event instanceof OrderCreatedEvent) {
            return messageMapper.toMessage((OrderCreatedEvent) event);
        } else if (event instanceof OrderPaidEvent) {
            return messageMapper.toMessage((OrderPaidEvent) event);
        }
        // 其他事件类型...
        throw new IllegalArgumentException("不支持的事件类型: " + event.getClass());
    }
}

步骤 6:实现消息消费者

java 复制代码
// OrderEventConsumer.java
package com.github.microwind.springboot4ddd.infrastructure.messaging.consumer;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.microwind.springboot4ddd.infrastructure.messaging.message.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Component;

@Slf4j
@Component
public class OrderEventConsumer {

    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper()
            .registerModule(new JavaTimeModule());

    // 订单创建事件消费者
    @Slf4j
    @Component
    @RocketMQMessageListener(
            topic = "order-events",
            consumerGroup = "springboot4ddd-order-created-consumer",
            selectorExpression = "OrderCreatedEvent"
    )
    public static class OrderCreatedConsumer implements RocketMQListener<String> {

        @Override
        public void onMessage(String message) {
            try {
                log.info("收到订单创建消息:{}", message);

                OrderCreatedMessage msg = OBJECT_MAPPER.readValue(
                        message, OrderCreatedMessage.class);

                // 业务处理:发送通知
                sendCreationNotification(msg);

                log.info("订单创建消息处理完成,eventId={}", msg.getEventId());
            } catch (Exception e) {
                log.error("处理订单创建消息失败", e);
                throw new RuntimeException("订单创建消息处理失败", e);
            }
        }

        private void sendCreationNotification(OrderCreatedMessage msg) {
            log.info("【模拟通知】向用户 {} 发送订单创建通知,订单号:{}",
                    msg.getUserId(), msg.getOrderNo());
        }
    }

    // 订单支付事件消费者
    @Slf4j
    @Component
    @RocketMQMessageListener(
            topic = "order-events",
            consumerGroup = "springboot4ddd-order-paid-consumer",
            selectorExpression = "OrderPaidEvent"
    )
    public static class OrderPaidConsumer implements RocketMQListener<String> {

        @Override
        public void onMessage(String message) {
            try {
                log.info("收到订单支付消息:{}", message);

                OrderPaidMessage msg = OBJECT_MAPPER.readValue(
                        message, OrderPaidMessage.class);

                // 业务处理:更新库存、发送通知
                updateInventory(msg);
                sendPaymentConfirmation(msg);

                log.info("订单支付消息处理完成,eventId={}", msg.getEventId());
            } catch (Exception e) {
                log.error("处理订单支付消息失败", e);
                throw new RuntimeException("订单支付消息处理失败", e);
            }
        }

        private void updateInventory(OrderPaidMessage msg) {
            log.info("【模拟业务】订单 {} 支付成功,更新库存", msg.getOrderNo());
        }

        private void sendPaymentConfirmation(OrderPaidMessage msg) {
            log.info("【模拟通知】向用户 {} 发送支付成功通知", msg.getUserId());
        }
    }
}

步骤 7:在 OrderService 中发布事件

java 复制代码
// OrderService.java
@Service
@RequiredArgsConstructor
@Transactional(transactionManager = "orderTransactionManager", readOnly = true)
public class OrderService {

    private final OrderRepository orderRepository;
    private final OrderEventProducer orderEventProducer;

    @Transactional(transactionManager = "orderTransactionManager")
    public OrderDTO createOrder(CreateOrderRequest request) {
        // 1. 创建订单
        Order order = Order.create(request.getUserId(), request.getTotalAmount());

        // 2. 持久化
        Order savedOrder = orderRepository.save(order);

        // 3. 记录事件
        savedOrder.recordCreatedEvent();

        // 4. 发布事件到 RocketMQ
        publishDomainEvents(savedOrder);

        return orderMapper.toDTO(savedOrder);
    }

    @Transactional(transactionManager = "orderTransactionManager")
    public OrderDTO payOrder(Long id) {
        Order order = orderRepository.findById(id)
                .orElseThrow(() -> new IllegalArgumentException("订单不存在"));

        // 调用领域行为(会自动记录事件)
        order.pay();

        Order updatedOrder = orderRepository.save(order);

        // 发布事件
        publishDomainEvents(updatedOrder);

        return orderMapper.toDTO(updatedOrder);
    }

    private void publishDomainEvents(Order order) {
        List<DomainEvent> events = order.getDomainEvents();
        if (!events.isEmpty()) {
            orderEventProducer.publishEvents(events);
            order.clearDomainEvents();
        }
    }
}

步骤 8:配置 RocketMQ

pom.xml 依赖(已包含):

xml 复制代码
<dependency>
    <groupId>org.apache.rocketmq</groupId>
    <artifactId>rocketmq-spring-boot-starter</artifactId>
    <version>2.3.0</version>
</dependency>

application.yaml 配置

yaml 复制代码
rocketmq:
  name-server: localhost:9876
  producer:
    group: springboot4ddd-producer  # 生产者组
    send-message-timeout: 3000      # 发送超时(毫秒)
    compress-message-body-threshold: 4096  # 压缩阈值
    # 可选:ACL 认证
    # access-key: RocketMQ
    # secret-key: 12345678
  consumer:
    group: springboot4ddd-consumer  # 默认消费者组
    # access-key: RocketMQ
    # secret-key: 12345678

步骤 9:启动 RocketMQ

bash 复制代码
# 启动 NameServer
cd rocketmq-all-5.3.2-bin-release
nohup sh bin/mqnamesrv &

# 启动 Broker
nohup sh bin/mqbroker -n localhost:9876 &

# 查看进程
jps -l | grep rocketmq

# 应该看到:
# xxxxx org.apache.rocketmq.namesrv.NamesrvStartup
# xxxxx org.apache.rocketmq.broker.BrokerStartup

步骤 10:测试消息收发

1. 启动 Spring Boot 应用

./mvnw spring-boot:run

2. 创建订单(会自动发布 OrderCreatedEvent 消息)

curl -X POST http://localhost:8080/api/orders/create

-H "Content-Type: application/json"

-d '{ "userId": 1, "totalAmount": 999.99 }'

响应示例:

{ "code": 200, "message": "success", "data": { "id": 1, "orderNo": "ORD1736121708403", "userId": 1, "totalAmount": 999.99, "status": "PENDING", "createdAt": "2026-01-11T13:45:08" } }

3. 支付订单(会自动发布 OrderPaidEvent 消息)

curl -X POST http://localhost:8080/api/orders/1/pay

4. 查看应用日志,可以看到:

  • 订单创建成功日志
  • 订单事件已发送,eventId=xxx, eventType=OrderCreatedEvent, msgId=xxx
  • 收到订单创建消息:{"eventId":"xxx",...}
  • 【模拟通知】向用户 1 发送订单创建通知,订单号:ORD1736121708403
  • 订单创建消息处理完成,eventId=xxx

5. 使用 RocketMQ 命令行工具查看消息

shell 复制代码
$ cd rocketmq-all-5.3.2-bin-release

查看 Topic

shell 复制代码
$ sh bin/mqadmin topicList -n 127.0.0.1:9876

查看 Topic 统计

shell 复制代码
sh bin/mqadmin topicStatus -n 127.0.0.1:9876 -t order-events

消费消息(验证)

shell 复制代码
sh bin/mqadmin consumeMessage \
  -n 127.0.0.1:9876 \
  -t order-events \
  -g test-consumer

预期日志输出

生产者日志

shell 复制代码
2026-01-11 13:45:08 INFO  OrderService - 创建订单,userId=1, totalAmount=999.99
2026-01-11 13:45:08 INFO  OrderService - 订单创建成功,orderNo=ORD1736121708403
2026-01-11 13:45:08 INFO  OrderEventProducer - 订单事件已发送,eventId=a1b2c3..., eventType=OrderCreatedEvent, msgId=7F00000100002A9F...

消费者日志

shell 复制代码
2026-01-11 13:45:08 INFO  OrderCreatedConsumer - 收到订单创建消息:{"eventId":"a1b2c3...","orderId":1,"orderNo":"ORD1736121708403"...}
2026-01-11 13:45:08 INFO  OrderCreatedConsumer - 【模拟通知】向用户 1 发送订单创建通知,订单号:ORD1736121708403
2026-01-11 13:45:08 INFO  OrderCreatedConsumer - 订单创建消息处理完成,eventId=a1b2c3...

架构优势总结

特性 传统同步调用 事件驱动架构
耦合度 高 - 订单服务依赖库存、通知等服务 低 - 只需发布事件
性能 差 - 同步等待所有服务响应 好 - 异步处理,秒级返回
可用性 低 - 任一服务故障影响主流程 高 - 消费者故障不影响订单创建
扩展性 难 - 添加新功能需修改订单服务 易 - 只需添加新的消费者
可维护性 复杂 - 职责不清晰 简单 - 职责分离明确

🔧 RocketMQ 配置说明

基础配置

yaml 复制代码
rocketmq:
  name-server: localhost:9876          # NameServer 地址
  producer:
    group: springboot4ddd-producer     # 生产者组名
    send-message-timeout: 3000         # 发送超时(毫秒)
    compress-message-body-threshold: 4096  # 消息体压缩阈值(字节)
    retry-times-when-send-failed: 2    # 发送失败重试次数
    max-message-size: 4194304          # 最大消息大小(4MB)
  consumer:
    group: springboot4ddd-consumer     # 默认消费者组名

ACL 权限配置(可选)

如果 RocketMQ 启用了 ACL,需要配置账号密码:

yaml 复制代码
rocketmq:
  producer:
    access-key: RocketMQ
    secret-key: 12345678
  consumer:
    access-key: RocketMQ
    secret-key: 12345678

RocketMQ Broker 端配置 (conf/plain_acl.yml):

yaml 复制代码
accounts:
  - accessKey: RocketMQ
    secretKey: 12345678
    whiteRemoteAddress:
    admin: false
    defaultTopicPerm: DENY
    defaultGroupPerm: SUB
    topicPerms:
      - order-events=PUB|SUB
    groupPerms:
      - springboot4ddd-producer=PUB
      - springboot4ddd-consumer=SUB
      - springboot4ddd-order-created-consumer=SUB
      - springboot4ddd-order-paid-consumer=SUB

常用 RocketMQ 命令

bash 复制代码
# 查看集群状态
sh bin/mqadmin clusterList -n localhost:9876

# 查看所有 Topic
sh bin/mqadmin topicList -n localhost:9876

# 查看 Topic 详情
sh bin/mqadmin topicStatus -n localhost:9876 -t order-events

# 查看消费者组状态
sh bin/mqadmin consumerProgress -n localhost:9876 -g springboot4ddd-order-created-consumer

# 发送测试消息
sh bin/mqadmin sendMessage \
  -n localhost:9876 \
  -t order-events \
  -p "test message"

# 消费消息
sh bin/mqadmin consumeMessage \
  -n localhost:9876 \
  -t order-events \
  -g test-consumer

🔧 常用配置

多数据源配置

如需添加第三个数据源,参考以下代码:

java 复制代码
@Configuration
public class DataSourceConfig {
    @Bean(name = "thirdDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.third")
    public DataSource thirdDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "thirdJdbcTemplate")
    public JdbcTemplate thirdJdbcTemplate(
            @Qualifier("thirdDataSource") DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }
}

RocketMQ 配置

完整的 RocketMQ 配置示例:

yaml 复制代码
rocketmq:
  name-server: localhost:9876
  producer:
    group: springboot4ddd-producer
    send-message-timeout: 3000
    retry-times-when-send-failed: 2
    max-message-size: 4194304
  consumer:
    group: springboot4ddd-consumer

📚 相关文档

  • README.md - 项目概述和快速开始
  • TUTORIAL.md - 完整的实战教程(3万字详解)
  • DATABASE.md - 多数据源配置详解
  • SIGN_GUIDE.md - API 签名验证使用指南

🤝 获取帮助


📋 检查清单

使用脚手架开发新功能时,确保完成以下步骤:

  • domain/model/ 创建领域模型
  • 在领域模型中实现业务方法
  • domain/repository/ 定义仓储接口
  • infrastructure/repository/ 实现仓储
  • application/service/ 创建应用服务
  • interfaces/controller/ 创建 REST 控制器
  • interfaces/vo/ 创建请求/响应对象
  • 编写数据库初始化脚本
  • 添加单元测试和集成测试
  • 更新 API 文档

⚡ 常见问题

Q: 如何切换数据库?

A: 修改 application.yml 中的数据源配置,并调整相应的初始化脚本。

Q: 如何禁用 API 签名验证?

A: 在控制器方法上添加 @IgnoreSignHeader 注解。

Q: 如何添加新的数据源?

A: 参考 DataSourceConfig.java,添加新的 DataSource Bean 和对应的 JdbcTemplate。

Q: 领域层可以依赖 Spring 吗?

A: 领域层应该保持纯净,除了持久化映射注解(如 @Table@Column)外,尽量不依赖 Spring。

Q: 如何禁用 RocketMQ?

A: 如果不需要事件驱动功能,可以移除 RocketMQ 相关依赖,或者不注入 EventPublisher,系统仍可正常运行。


作者: JarryLi

源码下载: github.com/microwind/d...

ruby 复制代码
https://github.com/microwind/design-patterns/tree/main/practice-projects/springboot4ddd
相关推荐
好奇龙猫7 小时前
【人工智能学习-AI-MIT公开课第 19. 架构:GPS、SOAR、包容架构】
人工智能·学习·架构
一嘴一个橘子7 小时前
spring-aop 的 基础使用 -3 - 切点表达式 的提取、复用
java
Re_zero8 小时前
Java新手避坑:为什么我劝你放弃 scanner.nextInt()?
java
uzong8 小时前
后端系统设计文档模板
后端
幽络源小助理8 小时前
SpringBoot+Vue车票管理系统源码下载 – 幽络源免费项目实战代码
vue.js·spring boot·后端
Good_Starry8 小时前
Java——反射
java
老前端的功夫8 小时前
TypeScript 类型魔术:模板字面量类型的深层解密与工程实践
前端·javascript·ubuntu·架构·typescript·前端框架
uzong8 小时前
软件架构指南 Software Architecture Guide
后端
又是忙碌的一天8 小时前
SpringBoot 创建及登录、拦截器
java·spring boot·后端