端口适配器案例

wen java案例 2

本文目录导读:

端口适配器案例

  1. 案例背景:电商订单系统
  2. 核心角色定义
  3. 代码实现(Java 伪代码)
  4. 核心优势(结合案例解析)
  5. 延伸:与“六边形架构”的映射

端口适配器模式(Port-Adapter Pattern),也常被称为六边形架构洋葱架构,是领域驱动设计(DDD)中非常经典的一种架构模式,它的核心思想是将业务逻辑(内部)与外部技术细节(数据库、消息队列、API等)解耦

下面通过一个电商订单系统的案例,逐步展示端口适配器模式的落地。


案例背景:电商订单系统

假设我们要开发一个订单服务,需要实现以下功能:

  1. 创建订单(业务逻辑)
  2. 保存订单(需要数据库)
  3. 发送订单确认通知(需要邮件/短信服务)
  4. 处理支付回调(外部系统调用)

传统做法:业务逻辑直接调用数据库的 OrderRepository 实现类或直接调用 sendEmail() 方法,这种方式会导致业务逻辑与具体技术(MySQL、阿里云短信)深度耦合,替换成本高。

端口适配器做法:我们在业务层之间定义接口(端口),让外部技术去实现接口(适配器)


核心角色定义

在编码前,我们先明确四个核心角色:

角色 说明 在本案例中的体现
核心领域(Core) 纯业务逻辑,不依赖任何框架或技术 OrderService
端口(Port) 内部的接口(抽象),定义“需要做什么” OrderRepository(持久化端口)、NotificationPort(通知端口)
适配器(Adapter) 外部的具体实现,实现了端口 MySqlOrderAdapterSmsNotificationAdapter
外部系统 适配器对接的实际技术 MySQL数据库、阿里云短信网关

代码实现(Java 伪代码)

核心领域层(内部,不依赖外部框架)

// 领域模型(实体)
public class Order {
    private Long id;
    private String productName;
    private OrderStatus status;
    // ... getters & setters
}
// ---------- 定义端口(接口) ----------
// 持久化端口:定义“怎么存”,但不关心存到MySQL还是Redis
public interface OrderRepositoryPort {
    void save(Order order);
    Order findById(Long id);
}
// 通知端口:定义“怎么发通知”,但不关心用短信还是邮件
public interface NotificationPort {
    void sendOrderConfirmation(String phoneNumber, String content);
}
// ---------- 核心业务服务(纯Java) ----------
public class OrderService {
    // 只依赖接口(端口),不依赖具体实现(适配器)
    private final OrderRepositoryPort orderRepository;
    private final NotificationPort notificationPort;
    // 通过构造函数注入(依赖倒置)
    public OrderService(OrderRepositoryPort orderRepository, NotificationPort notificationPort) {
        this.orderRepository = orderRepository;
        this.notificationPort = notificationPort;
    }
    // 核心业务逻辑:创建订单
    public Order createOrder(String productName, long userId, String userPhone) {
        // 1. 业务校验(不涉及技术)
        if (productName == null || productName.isEmpty()) {
            throw new IllegalArgumentException("商品名称不能为空");
        }
        // 2. 构建订单实体
        Order order = new Order();
        order.setProductName(productName);
        order.setStatus(OrderStatus.PENDING);
        // 3. 调用端口:保存(具体怎么存,由外部适配器决定)
        orderRepository.save(order);
        // 4. 调用端口:发送通知(具体怎么发,由外部适配器决定)
        notificationPort.sendOrderConfirmation(userPhone, "您的订单已创建:" + productName);
        return order;
    }
}

外部适配器层(外层,依赖具体技术)

这一层就是“适配器”,负责将技术细节适配到业务端口。

