Java 后端开发:新架构趋势详解(DDD、六边形、模块化单体、事件驱动、平台工程)

引言

当业务复杂度、团队规模、上线频率不断上升,传统的三层架构(Controller/Service/DAO)很容易变成“文件夹分类”,最终带来:

  • 服务层越来越大,变更难以定位
  • 模块之间通过共享数据库实体/公共包耦合
  • 技术细节(DB/MQ/第三方接口)侵入业务代码
  • 跨模块协同依赖链很长,上线风险高

这篇文章用清晰的方式解释这些架构趋势:它们各自解决什么问题、怎么落地、适合哪些场景。


1. 从三层模式到领域边界

1.1 三层模式在复杂业务下的常见问题

在简单系统中,三层模式很高效;但业务一变复杂,就容易出现:

  • “业务规则”散落在多个类里:Service/DAO/查询逻辑都写着规则
  • 同一个业务概念在不同模块被重复建模(“订单”在多个模块有不同含义)
  • 团队为了复用,把 Entity 在多个模块之间传来传去,导致对象随处可改
  • 改一条流程需要改多个 Service,甚至改 SQL,影响范围不可控

1.2 领域边界(Bounded Context)的意义

领域边界的核心是:在系统内部划清“职责范围”和“术语范围”。

  • 每个边界只定义自己理解的业务概念
  • 对外只暴露必要的接口(API/DTO/事件)
  • 内部实现(数据库表、具体状态机、具体流程)不泄露给外部

这样做的好处是:模块间协作更清晰、代码变化集中、测试和上线风险更小。

1.3 划分边界的实践步骤(建议)

  1. 列出主要业务能力:订单、库存、结算、会员、风控、支付等
  2. 逐项回答:这个能力由哪个团队负责?数据归谁维护?规则属于谁?
  3. 确定上下游关系:谁是来源(上游),谁依赖(下游)
  4. 定义对外契约:API/DTO/事件(不要在跨边界时传数据库实体)

1.4 示例:电商业务

  • 订单负责订单生命周期(创建、支付、取消、完成)
  • 库存负责库存锁定/扣减,但不需要理解订单所有字段
  • 会员/营销只需要订单事件做优惠发放,并不直接改订单系统
  • 支付只负责完成支付流程并把支付结果通知订单

2. DDD:把业务规则放到核心

DDD 的目标不是“用很多名词”,而是让业务规则变得可读、可维护、可演进。

2.1 战略设计:先确定边界,再谈实现

  • Bounded Context:确定哪些功能属于同一个边界
  • Context Map:确定边界与边界的关系
    • 上游/下游(Upstream/Downstream)
    • 反腐层(ACL):当接入上游的模型与本边界不一致时,做转换
    • 共享内核:少量稳定的共享模型(谨慎使用)

2.2 战术设计:把规则和模型写“对”

  • 实体(Entity):有唯一身份(ID),状态可变化
  • 值对象(Value Object):描述属性、可比较、不可变
  • 聚合/聚合根(Aggregate/Aggregate Root):把一致性规则放在边界内,外部只能通过聚合根修改
  • 领域服务(Domain Service):当规则跨多个对象且不属于某一个对象时使用
  • 应用服务(Application Service):负责用例编排、事务边界、调用外部端口
  • 领域事件(Domain Event):表达“发生了某件业务上的变化”,用于内部解耦
  • 仓储(Repository):聚合的持久化接口

2.3 示例:订单聚合与领域事件

// 领域层
class Order {
  private final OrderId id;
  private OrderStatus status;
  private Money total;

  void pay(String paymentId) {
    if (status != OrderStatus.CREATED) {
      throw new IllegalStateException("Only CREATED order can be paid");
    }
    status = OrderStatus.PAID;
    DomainEvents.raise(new OrderPaid(id, paymentId));
  }
}

// 应用层
@Service
class OrderCommandService {
  private final OrderRepository orderRepository;
  private final PaymentPort paymentPort;

  @Transactional
  void payOrder(OrderId id, PaymentRequest request) {
    Order order = orderRepository.get(id);
    String paymentId = paymentPort.pay(order, request);
    order.pay(paymentId);
    orderRepository.save(order);
  }
}

领域层负责“规则”和“状态变化”,应用层负责“流程编排”和“事务”。


3. 模块化单体(Modular Monolith):单体部署,模块化治理

模块化单体强调:部署仍是一个应用,但代码按模块严格划边界,依赖方向受控。它是很多团队从小系统成长到复杂系统时非常实用的形态。

3.1 适用场景

  • 业务已经变复杂,但团队/资源不适合马上做微服务治理
  • 希望先把边界定义清楚,后续再按边界拆分
  • 需要快速迭代,但又不想系统越写越耦合

3.2 目录与依赖约束示例

