一个领域驱动设计的交易系统的示例项目
本项目是一个基于领域驱动设计(DDD)原则构建的交易系统示例,展示了如何在 Java 8 + Spring Boot 2.7.18 环境下实现企业级的交易系统架构。项目采用 Gradle 多模块构建,遵循 SOFA 分层架构设计。
- Java: 8 (使用 Gradle Toolchain 管理)
- Spring Boot: 2.7.18
- Spring Cloud: 2021.0.8
- 构建工具: Gradle 8.6
- 持久层:
- Spring Data JPA
- MyBatis 2.3.2
- MySQL / MariaDB
- 测试框架: JUnit 5.9.3
- 日志: Log4j2
- 可观测性: OpenTelemetry 2.10.0
domain-driven-transaction-sys
├── biz-service-impl/ # 业务服务实现层
├── biz-shared/ # 业务共享模块
├── common-dal/ # 数据访问层
├── common-service-facade/ # 服务门面层
├── common-service-integration/ # 服务集成层
├── common-util/ # 通用工具类
├── core-model/ # 核心领域模型
└── core-service/ # 核心服务层
- biz-service-impl: 业务服务实现,包含 WebFlux 和 Web MVC 支持,是应用的可启动模块
- biz-shared: 业务共享模块,提供业务级别的共享组件
- common-dal: 数据访问层,集成 JPA 和 MyBatis,支持多数据源
- common-service-facade: 服务门面,定义对外暴露的服务接口
- common-service-integration: 服务集成层,处理第三方系统集成
- common-util: 通用工具类库
- core-model: 核心领域模型,包含领域实体、值对象、聚合根等
- core-service: 核心领域服务,实现业务逻辑
- JDK 8 (推荐使用 SDKMAN 安装:
sdk install java 8.0.432) - Gradle 8.6 (项目自带 Gradle Wrapper)
# 清理并构建(跳过测试)
./gradlew clean build -x test --stacktrace
# 完整构建(包含测试)
./gradlew clean build# 运行所有测试
./gradlew test
# 运行所有测试并生成聚合报告
./gradlew testAggregateTestReport
# 运行指定测试类
./gradlew test --tests com.magicliang.transaction.sys.aop.factory.ProxyFactoryTest
# 运行指定测试方法
./gradlew test --tests 'com.magicliang.transaction.sys.DomainDrivenTransactionSysApplicationIntegrationTest.testGetWildCardType'
# 使用通配符运行测试
./gradlew test --tests com.magicliang.transaction.sys.DomainDrivenTransactionSysApplicationIntegrationTest.*test*项目通过 Spring Profile 机制支持多种数据库接入方式,通过 application.yml 中的 spring.profiles.active 切换,或在启动参数中指定 --spring.profiles.active=xxx。
| Profile | 数据库类型 | 适用环节 | 是否需要外部数据库 | 前置条件 |
|---|---|---|---|---|
local-tc-dev (默认) |
MariaDB (Docker 容器) | 本地开发、集成测试 | 否 | 安装并运行 Docker 或 Podman |
local-mariadb4j-dev |
MariaDB (嵌入式二进制) | 本地开发、集成测试 | 否 | 仅支持 x86 架构 |
local-mysql-dev |
MySQL 8.0+ | 本地开发 | 是 | 本地安装并启动 MySQL |
staging |
由部署环境提供 | 预发环境 | 是 | 环境数据库连接配置 |
prod |
由部署环境提供 | 生产环境 | 是 | 环境数据库连接配置 |
以下 Profile 内置了数据库的完整生命周期管理,启动时自动创建数据库、执行 DDL,开发者无需手动安装或配置任何数据库:
local-tc-dev(推荐,默认激活)
- 通过 Testcontainers 自动启动 MariaDB 10.11 Docker 容器
- 支持所有芯片架构(ARM64/AMD64/x86_64)和操作系统
- 容器运行时支持 Docker Desktop 和 Podman
- 自动创建
test_master和test_slave1双数据库,执行sql/mysql/schema.ddl初始化 - 数据库初始化由
EmbeddedTestcontainersDbConfig手动管理(spring.sql.init.mode=never),不依赖 Spring Boot 自动初始化 - 首次运行自动拉取镜像(约 400MB),后续使用缓存
local-mariadb4j-dev
- 通过 mariadb4j 在 JVM 进程内解压并启动 MariaDB 原生二进制
- 在端口 4306(master)和 4307(slave)启动两个独立实例
- 仅支持 x86_64 架构,ARM 芯片(Apple Silicon 等)不可用
- 无需 Docker,无需安装任何数据库
以下 Profile 需要开发者自行准备数据库实例,并在配置中提供正确的连接信息:
local-mysql-dev
- 连接本地运行的 MySQL 8.0+ 实例
- 需要在
application.yml中配置spring.datasource.master.jdbc-url、username、password等属性 - 需要手动创建数据库和执行 DDL
- 配置类:
DataSourceConfig.java,通过@ConfigurationProperties绑定属性
staging / prod
- 由部署环境(K8s ConfigMap、环境变量等)提供数据库连接配置
- 项目中不包含具体的数据源配置,需由运维或部署流程注入
安装 Docker Desktop 后,无需额外配置,直接运行:
# 使用默认 Profile(local-tc-dev)
./gradlew test
# 启动应用
./gradlew bootRun如果使用 Podman 替代 Docker,需要额外配置:
1. 安装并启动 Podman
# macOS
brew install podman
podman machine init
podman machine start2. 配置 Testcontainers 使用 Podman socket
创建 ~/.testcontainers.properties:
docker.host=unix:///var/folders/<your-path>/podman/podman-machine-default-api.sock
ryuk.container.privileged=true
testcontainers.reuse.enable=true获取实际 socket 路径:
podman machine inspect --format '{{.ConnectionInfo.PodmanSocket.Path}}'3. 配置镜像加速(可选,网络受限时使用)
编辑 Podman 虚拟机内的 /etc/containers/registries.conf:
podman machine ssh
sudo vi /etc/containers/registries.conf添加镜像加速配置:
[[registry]]
location = "docker.io"
[[registry.mirror]]
location = "docker.m.daocloud.io"4. 运行测试
# 设置 DOCKER_HOST 环境变量指向 Podman socket
export DOCKER_HOST="unix://$(podman machine inspect --format '{{.ConnectionInfo.PodmanSocket.Path}}')"
export TESTCONTAINERS_RYUK_DISABLED=true
# 运行测试
./gradlew test
# 或一行命令
DOCKER_HOST="unix://$(podman machine inspect --format '{{.ConnectionInfo.PodmanSocket.Path}}')" \
TESTCONTAINERS_RYUK_DISABLED=true \
./gradlew test# 使用默认 Profile(local-tc-dev,需要 Docker/Podman)
./gradlew test
# 指定其他 Profile
./gradlew test -Dspring.profiles.active=local-mariadb4j-dev
./gradlew bootRun -Dspring.profiles.active=local-mysql-dev项目支持通过 Docker 容器化 + Kubernetes 集群化部署,使用 Podman 替代 Docker、minikube 作为本地 K8s 环境。支持 dev / staging / prod 三套独立环境,每个环境拥有独立的 MariaDB 数据库(持久化存储)和独立的 Java 应用 Pod。
┌─────────────── minikube cluster ───────────────────┐
│ │
│ ┌─── ddts-dev ─────────────────────────────────┐ │
│ │ MariaDB Pod (10.11) ◄── App Pod (JDK 8) │ │
│ │ PVC 1Gi 1 replica │ │
│ │ ClusterIP:3306 LoadBalancer:8502 │ │
│ └──────────────────────────────────────────────┘ │
│ │
│ ┌─── ddts-staging ─────────────────────────────┐ │
│ │ MariaDB Pod ◄── App Pod │ │
│ │ PVC 5Gi 1 replica │ │
│ └──────────────────────────────────────────────┘ │
│ │
│ ┌─── ddts-prod ────────────────────────────────┐ │
│ │ MariaDB Pod ◄── App Pod │ │
│ │ PVC 20Gi 2 replicas │ │
│ └──────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────┘
│ minikube tunnel
▼
localhost:8502
deploy/
├── docker/
│ └── Dockerfile # 两阶段构建(JDK 8 编译 → JRE 8 运行)
├── k8s/
│ ├── dev/ # dev 环境 K8s 清单
│ │ ├── 00-namespace.yaml # 命名空间 ddts-dev
│ │ ├── 01-mariadb-secret.yaml # MariaDB root 密码
│ │ ├── 02-mariadb-init-configmap.yaml # 数据库初始化 SQL(schema.ddl)
│ │ ├── 03-mariadb-pvc.yaml # 持久化卷声明(1Gi)
│ │ ├── 04-mariadb-deployment.yaml # MariaDB 10.11 Deployment
│ │ ├── 05-mariadb-service.yaml # MariaDB ClusterIP Service
│ │ ├── 06-app-configmap.yaml # Spring Boot 环境变量配置
│ │ ├── 07-app-secret.yaml # 数据库连接密码
│ │ ├── 08-app-deployment.yaml # Java 应用 Deployment
│ │ └── 09-app-service.yaml # 应用 LoadBalancer Service
│ ├── staging/ # staging 环境(同结构,更大资源配额)
│ └── prod/ # prod 环境(同结构,2副本,最大配额)
└── scripts/
├── env-init.sh # 一键安装工具链
└── env-start.sh # 一键启动/销毁/查看环境
env-init.sh 会自动检测操作系统(macOS/Linux),幂等安装 JDK 8、Podman、kubectl、minikube,并启动 minikube 集群:
./deploy/scripts/env-init.sh支持的系统和包管理器:
- macOS: Homebrew (
brew) - Debian/Ubuntu: APT (
apt) - RHEL/Fedora/CentOS: DNF/YUM (
dnf/yum)
已安装的工具会自动跳过,脚本可重复执行。
env-start.sh 会自动构建 Docker 镜像、加载到 minikube、部署 K8s 清单、等待就绪、启动 LoadBalancer tunnel:
# 启动 dev 环境
./deploy/scripts/env-start.sh dev
# 启动 staging 环境
./deploy/scripts/env-start.sh staging
# 启动 prod 环境
./deploy/scripts/env-start.sh prod启动完成后,脚本会打印访问地址(通常为 http://127.0.0.1:8502)。
./deploy/scripts/env-start.sh dev --status输出包括 Pod 运行状态、Service 信息、PVC 使用情况。
./deploy/scripts/env-start.sh dev --destroy会删除整个 namespace 及其所有资源(包括持久化数据)。
| 参数 | dev | staging | prod |
|---|---|---|---|
| Namespace | ddts-dev |
ddts-staging |
ddts-prod |
| PVC 容量 | 1Gi | 5Gi | 20Gi |
| MariaDB 内存 | 256Mi-512Mi | 512Mi-1Gi | 1Gi-2Gi |
| App 副本数 | 1 | 1 | 2 |
| JVM 参数 | -Xms256m -Xmx512m |
-Xms512m -Xmx1g |
-Xms1g -Xmx2g |
| App 内存 | 512Mi-768Mi | 768Mi-1.5Gi | 1.5Gi-3Gi |
| 日志配置 | log4j2-offline.xml | log4j2-online.xml | log4j2-online.xml |
采用两阶段构建优化镜像大小和构建缓存:
- Stage 1 (builder): 基于
eclipse-temurin:8-jdk,使用项目自带的gradlew编译 bootJar。构建配置文件优先复制以利用 Docker 层缓存——源码变更不会重新下载依赖。 - Stage 2 (runtime): 基于
eclipse-temurin:8-jre,仅包含运行时。以非 root 用户app运行,暴露端口 8502。
手动构建镜像:
podman build -t domain-driven-transaction-sys:latest -f deploy/docker/Dockerfile .每个 K8s 环境使用各自独立的 Spring Profile,而非共用同一个:
| K8s 环境 | SPRING_PROFILES_ACTIVE | application.yml 中的 profile | 说明 |
|---|---|---|---|
| dev | local-mysql-dev |
local-mysql-dev |
本地 MySQL 开发 profile,与独立运行时相同 |
| staging | staging |
staging |
预发布环境专用 profile |
| prod | prod |
prod |
生产环境专用 profile |
DataSourceConfig.java 中的 @Profile({"local-mysql-dev", "staging", "prod"}) 确保在以上三个 profile 激活时都会创建数据源 Bean。
K8s 通过 ConfigMap 和 Secret 注入环境变量,覆盖 application.yml 中的默认值。Spring Boot 的松绑定规则将环境变量自动映射为配置属性(例如 SPRING_DATASOURCE_MASTER_JDBC_URL → spring.datasource.master.jdbc-url)。
优先级链(从高到低):
K8s Secret 环境变量 > K8s ConfigMap 环境变量 > application.yml 中 profile 段的默认值 > application.yml 主段的默认值
各环境变量的用途:
| 环境变量 | 来源 | 用途 |
|---|---|---|
SPRING_PROFILES_ACTIVE |
ConfigMap | 激活对应环境的 Spring Profile |
SPRING_DATASOURCE_MASTER_JDBC_URL |
ConfigMap | 主库 JDBC 连接串(指向 K8s 内部 mariadb-svc:3306) |
SPRING_DATASOURCE_MASTER_USERNAME |
ConfigMap | 主库用户名 |
SPRING_DATASOURCE_MASTER_PASSWORD |
Secret | 主库密码(base64 编码存储) |
SPRING_DATASOURCE_SLAVE1_JDBC_URL |
ConfigMap | 从库 JDBC 连接串 |
SPRING_DATASOURCE_SLAVE1_USERNAME |
ConfigMap | 从库用户名 |
SPRING_DATASOURCE_SLAVE1_PASSWORD |
Secret | 从库密码(base64 编码存储) |
JAVA_OPTS |
ConfigMap | JVM 参数(-Xms/-Xmx 等) |
COMMON_ENV |
ConfigMap | 业务环境标识 |
LOGGING_CONFIG |
ConfigMap | 日志配置路径 |
MariaDB 数据库初始化利用官方镜像的 /docker-entrypoint-initdb.d/ 机制:首次启动(PVC 为空)时自动执行 ConfigMap(02-mariadb-init-configmap.yaml)中的 SQL 脚本,按文件名顺序执行:
00-create-databases.sql— 创建test_master和test_slave1数据库01-schema.sql— 在两个数据库中执行 schema.ddl 建表02-data.sql— 插入初始数据
PVC 非空时(非首次启动),初始化脚本不会重复执行。
以下是各种常见配置修改场景的操作方法。所有修改完成后重新 apply 即可生效:
kubectl apply -f deploy/k8s/<env>/步骤 1: 生成新密码的 base64 编码:
echo -n '你的新密码' | base64步骤 2: 同时修改两个 Secret 文件(MariaDB root 密码和应用连接密码必须一致):
deploy/k8s/<env>/01-mariadb-secret.yaml— 修改MARIADB_ROOT_PASSWORDdeploy/k8s/<env>/07-app-secret.yaml— 修改SPRING_DATASOURCE_MASTER_PASSWORD和SPRING_DATASOURCE_SLAVE1_PASSWORD
步骤 3: 重启 Pod 使新密码生效:
kubectl rollout restart deployment/mariadb -n ddts-<env>
kubectl rollout restart deployment/ddts-app -n ddts-<env>注意: 如果 MariaDB 的 PVC 已经初始化过,仅修改
01-mariadb-secret.yaml中的密码不会改变数据库中已有的 root 密码。需要手动进入容器执行ALTER USER或删除 PVC 重建。
编辑 deploy/k8s/<env>/06-app-configmap.yaml,修改 SPRING_DATASOURCE_MASTER_JDBC_URL 和 SPRING_DATASOURCE_SLAVE1_JDBC_URL 的值。
常见调整项(均在 JDBC URL 查询参数中):
| 参数 | 默认值 | 说明 |
|---|---|---|
socketTimeout |
10000 | 数据库操作超时(ms),高延迟环境可增大 |
connectTimeout |
5000 | 连接建立超时(ms) |
useSSL |
false | K8s 集群内部通信,默认关闭 SSL |
useCompression |
true | 启用压缩,减少网络传输量 |
如需连接外部数据库(非 K8s 内 MariaDB),将 mariadb-svc:3306 替换为外部地址:
SPRING_DATASOURCE_MASTER_JDBC_URL: "jdbc:mysql://external-db.example.com:3306/test_master?..."编辑 deploy/k8s/<env>/06-app-configmap.yaml,修改 JAVA_OPTS 字段:
JAVA_OPTS: "-Xms1g -Xmx2g -XX:+UseG1GC -XX:MaxGCPauseMillis=200"注意: JVM 最大堆内存(
-Xmx)不应超过08-app-deployment.yaml中resources.limits.memory的 70%,否则 Pod 可能被 OOMKilled。
编辑 deploy/k8s/<env>/08-app-deployment.yaml,修改 resources 段:
resources:
requests:
memory: "1Gi" # 最低保证内存
cpu: "500m" # 最低保证 CPU(1000m = 1核)
limits:
memory: "2Gi" # 内存上限,超过会 OOMKill
cpu: "2000m" # CPU 上限修改副本数:
spec:
replicas: 3 # 调整 Pod 副本数编辑 deploy/k8s/<env>/02-mariadb-init-configmap.yaml。该 ConfigMap 包含三个 SQL 文件:
00-create-databases.sql— 创建数据库(一般不需要改)01-schema.sql— 表结构 DDL(修改表结构时更新此处)02-data.sql— 初始数据(修改初始化数据时更新此处)
注意: 修改初始化 SQL 只影响新创建的环境(PVC 为空时)。已有环境需要手动进入 MariaDB 执行变更 SQL,或者销毁环境后重建:
./deploy/scripts/env-start.sh <env> --destroy ./deploy/scripts/env-start.sh <env>
编辑 deploy/k8s/<env>/06-app-configmap.yaml,修改 LOGGING_CONFIG:
# 线下(详细日志 + mybatis SQL)
LOGGING_CONFIG: "classpath:log4j2/log4j2-offline.xml"
# 线上(精简日志)
LOGGING_CONFIG: "classpath:log4j2/log4j2-online.xml"任何 Spring Boot 配置属性都可以通过 ConfigMap 环境变量注入。命名规则:
配置属性: spring.datasource.hikari.maximum-pool-size
环境变量: SPRING_DATASOURCE_HIKARI_MAXIMUM_POOL_SIZE
转换规则:. → _,- → _,全部大写。
在 06-app-configmap.yaml 的 data 段添加即可:
data:
# ... 现有配置 ...
SPRING_DATASOURCE_HIKARI_MAXIMUM_POOL_SIZE: "50"
SERVER_TOMCAT_THREADS_MAX: "300"K8s 部署不影响本地开发工作流。IDEA 中继续使用默认的 local-tc-dev profile 启动 DomainDrivenTransactionSysApplication,Testcontainers 自动管理临时 MariaDB 容器,数据不持久化,行为与此前完全一致。
DataSourceConfig.java 中的 @Profile({"local-mysql-dev", "staging", "prod"}) 不影响 local-tc-dev profile — 后者的数据源由 EmbeddedTestcontainersDbConfig 单独管理。
# 查看 Pod 状态
kubectl get pods -n ddts-dev
# 查看应用日志
kubectl logs -n ddts-dev -l app=ddts-app -f
# 查看 Service(获取外部 IP)
kubectl get svc -n ddts-dev
# 进入 MariaDB 容器
kubectl exec -it -n ddts-dev deploy/mariadb -- mysql -uroot -p
# 端口转发(不用 tunnel 的替代方案)
kubectl port-forward -n ddts-dev svc/ddts-app-svc 8502:8502项目遵循 SOFA (Service Oriented Fabric Architecture) 分层原则,实现清晰的职责分离:
分层说明:
- 展示层: 处理 HTTP 请求,参数校验,响应封装
- 应用层: 编排业务流程,事务管理
- 领域层: 核心业务逻辑,领域模型
- 基础层: 数据访问,第三方集成,技术支撑
采用 Gradle 多模块构建,每个模块职责清晰,依赖关系明确:
biz-service-impl
├── biz-shared
├── common-service-facade
└── (Spring Boot dependencies)
core-model
├── common-util
├── common-service-integration
└── common-dal
- 尽量对列表使用浅拷贝后再操作,避免影响原列表及其 subList
- 使用 Rich Object 设计模式,封装业务行为
- 遵循领域驱动设计原则,将业务逻辑集中在领域层
项目支持单元测试和集成测试:
sourceSets {
test.java.srcDirs = ['src/test/integration/java', 'src/test/unit/java']
}测试任务支持并行执行,提升测试效率:
test {
maxParallelForks = Runtime.runtime.availableProcessors()
}- 评估是否将服务改造为 K8s 微服务架构
- 引入更多最佳实践到开源项目
- 梳理 Gradle Task,完善集成测试和单元测试用例
- 实现 Spring WebFlux Reactor Controller
- 引入 mariadb4j 用于集成测试
- 脚本 Docker 化,准备 MySQL + K8s 集群
- Mock 支付环节
- 补充模型设计和系统分层文档
- 实现线程池开源化
- 实现基于 DB 的最大努力型事务
- 实现聚合根(Aggregate Root)的工厂方法化
- 实现值对象(Value Object)的构建
- 补全 JPA 映射方法
- 补全 MyBatis 映射方法
- 实现动态生成或 Immutable List 的 DRM/MCC/apollo/rainbow 方案
如果遇到 git push 时提示 "Authentication failed" 或要求输入用户名密码,说明仓库使用的是 HTTPS URL 而非 SSH URL。
-
检查 SSH 密钥
ls -la ~/.ssh/ # 应该能看到 id_rsa 和 id_rsa.pub
-
复制公钥内容
cat ~/.ssh/id_rsa.pub -
添加公钥到 GitHub
- 访问: https://github.com/settings/keys
- 点击 "New SSH key"
- 粘贴公钥内容并保存
-
测试 SSH 连接
ssh -T git@github.com # 成功会显示: Hi username! You've successfully authenticated -
更新 remote URL 为 SSH 格式
# 查看当前 remote URL git remote -v # 如果是 HTTPS URL,改为 SSH URL git remote set-url origin git@github.com:magicliang/domain-driven-transaction-sys.git # 验证修改 git remote -v
- ❌ HTTPS:
https://github.com/magicliang/domain-driven-transaction-sys.git(需要输入密码) - ✅ SSH:
git@github.com:magicliang/domain-driven-transaction-sys.git(免密登录)
f6cce91- docs: 添加 GitHub SSH 免登录配置说明475545e- feat: 添加 JsonDiffToolJackson 工具类及完整测试62094b7- chore: 删除 ArrayDeque 等多个数据结构类文件755b709- feat: 新增空间优化的动态规划方法及多场景测试f8b3a6d- docs: 为二维和一维DP数组添加初始条件及逻辑注释
本项目仅供学习和参考使用。
欢迎提交 Issue 和 Pull Request!

