疯狂餐厅
86.88M · 2026-03-21
OpenFeign 的实现归属:调用方(消费者)定义是主流和推荐做法。
在 Spring Cloud 官方文档和绝大多数教程中,OpenFeign 客户端接口(@FeignClient)由调用方实现。服务提供方只需要提供标准的 REST Controller 接口(用 @RestController + Spring MVC 注解),调用方根据提供方的 API 文档(OpenAPI/Swagger)在自己项目中定义 Feign 接口。
示例(调用方代码):
@FeignClient(name = "user-service", path = "/api/users")
public interface UserFeignClient {
@GetMapping("/{id}")
UserResponse getUser(@PathVariable("id") Long id);
}
提供方不需要也不应该在自己的模块里写 @FeignClient(除非是测试桩)。
很多企业项目(尤其是 Java 同构微服务)会采用提供方维护公共 API 模块的模式:
xxx-api 模块,里面放:
maven/gradle 依赖这个 api 模块。
优点:避免代码重复、接口一致性强。这种做法在 V2EX 等社区讨论中被认为是“TOB 产品化项目”的实用折中方案,但不是最优解。
DDD 强调每个微服务是一个限界上下文(Bounded Context),上下文之间必须保持模型独立(不能共享领域对象)。直接依赖提供方的 DTO 或 Feign 接口会“腐蚀”本上下文的领域模型。
核心原则:
调用方微服务
├── domain/ # 领域层(纯业务模型,不依赖任何外部 DTO)
├── application/ # 应用层
├── infrastructure/
│ └── acl/ # 防腐层(重点!)
│ ├── feign/ # Feign 接口定义
│ ├── adapter/ # 适配器(Feign → Domain Model 转换)
│ └── translator/ # 翻译器(DTO 转换)
└── interfaces/ # 对外暴露
Feign 接口(ACL 内):
@FeignClient(name = "order-service")
public interface OrderFeignClient {
@PostMapping("/orders")
ExternalOrderResponse createOrder(ExternalCreateOrderRequest req);
}
注意:这里用的是外部模型(ExternalXXX),不是本领域模型。
适配器/翻译器(核心解耦):
@Component
public class OrderAclAdapter {
private final OrderFeignClient feignClient;
public Order createOrder(CreateOrderCommand cmd) {
ExternalCreateOrderRequest req = translator.toExternal(cmd);
ExternalOrderResponse resp = feignClient.createOrder(req);
return translator.toDomain(resp); // 翻译成领域对象
}
}
领域层完全隔离:领域服务只看到 Order(本上下文领域对象),不知道外部服务长什么样。
不推荐:提供方直接提供 Feign 接口给调用方(违反 DDD 模型独立性)。
| 场景 | Feign 接口归属 | 推荐度(DDD 项目) | 优缺点 |
|---|---|---|---|
| 普通 Spring Cloud 项目 | 调用方定义(最常见) | 简单,但多调用方重复 | |
| 企业同构项目 | 提供方提供 api 模块 | 方便但耦合高 | |
| 严格 DDD 项目 | 调用方 ACL 内定义 | 解耦最彻底,符合领域模型独立 |
额外建议:
在 DDD 项目中,坚决把 OpenFeign 放在调用方的防腐层,这是保持领域模型纯净、系统长期可演化的关键实践。很多阿里/华为的 DDD 落地案例都是这么做的。