前言
做个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牛逼,性能屌,他又开始说你瞎选没有理解了。
所以我们跟他扯一点东西,让他觉得我好像真懂点。
- JSON/JSONB 这个绝对可以把,直接把mongodb的市场抢占了
- uuid_generate_v4():这就可以扯到为什么PG可以用uuid作为主键了
- 原本想说开窗函数啥的,后来发现这个高版本MySQL也支持了
- 依旧秒杀:
FOR UPDATE SKIP LOCKED,然后主键id不同,goodId相同,这样的话相当于把good拆成几分,然后跳过锁上的。
UNLOGGED TABLE:这个抽象吧,直接当缓存用了,就是不用WAL日志