核心是高层模块定义接口、低层模块实现,实现依赖倒置。支付模块定义OrderQuery接口并声明findById方法,订单模块实现该接口且不引入支付模块类,参数返回值须抽象通用,禁用三方SDK类型和渠道特定命名。

核心是让模块之间不直接耦合,而是通过各自定义的接口来通信。接口由调用方(高层模块)来定义,实现由被调用方(低层模块)来提供——不是反过来。
接口必须由调用方定义
比如支付服务要查订单,它不该依赖订单模块里的 OrderService 类。而是支付模块自己定义一个 OrderQuery 接口,只声明“根据 ID 查订单”这个动作:
- 接口放在支付模块包下,如
com.example.pay.infra.OrderQuery - 方法签名用领域概念:
Optional<Order> findById(OrderId id),不用OrderEntity或OrderDTO - 禁止出现三方类型,比如
ResponseEntity<Order>或Result<Order>也尽量避免,除非是本模块统一封装的 Result
实现类由被调用方提供并注入
订单模块收到这个接口后,写一个 OrderQueryImpl 实现它,并通过 @Component 或手动注册进 Spring 容器:
- 订单模块的代码里不能 import 支付模块的任何类,只能实现支付模块定义的接口
- 支付模块通过构造函数注入该接口:
public PaymentService(OrderQuery orderQuery) - Spring 自动把订单模块的实现类装配进来,但支付模块完全不知道它是怎么实现的
参数和返回值要足够抽象
接口方法的设计直接影响能否替换实现。关键看能不能塞进微信、支付宝、PayPal 等不同渠道:
立即学习“Java免费学习笔记(深入)”;
- 入参用通用领域对象:
ChargeRequest(含金额、币种、用户 ID),而不是AlipayTradePayRequest - 返回值用简单类型或标准封装:
Result<PaymentId>或PaymentResult,不返回AlipayTradePayResponse - 如果必须透传原始字段,用
Map<String, Object>或自定义泛型容器,避开 SDK 类型绑定
避免常见反模式
看似用了接口,实际没倒置,这类写法很常见:
- 在支付模块接口里写
pay(AlipayOrderDTO dto)—— 这等于把支付宝细节强加给所有实现 - 订单模块定义了
OrderService,支付模块直接@Autowired它 —— 接口是别人定的,你只能被动适配 - 所有实现都 throw 同一个自定义异常,但异常类放在公共 module 里 —— 这反而制造了新耦合
- 接口方法命名带渠道名,如
wechatRefund()—— 违背“聚焦业务动作”原则


