// ---------- 适配器 1:MySQL持久化实现 ----------
// 假设使用了 Spring Data JPA 或 MyBatis
public class MySqlOrderRepositoryAdapter implements OrderRepositoryPort {
    private final JdbcTemplate jdbcTemplate; // 假设使用JDBC
    @Override
    public void save(Order order) {
        // 具体的SQL语句,这里不需要业务关心
        jdbcTemplate.update("INSERT INTO orders (product_name, status) VALUES (?, ?)",
                order.getProductName(), order.getStatus());
    }
    @Override
    public Order findById(Long id) {
        // 查询并组装对象
        return jdbcTemplate.queryForObject(...);
    }
}
// ---------- 适配器 2:短信通知实现 ----------
public class SmsNotificationAdapter implements NotificationPort {
    @Override
    public void sendOrderConfirmation(String phoneNumber, String content) {
        // 调用阿里云短信 SDK
        AliyunSmsClient.send(phoneNumber, content);
    }
}
// ---------- 如果需要换成邮件,只需新增一个适配器,不需要改业务代码 ----------
public class EmailNotificationAdapter implements NotificationPort {
    @Override
    public void sendOrderConfirmation(String email, String content) {
        // 调用 JavaMail API 发送邮件
    }
}

装配(组合根)

在系统启动时(如Spring的 @Configuration 或手动 new),将适配器注入端口。

// 使用依赖注入容器(如Spring)进行装配
@Configuration
public class AppConfig {
    @Bean
    public OrderService orderService(OrderRepositoryPort orderRepository, NotificationPort notification) {
        return new OrderService(orderRepository, notification);
    }
    @Bean
    public OrderRepositoryPort orderRepository(DataSource dataSource) {
        return new MySqlOrderRepositoryAdapter(dataSource);
    }
    // 如果将来想用邮件,只需要改这里返回的Bean,OrderService完全不用动
    @Bean
    public NotificationPort notificationPort() {
        return new SmsNotificationAdapter();
    }
}

核心优势(结合案例解析)

业务逻辑的“绝对隔离”

OrderService 中没有任何 import java.sql.*import com.alibaba.sms.* 的代码,它只知道“先存一下,再通知一声”,具体怎么存、怎么发,外包给端口。

替换技术成本极低

  • 场景A:数据库从 MySQL 换成 PostgreSQL。
    • 只需新增一个 PostgresOrderRepositoryAdapter 实现端口,修改 @Bean 注入即可,业务代码一行不改
  • 场景B:通知从短信换成邮件。
    • 新增 EmailNotificationAdapter,修改配置即可。

便于单元测试

在测试 OrderService 时,我们可以直接注入假的适配器(Mock),不需要启动数据库和短信服务:

public class OrderServiceTest {
    @Test
    public void testCreateOrder() {
        // 使用 Mockito 模拟端口
        OrderRepositoryPort mockRepo = mock(OrderRepositoryPort.class);
        NotificationPort mockNotif = mock(NotificationPort.class);
        OrderService service = new OrderService(mockRepo, mockNotif);
        // 调用业务,验证只调用了两个端口
        service.createOrder("iPhone", 1L, "13800138000");
        verify(mockRepo).save(any(Order.class));
        verify(mockNotif).sendOrderConfirmation(eq("13800138000"), anyString());
    }
}

延伸:与“六边形架构”的映射

如果你听过“六边形架构”,这个案例完全可以映射过去:

            [外部:短信服务] -- 适配器 --> (通知端口 <---+      +---> 适配器 --> [MySQL数据库])
                                                          |      |
[外部:HTTP请求] -- 控制器适配器 --> (命令端口 ------ [核心业务] ------ 查询端口) -- 适配器 --> [缓存服务]
                                                          |      |
            [外部:消息队列] -- 适配器 --> (事件端口 <-------+      +---> 适配器 --> [外部API])
  • 左侧(请求进来):HTTP控制器、消息队列监听器都属于“驱动适配器”,它们将外部请求转换为内部命令。
  • 右侧(依赖出去):MySQL、短信、Redis都属于“被驱动适配器”,它们实现内部定义的端口。
  • 中间是纯Java业务,像一个“心脏”,不感知外部世界的存在。

端口适配器案例的关键在于:

  1. “端口”是内部的接口,是业务的诉求(我要存数据,我要发短信)。
  2. “适配器”是外部的插件,是技术的实现(用MySQL存,用阿里云发)。
  3. 依赖关系永远指向内部(外层适配器依赖内层端口,内层业务不依赖外层技术)。

实际开发中,如果你发现“换数据库要改业务代码”“短信服务商变了要改业务代码”,那就是缺少端口适配器设计,这个模式非常适合业务逻辑复杂、长期演进、需要保持核心代码纯净的中大型系统。

抱歉,评论功能暂时关闭!