app
├── order-domain
├── order-app
├── order-adapter
├── order-api
└── shared-kernel (谨慎、少量、稳定)

依赖方向建议:

  • domain 不依赖 adapter
  • adapter 实现 port,不要反向侵入 domain
  • 控制 shared-kernel 的内容和体积,避免变成“大杂烩”

3.3 渐进式拆分策略

当某个模块:团队独立、部署频繁、资源隔离需求明确时,才考虑拆成微服务;拆分时尽量沿着之前定义好的边界切割。


4. 六边形架构:端口与适配器

六边形架构(Ports & Adapters)强调:业务逻辑与外部技术细节解耦。

4.1 核心思想

  • Domain:纯业务模型(尽量不依赖框架)
  • Application:用例编排
  • Ports:抽象外部依赖(例如支付、仓储、消息发布)
  • Adapters:实现 ports,把外部世界接进来(DB/MQ/HTTP/第三方)

4.2 示例:支付端口

// port
public interface PaymentPort {
  String pay(Order order, PaymentRequest request);
}

// adapter(支付渠道A)
@Component
class PaymentAdapterA implements PaymentPort {
  public String pay(Order order, PaymentRequest request) {
    // 调用渠道A
    return channelA.pay(...);
  }
}

// adapter(支付渠道B)
@Component
class PaymentAdapterB implements PaymentPort {
  public String pay(Order order, PaymentRequest request) {
    // 调用渠道B
    return channelB.pay(...);
  }
}

当支付渠道发生变化,只需要替换 adapter,而不是改业务逻辑。

4.3 适用场景

  • 经常需要替换 DB/MQ/缓存/第三方接口
  • 希望写更容易测试的业务代码(domain 可单元测试)
  • 想降低“技术选型”对业务的影响

5. 事件驱动架构(EDA):用事件解耦跨模块协同

事件驱动的目标是:让一个模块完成自己的主要流程后,用事件通知其他模块做后续处理,从而降低耦合。

5.1 不要把所有事情都变成事件

  • 命令(Command):用于“做事”——创建订单、提交支付等
  • 事件(Event):用于“通知发生过什么”——订单创建完成、支付成功

主流程还是用命令驱动,事件更适合用来触发边侧动作(补充、增强,而不是主流程的关键链路)。

5.2 领域事件 vs 集成事件

  • 领域事件(Domain Event):在边界内用来表达状态变化
  • 集成事件(Integration Event):对外系统使用的事件格式,用于跨系统或跨边界通知

很多团队会在内部用领域事件,在发往消息系统时转换成集成事件(DTO)。

5.3 落地要点

  • Outbox 模式:把事件与业务更新放在同一事务写入 Outbox 表,再异步发布到 MQ,避免“业务成功但事件丢失”
  • 幂等处理:消费端按照 eventId 或业务主键去重,允许重复投递
  • 错误重试与死信:明确失败后的重试、死信队列策略,并做好告警
  • 可观测性:事件链路需要 Trace/Log/Metrics,方便定位问题

5.4 示例:订单创建事件

订单创建完成后发布事件:

  • 库存模块订阅事件:锁定或扣减库存
  • 会员/营销模块订阅事件:发放优惠券/积分
  • 风控模块订阅事件:做规则检查、记录日志

各模块只需要关心自己的“动作”,不用互相调用复杂接口。


6. 平台工程:把工程能力标准化

平台工程(Platform Engineering)目标是:把研发过程中重复的工作(建项目、部署、监控、日志、告警、流程、规范)统一成“平台能力”,让业务团队更专注业务。

6.1 平台常见能力

  • 服务模板/项目脚手架:生成统一的项目结构与治理能力
  • CI/CD:构建、测试、发布流水线标准化
  • Observability:日志、指标、链路追踪统一
  • 配置与密钥管理:安全、可审计、可回滚
  • 数据库变更管理:对 schema 变更进行版本控制与回放
  • 自动化检测:安全扫描、依赖更新、基础门禁(单测、代码质量)

6.2 平台团队与业务团队的协作方式

平台团队像“产品团队”一样运营平台:有路线图、版本、文档、灰度与支持;业务团队像“平台用户”,通过自助服务完成环境、发布、监控等工作。


7. 如何组合落地(建议)

  • 如果刚开始变复杂:优先定义领域边界 + 模块化单体
  • 在模块内部:用六边形(端口/适配器)保护业务逻辑
  • 跨模块协同:用事件驱动做通知与补充动作
  • 工程效率:用平台工程把模板、流程、监控、发布标准化
  • 注意防止过度设计:只在“需要”时引入新的机制,并持续复盘成本

总结

这些架构趋势有一个共同目标:边界清晰、职责清晰、变更可控。

它们并不是要你一次做完所有,而是帮你用更小的风险、更清晰的结构,让系统从“可用”走向“可维护”。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