Java中的六边形架构:无痛实现纯净逻辑

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

引言:为什么你的应用程序不仅仅是它的数据库

\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
\n\n

主要规则是:核心对外部库、数据库、Web框架一无所知。它只知道自己的端口(接口)。这带来了几个优势:

\n
    \n
  • 独立于基础设施——更改数据库或框架时,无需触及业务逻辑。
  • \n
  • 可测试性——无需启动Spring上下文或数据库即可测试业务逻辑。
  • \n
  • 可读性——核心代码讲述领域的故事,而不是技术细节。
  • \n
\n\n

项目结构:如何组织代码

\n\n

没有唯一正确的包结构,但我建议基于DDD(领域驱动设计)和六边形原则的经过验证的方案:

\n\n
com.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

让我们设计一个简单的系统:用户可以创建订单,系统检查库存中的商品可用性并发送通知。

\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                

博客

书籍推荐