前言
feign调用方式是微服务调用最为广泛的使用方式,feign接口声明位置也是比较关键的一环。目前来说,feign的3种接口声明方式各自存在利弊,并不存在最优解决方案,只能根据需求去选择。本文中不作详细项目搭建过程,但并非无码之谈,阅读前请认真阅读项目说明。
项目说明
案例项目中有两个项目,分别是用户(user-service-nacos)与订单(order-service-nacos)。用户是服务提供者,订单是消费者。
user-service-nacos中只有一个接口findById,返回一串随机数
@RestController
public class UserController {
@GetMapping("/user/{id}")
public String findById(@PathVariable("id") String id) {
Random r = new Random();
return "user/id: " + id + r.nextInt();
}
}
order-service-nacos有一个接口会去调用user-service-nacos的findById
@RestController
public class OrderController {
@Autowired
private UserClient userClient;
@GetMapping("/order/{id}")
public String findById(Long id) {
String byId = userClient.findById("9");
return "userClient return value : " + byId;
}
}
代码很简单,我们要探讨的问题就是上面的UserClient
的feign接口声明放置的位置的用法与利弊
方式一:消费者中单独声明
在order-service-nacos中单独声明一个UserClient接口
@FeignClient("user-service")
public interface UserClient {
@GetMapping("/user/{id}")
String findById(@PathVariable("id") String id);
}
调用结果:
优劣分析
使用在消费者单独声明的方式,写法简单,代码耦合度低,可用于不处于同一个工程下的应用。但存在问题也是很明显,需要维护两个地方一模一样的代码,订单服务中的UserClient的声明其实和UserController声明的接口是一模一样的,而且必须要一模一样的,这就造成了代码冗余和维护的不便。
方式二
基于方式一的探讨,我们很容易想到,一模一样的代码,而且冗余,那解决方案很简单呀,抽方法,定义一个公共接口,让服务者与消费者都继承接口。
定义一个公共的接口:UserAPI
public interface UserAPI {
@GetMapping("/user/{id}")
String findById(@PathVariable("id") String id);
}
订单服务中,UserClient直接继承了这个接口,方法不用手动编写
@FeignClient("user-service")
public interface UserClient extends UserAPI {
}
用户服务中,UserController去实现接口中的方法
@RestController
public class UserController implements UserAPI {
@GetMapping("/user/{id}")
@Override
public String findById(@PathVariable("id") String id) {
Random r = new Random();
return "user/id: " + id + r.nextInt();
}
}
优劣分析
这样的做法有效避免了方式一中的缺陷,但由于提供者与消费者共用同一个接口,造成了紧耦合关系,如果UserAPI接口发生变动,提供者与消费者两端都受到影响,也无法作用于隔离开的两个工程项目。而且spring mvc继承的方式无法继承参数映射的注解,如上面的@PathVariable
。尽管这种方式有缺点,但是也是遵循了面向契约的编程思想,在企业中有广泛的使用。
提出问题
假设有很多个微服务都要调用用户服务,那岂不是要在各个服务中都定义一个UserClient?如果在同一个父级pom文件下,你会想到将UserClient抽出来作为一个单独的module,这就是方式三的基本思想。
方式三:抽取独立模块
将FeignClient抽取为独立模块,并且把接口相关的实体类(pojo)、默认的Feign配置都放到这个模块中,提供给所有消费者使用。
创建一个单独module,引入feign依赖,将UserClient复制到此处。我这里的案例比较简单,没有pojo,如果说接口返回的是一个对象,那么这个对象应该声明在下面pojo包中(也可以是dto)。其它地方使用时,需要导入该模块,注意配置好springboot的包路径扫描。
通常来说,feign-api模块最好是放置在服务提供者工程中,方便管理。若提供者与消费者属于同一个工程,则可直接引入pom文件。否则,则需要执行maven的package命令将feign-api打成jar包才能被消费者使用。
优劣分析
这种方式有效降低耦合度,并减少重复开发的工作,但也存在一定问题,假设定义的feign-api module中有大量的接口类,消费者只需要其中一两个接口,但是导入了比较多的类,存在冗余。相对而言,这种方式是feign的最佳实践方式。