前言

做个demo愿意用啥用啥呗,非得问我怎么选择,说白了我直接让ai不到一天就能换一个中间件,我也没啥历史的重担。但是我们以长远的眼光来看,万一你成架构师了呢。

DDD

DDD作为一个比较新,又没那么新的东西。首先就是老生常谈的高内聚和低耦合,通过充血对象,把一堆repository注入到bo,来让bo自己去实现校验、状态转移、业务行为等。

我们先来看一下它的目录长什么样(虽然这然很直观,但是事实上正确理解DDD应该从业务作为起点)

适配器层

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
adapter/
├─ controller/          # 接口控制器(RESTful,适配前端请求)
│  ├─ OrderController.java       # 订单核心接口(创建、支付、取消等)
│  └─ OrderQueryController.java  # 订单查询接口(单独拆分,区分命令/查询)
├─ dto/                 # 数据传输对象(入参,适配外部入参格式)
│  ├─ OrderCreateDto.java
│  └─ OrderPayDto.java
├─ vo/                  # 视图对象(出参,专门给前端展示使用)
│  ├─ OrderDetailVo.java
│  └─ OrderPageVo.java
└─ converter/           # DTO/Vo <-> 领域模型
   └─ OrderConverter.java

应用层

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
application/
├─ service/             # 应用服务(对外提供可调用的业务能力)
│  ├─ OrderApplicationService.java  # 订单核心流程(创建订单、支付订单等)
│  └─ OrderQueryApplicationService.java # 订单查询流程(分页、详情)
├─ command/             # 命令对象(封装“执行操作”的请求,对应领域命令)
│  ├─ CreateOrderCommand.java
│  └─ PayOrderCommand.java
└─ event/               # 应用层事件相关(订阅领域事件,触发跨上下文/跨聚合逻辑)
   └─ listener/         # 领域事件监听器
      ├─ OrderPaidEventListener.java  # 监听订单支付事件,触发库存、积分逻辑
      └─ OrderCanceledEventListener.java

领域层

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
domain/
├─ bo/                  # 业务对象(封装业务逻辑、业务属性,核心业务载体)
│  ├─ OrderBo.java      # 订单BO(包含订单业务逻辑、属性,如取消、支付判断)
│  ├─ OrderItemBo.java  # 订单明细BO
│  └─ CouponBo.java     # 优惠券BO(业务相关实体)
├─ service/             # 领域服务(封装核心业务逻辑,操作BO)
│  └─ OrderDomainService.java  # 订单领域服务(如:订单履约校验、取消规则判断)
├─ event/               # 领域事件(业务事实,由BO触发发布)
│  ├─ OrderCreatedEvent.java   # 订单创建事件
│  ├─ OrderPaidEvent.java      # 订单支付事件
│  └─ OrderCanceledEvent.java  # 订单取消事件
├─ repository/          # 仓储接口(抽象,定义BO/PO的持久化操作,不关心实现)
│  └─ OrderRepository.java     # 订单仓储接口(增删改查、BO与PO转换)
└─ specification/       # 领域规格(封装复杂查询/校验规则,如:订单可取消规格)
   └─ OrderCancelSpecification.java

设施层

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
infrastructure/
├─ repository/          # JPA仓储实现(实现领域层仓储接口,基于JPA完成持久化)
│  └─ impl/
│     └─ OrderRepositoryImpl.java  # 订单JPA仓储实现(继承JpaRepository,操作PO)
├─ po/                  # 数据库实体(与领域层PO一致,添加JPA注解,用于ORM映射)
│  ├─ OrderPo.java      # 订单PO(添加@Entity、@Id等JPA注解)
│  └─ OrderItemPo.java  # 订单明细PO(添加JPA注解)
└─ config/              # 基础设施配置(JPA配置、数据库配置等全局配置)
   ├─ JpaConfig.java    # JPA核心配置(指定实体扫描、方言等)
   └─ DataSourceConfig.java  # 数据源配置

最后还可以加个common

1
2
3
4
5
6
7
8
common/
├─ constant/            # 全局常量
│  ├─ OrderConstant.java
│  └─ EventConstant.java
├─ exception/           # 全局异常(区分业务异常、系统异常)
│  ├─ BusinessException.java  # 业务异常(如:订单不可取消)
│  └─ GlobalExceptionHandler.java  # 全局异常处理器
├─ util/                # 通用工具

最后,我没用过纯的DDD架构,事实上也没人知道DDD架构是什么样子的,准确的来讲DDD风味架构更适合大家。

最后用一个订单支付来体验一下纯的ddd流程。

1
2
3
4
5
6
7
8
接口层请求(PayDTO) 
→ 应用层服务(OrderPayService)
  → 调用OrderRepository(领域接口)加载聚合Order
    → Order(BO/聚合根).pay():校验规则 + 改状态 + 生成OrderPaid领域事件
  → 应用层保存Order(通过Repository)
  → 发布领域事件OrderPaid
→ 事件监听器监听事件
  → 执行后续业务(扣库存/发通知/积分等)

其中最关键的就是bo的pay没有真的pay,而是生成了一个cmd到service层来让它再去调用支付。

这就体现出来依赖倒置:接口层 → 应用层 → 领域层 ← 基础设施层

消息队列

先来一个酣畅淋漓的AI表格总结一手。

指标 RabbitMQ Kafka RocketMQ
吞吐量 中等(10K-50K TPS) 极高(100K+ TPS,单机百万级) 高(50K-100K TPS)
延迟 低(微秒级,最佳约 50μs) 较高(毫秒级,批处理设计) 低(毫秒级,平衡吞吐与延迟)
消息堆积能力 强(适合中小规模堆积) 极强(海量日志 / 数据,分层存储) 强(支持百亿级消息堆积)
顺序消息 单队列严格顺序 分区内顺序 全局顺序 / 分区顺序

如果简而言之给出一个爆论那就是 Java 就用 rocketmq 大数据就用kafka。可是悲剧的是当时我一懒,用的rabbitmq,那如果问我技术选型怎么回答呢。

RabbitMQ 部署简单、API 友好,开发效率高,适合快速搭建项目原型,消息确认、持久化、死信队列这些功能开箱即用,足够支撑秒杀业务。然我也了解,在真实生产环境里,Java 电商 / 金融类业务更常用 RocketMQ,它在事务消息、顺序消息、大规模堆积上更有优势;如果是日志采集、流计算场景会用 Kafka。

当然这就给自己挖坑了,就该问你这仨架构,然后分布式事务,幂等,然后就寄了。

数据库

MySQL vs PostgreSQL 你们知道吗,但是好像Oracle牛逼。

但是如果问你为啥用PostgreSQL呢?如果你干巴巴地说你觉得PostgreSQL牛逼,性能屌,他又开始说你瞎选没有理解了。

所以我们跟他扯一点东西,让他觉得我好像真懂点。

  1. JSON/JSONB 这个绝对可以把,直接把mongodb的市场抢占了
  2. uuid_generate_v4():这就可以扯到为什么PG可以用uuid作为主键了
  3. 原本想说开窗函数啥的,后来发现这个高版本MySQL也支持了
  4. 依旧秒杀:FOR UPDATE SKIP LOCKED,然后主键id不同,goodId相同,这样的话相当于把good拆成几分,然后跳过锁上的。
  5. UNLOGGED TABLE:这个抽象吧,直接当缓存用了,就是不用WAL日志