引言:为什么你的应用程序不仅仅是它的数据库
\n\n想象一下你在建造一栋房子。地基、墙壁、屋顶——一切都经过精心设计。但一年后,你决定更换电线。这时你发现电线被浇筑在混凝土中,插座被焊接在钢筋上。要更换一个开关,你得拆掉半栋房子。
\n\n这大致就是经典Java应用程序的样子,其中业务逻辑与框架、数据库或REST控制器紧密交织在一起。你想从PostgreSQL迁移到MongoDB?想把控制器从Spring MVC重写为JAX-RS?欢迎来到重构的地狱。
\n\n解决方案是——六边形架构(也称为“端口与适配器”,或Alistair Cockburn诠释的“整洁架构”)。这不是银弹,但却是让你的应用程序变得灵活、可测试且独立于外部世界的一种方法。
\n\n今天,我们将通过真实的代码示例来剖析Java中的六边形架构,没有空话和抽象讨论。我们开始吧。
\n\n什么是六边形架构(端口与适配器)?
\n\n核心思想很简单:你的应用程序是一个包含业务逻辑的核心(core)。核心周围是一个“六边形”(实际上可以是任意数量的边),外部世界通过它与核心进行通信。
\n\n每条边都有自己的端口(接口)。而适配器则是针对特定技术的端口实现。例如:
\n- \n
- 端口“UserRepository”——适配器“JpaUserRepository”或“MongoUserRepository” \n
- 端口“SendNotification”——适配器“EmailSender”或“SmsSender” \n
主要规则是:核心对外部库、数据库、Web框架一无所知。它只知道自己的端口(接口)。这带来了几个优势:
\n- \n
- 独立于基础设施——更改数据库或框架时,无需触及业务逻辑。 \n
- 可测试性——无需启动Spring上下文或数据库即可测试业务逻辑。 \n
- 可读性——核心代码讲述领域的故事,而不是技术细节。 \n
项目结构:如何组织代码
\n\n没有唯一正确的包结构,但我建议基于DDD(领域驱动设计)和六边形原则的经过验证的方案:
\n\ncom.example.app\n├── domain\n│ ├── model // 实体、值对象\n│ ├── port // 端口接口(入站/出站)\n│ └── service // 业务服务(用例)\n├── adapter\n│ ├── inbound\n│ │ ├── rest // REST控制器(入站适配器)\n│ │ └── messaging // Kafka/RabbitMQ监听器\n│ └── outbound\n│ ├── persistence // JPA、JDBC、MongoDB仓库\n│ └── client // HTTP客户端、外部API\n└── config // Spring配置(如果使用)\n\n\n请注意:domain不导入adapter中的任何内容。这一点至关重要。如果你在domain中看到@Entity或@RestController,说明架构已被破坏。
示例:订单管理系统
\n\n让我们设计一个简单的系统:用户可以创建订单,系统检查库存中的商品可用性并发送通知。
\n\n步骤1. 领域模型(domain/model)
\n\n// Order.java\npublic class Order {\n private final OrderId id;\n private final List<OrderItem> items;\n private OrderStatus status;\n\n public Order(List<OrderItem> items) {\n this.id = new OrderId(UUID.randomUUID().toString());\n this.items = Collections.unmodifiableList(items);\n this.status = OrderStatus.PENDING;\n }\n\n public void confirm() {\n if (status != OrderStatus.PENDING) {\n throw new IllegalStateException("Only PENDING orders can be confirmed");\n }\n this.status = Ord