一、架构的本质是管理依赖
很多人把架构等同于技术选型,其实架构的第一性问题只有一个:谁依赖谁。
一个健康的 Java 系统,依赖方向应该从外向内:
- 外层:Web、消息、数据库、缓存;
- 内层:应用服务、领域模型;
- 核心:业务规则和领域知识。
内层不应该知道外层的存在。领域模型不应该 import Spring、MyBatis 或 Kafka。这样,当框架升级、数据库更换、协议调整时,业务核心不受影响。
稳定依赖原则、稳定抽象原则,这些听起来抽象,落地就是一句话:让容易变的东西依赖不容易变的东西,而不是反过来。
二、从分层到六边形
传统三层架构 Controller-Service-DAO 简单直接,但容易滑向“贫血模型 + 事务脚本”:业务逻辑散落在 Service,领域对象只剩 getter/setter,数据库表结构驱动代码结构。
六边形架构(端口与适配器)提供了另一种思路:领域在中心,外部通过端口接入。
public interface OrderRepository {
Optional<Order> findById(OrderId id);
void save(Order order);
}
这是端口。它属于领域层,只表达业务需要什么,不关心数据库是 MySQL 还是 MongoDB。
@Repository
public class JpaOrderRepository implements OrderRepository {
private final JpaOrderDao dao;
public Optional<Order> findById(OrderId id) {
return dao.findById(id.value()).map(OrderMapper::toDomain);
}
public void save(Order order) {
dao.save(OrderMapper.toEntity(order));
}
}
这是适配器。它属于基础设施层,负责把 JPA 的细节翻译成领域语言。
好处很直接:领域层可以独立测试;换持久化技术只需新增适配器;业务规则不会被框架注解污染。
三、模块化:单体不是原罪
微服务流行之后,很多团队把“单体”当成贬义词。但一个边界清晰、模块化良好的单体,往往比一堆边界混乱的微服务更容易维护。
在 Java 中,模块化可以分三个层次:
- 包结构:按限界上下文划分顶层包,而不是按技术层划分;
- 构建模块:Maven/Gradle 多模块,用依赖关系强制边界;
- 运行时模块:JPMS 或 Spring Modulith,控制可见性。
模块之间通过事件解耦,而不是互相调用内部服务。例如订单模块发布 OrderPlaced,库存模块监听并扣减。发布者不需要知道订阅者是谁。
架构边界不能只靠自觉,最好写进测试。ArchUnit 可以用几行代码守住规则:
@AnalyzeClasses(packages = "com.example")
class ArchitectureTest {
@ArchTest
static final ArchRule domain_should_not_depend_on_infra =
noClasses().that().resideInAPackage("..domain..")
.should().dependOnClassesThat().resideInAPackage("..infra..");
}
这条规则进入 CI 后,任何违规依赖都会导致构建失败。架构从“口头约定”变成“可执行约束”。
四、微服务不是默认答案
微服务解决的是组织问题,不是技术问题。拆之前先问四个问题:
- 团队是否已经大到需要独立部署?
- 不同模块是否需要独立伸缩?
- 数据边界是否清晰?
- 是否愿意承担网络延迟、分布式事务、链路追踪、运维复杂度?
如果答案是否定的,模块化单体通常是更优起点。康威定律告诉我们,系统结构会映射组织沟通结构。强行拆成微服务,只会把代码耦合变成服务耦合。
一个务实的路径是:先模块化单体,再按压力拆分。 当某个模块确实需要独立团队、独立发布、独立扩容时,再把它抽出去。绞杀者模式、分支抽象,都是降低迁移风险的手段。
五、事件驱动与数据一致性
Java 架构中,跨模块、跨服务的一致性无法靠本地事务解决。常见方案是领域事件 + 最终一致性。
核心模式有三个:
- Outbox:业务数据和事件写入同一本地事务,再由后台任务投递,避免双写不一致;
- Saga:用一系列本地事务和补偿动作替代分布式事务;
- 幂等消费者:每个消费者都能安全重放消息,用业务键去重。
不要用 XA 或分布式事务硬扛高并发场景。大多数业务可以接受秒级、分钟级的最终一致,换取更高的可用性和更低的复杂度。
六、可观测性与性能
架构做得再漂亮,线上出问题无法定位就是灾难。Java 系统的可观测性建议统一到三个维度:
- 日志:结构化 JSON,带 Trace ID;
- 指标:Micrometer 采集,Prometheus 存储;
- 追踪:OpenTelemetry 贯穿 HTTP、RPC、DB、消息。
JVM 层面,GC 选择、堆大小、线程模型都会影响架构。Java 21 的虚拟线程让阻塞式编程重新变得有吸引力,但它不会让数据库变快,也不会消除下游瓶颈。架构上仍要关注连接池、背压、超时和隔离。
性能不是事后优化,而是架构决策:同步还是异步、批处理还是流式、缓存放在哪一层、是否允许降级。
七、演进式架构
好的 Java 架构不是一次设计出来的,而是持续演进的。几个实践值得坚持:
- ADR:记录每个重要架构决策的背景、选项和后果;
- 架构测试:用 ArchUnit 等工具守住依赖规则;
- 小步重构:不要一次重写,用绞杀者模式逐步替换;
- 技术债可见:把债务变成待办事项,而不是口头抱怨。
总结
Java 架构的竞争力,不在于堆了多少框架,而在于:
边界清晰、依赖可控、变化被隔离、演进可持续。
六边形架构帮你分离领域与基础设施,模块化帮你控制单体复杂度,事件驱动帮你解耦一致性,可观测性帮你守住线上稳定。少量代码就能守住边界,但真正的功夫,在于每一次架构决策背后的取舍。