SpringCloud多个微服务场景下轻量级feignclient架构(架构图+代码实现)

目录

      • 1 引入-多服务场景下如何配置feignclient
      • 2 项目依赖关系树,类图
      • 3 具体实施
          • 3.1 db-api
            • 1) ResultResponse
            • 2) IUserApi、IGoodsApi、IOrderApi
            • 3) ApiFactory
          • 3.2 projdb
            • 1)UserApi
            • 2)GoodsApi
            • 3)OrderApi
          • 3.3 proj1/proj2/proj3

上篇博客简单介绍了feignclient基本知识和使用方法,这篇博客用来介绍feignclient进阶使用。

1 引入-多服务场景下如何配置feignclient

上篇博客介绍的是单个工程简单使用FeignClient的例子,如果在多工程情况下,每个工程都需要增删改查多张表呢?比如现在有多个业务微服务工程proj1proj2proj3……都需要增删改查db库的多张表userordergood……(假设此处proj,proj2,proj3……都有其他业务职责,不直接连接数据库)。

最简单的做法就是每个工程都声明一个自己的feign客户端,然后在客户端里面定义好对db库多张表的增删改查请求方法,另外有一个连接db库的工程projdb专门有一个controller来响应这些方法。此时可能会有一个问题,为什么proj1,proj2……不直接依赖projdb,然后将其一起打包进jar包运行呢?这是因为使用feign客户端会比直接依赖projdb更轻量级projdb要访问数据库,必然包括mybatis-xml文件或其他访问数据库代码,这种访问数据库的细节对于proj1、proj2来说是需要屏蔽的,因此通常不会直接让微服务工程依赖数据库工程。

然而,上述方法会存在一些问题:

  1. 每个工程重复声明了feignclient,代码冗余过高;
  2. 每个工程只定义一个feignclient,对库里表的访问杂糅在一起,方法过多,不便于管理;
  3. projdb工程使用一个controller响应,存在与2相同的问题;
  4. 没有较好的异常处理,为了避免在访问数据库时出现差错抛出异常导致程序终止,需要在使用feign客户端发送请求的地方使用try-catch处理或者在FeignClient注解里定义fallback属性。

针对这些问题,可以考虑如下改进:

  1. 将feignclient相关代码独立为一个工程db-api,其他微服务proj1、proj2、proj3……依赖db-api
  2. db-api里面定义多个feign客户端,每个客户端对应一张表的增删改查请求;
  3. projdb同样使用多个controller响应请求,每个controller对应一张表。为了使得请求不被遗漏,projdb里的controller可以实现feign客户端接口,这样可以保持方法一一对应,且可以省略@RequestMapping及其子注解,因为请求的映射路径会被继承下来;
  4. 对controller返回的数据统一化处理,定义类似于响应前端的类ResultResponse作为返回值,它包括responseCoderesponseMsgdata。异常捕获放在controller中,出现异常则返回code为errorCodeResultResponse,通过code值来判断响应是否正常;

进行到这一步,似乎上述问题都已解决,只需要在要请求的地方调用feignclient接口方法即可,但是若要访问的表数量随着项目发展不断扩大,再在代码里注入客户端Bean,使用各种接口方法去发送请求并从返回体中取数据会较难维护,代码没有那么清晰明了,因此可以提供一个统一的入口,通过这个入口来实现对任一客户端的调用,这个入口即静态工厂,通过静态工厂实现按需直接发送请求(无需注入Bean),静态工厂处理ResultResponse并判断responseCode以及取出data返回,这样能够对业务工程屏蔽feign客户端细节,让业务代码能够直接取到所需数据。因此,最后一步改进为:

  1. db-api工程提供一个静态工厂ApiFactory,工厂里包含所有定义的feignclient,通过静态方法来实现对某一客户端某一方法的调用并获得返回结果,比如业务工程通过ApiFactory.getUserById("1")这一行代码就能获得一个id为1的User对象。

2 项目依赖关系树,类图

按照上述思路梳理一下现有工程及相互之间的依赖关系:

  • feign客户端工程:db-api,定义多个feign接口,提供静态工厂访问入口、通用响应类ResultResponse
  • 业务工程:proj1、proj2、proj3……使用db-api静态工厂访问数据库数据,依赖db-api
  • 数据库响应工程:projdb,controller实现feign接口,依赖db-api;
    maven依赖树结构为:
//业务工程
proj1
   ├── (依赖 db-api)
proj2
   ├── (依赖 db-api)
proj3
   └── (依赖 db-api)

//数据库响应工程
projdb
   └── (依赖 db-api)

//feign客户端工程
db-api

三个模块之间的类图关系如下所示,另有两个大箭头标注工程依赖关系和请求响应关系。

SpringCloud多个微服务场景下轻量级feignclient架构(架构图+代码实现)_第1张图片

业务工程proj1、2、3通过db-api工程的ApiFactory类的静态方法实现对数据库的访问和修改。

静态工厂ApiFactory包含所有feign客户端Api作为静态成员变量(定义成静态是因为静态方法只能访问静态成员变量),同时定义包含了调用Feignclient接口方法的各种静态方法,每个方法都会对feign客户端返回值ResultResponse进行处理,若responseCode正常则取出其包含的data并返回,否则返回空或抛出异常。

projdb是真实访问数据库的工程,db-api工程的Feign客户端Api请求类和projdb的controller响应类一一对应,为实现关系。Api发送的请求将由controller同名方法进行响应。controller将对访问数据库的结果进行封装,返回ResultResponse

3 具体实施

假设上述工程在注册中心的注册名为:

proj1/proj2/proj3: biz-module
db-api: db-api-module
projdb: db-module

下面给出具体代码实施。

3.1 db-api
1) ResultResponse

ResultResponse类的responseCoderesponseMsg可以用枚举类表示,先定义枚举ResponseCode

public enum ResponseCode {
    SUCCESS("200", "成功"),
    FAILURE("500", "失败");

    private final String code; 
    private final String message; 

    ResponseCode(String code, String message) {
        this.code = code;
        this.message = message;
    }

    public String getCode() {
        return code;
    }

    public String getMessage() {
        return message;
    }
}

ResultResponse类:

public class ResultResponse<T> {

    private String responseCode; 
    private String responseMsg; 
    private T data;

    public ResultResponse() {
    }

    public ResultResponse(String responseCode, String responseMsg, T data) {
        this.responseCode = responseCode;
        this.responseMsg = responseMsg;
        this.data = data;
    }

    public static <T> ResultResponse<T> success(T data) {
        return new ResultResponse<>(ResponseCode.SUCCESS.getCode(), ResponseCode.SUCCESS.getMessage(), data);
    }

    public static <T> ResultResponse<T> fail(String message) {
        return new ResultResponse<>(ResponseCode.FAILURE.getCode(), message, null);
    }
	// 自定义其他返回方法……
}

2) IUserApi、IGoodsApi、IOrderApi

Feignclient接口类/客户端Api请求类IUserApiIGoodsApiIOrderApi

@FeignClient(contextId = "IUserApi", name = "db-module")
public interface IUserApi {

    @GetMapping("/users/{id}")
    ResultResponse<User> getUserById(@PathVariable("id") int id);

    @PostMapping("/users")
    ResultResponse<Integer> insert(@RequestBody User user);
}

@FeignClient(contextId = "IGoodsApi", name = "db-module")
public interface IGoodsApi {

    @GetMapping("/goods/{id}")
    ResultResponse<Goods> getGoodsById(@PathVariable("id") int id);

    @DeleteMapping("/goods/{id}")
    ResultResponse<Integer> delete(@PathVariable("id") int id);
}

@FeignClient(contextId = "IOrderApi", name = "db-module")
public interface IOrderApi {

    @GetMapping("/orders/{id}")
    ResultResponse<Order> getOrderById(@PathVariable("id") int id);

    @PutMapping("/orders")
    ResultResponse<Integer> update(@RequestBody Order order);
}

3) ApiFactory

ApiFactory作为使用feignclient的入口,提供所有调用feign服务的静态方法。

@Component
public class ApiFactory {
	
	// 静态成员变量
    private static IUserApi userApi;
    private static IGoodsApi goodsApi;
    private static IOrderApi orderApi;

    @Autowired
    public ApiFactory(IUserApi userApi, IGoodsApi goodsApi, IOrderApi orderApi) {
        ApiFactory.userApi = userApi;
        ApiFactory.goodsApi = goodsApi;
        ApiFactory.orderApi = orderApi;
    }

    // 用户相关 API 调用
    public static User getUserById(int id) {
        try {
            ResultResponse<User> response = userApi.getUserById(id);
            return handleResponse(response);
        } catch (Exception e) {
            // 处理异常(可以记录日志或抛出自定义异常)
            throw new RuntimeException("获取用户失败: " + e.getMessage(), e);
        }
    }

    public static Integer insertUser(User user) {
        try {
            ResultResponse<Integer> response = userApi.insert(user);
            return handleResponse(response);
        } catch (Exception e) {
            throw new RuntimeException("插入用户失败: " + e.getMessage(), e);
        }
    }

    // 商品相关 API 调用
    public static Goods getGoodsById(int id) {
        try {
            ResultResponse<Goods> response = goodsApi.getGoodsById(id);
            return handleResponse(response);
        } catch (Exception e) {
            throw new RuntimeException("获取商品失败: " + e.getMessage(), e);
        }
    }

    public static Integer deleteGoods(int id) {
        try {
            ResultResponse<Integer> response = goodsApi.delete(id);
            return handleResponse(response);
        } catch (Exception e) {
            throw new RuntimeException("删除商品失败: " + e.getMessage(), e);
        }
    }

    // 订单相关 API 调用
    public static Order getOrderById(int id) {
        try {
            ResultResponse<Order> response = orderApi.getOrderById(id);
            return handleResponse(response);
        } catch (Exception e) {
            throw new RuntimeException("获取订单失败: " + e.getMessage(), e);
        }
    }

    public static Integer updateOrder(Order order) {
        try {
            ResultResponse<Integer> response = orderApi.update(order);
            return handleResponse(response);
        } catch (Exception e) {
            throw new RuntimeException("更新订单失败: " + e.getMessage(), e);
        }
    }

    // 处理 API 响应
    private static <T> T handleResponse(ResultResponse<T> response) {
        if (ResponseCode.SUCCESS.getCode().equals(response.getResponseCode())) {
            return response.getData(); // 返回正常的数据
        } else {
            throw new RuntimeException(response.getResponseMsg()); // 抛出异常,可自定义异常
        }
    }
}

上面的代码是使用构造函数注入相关成员变量,也可以使用@PostConstruct,这样的话需要先声明普通成员变量并自动注入:

@Component
public class ApiFactory {

    private static IUserApi userApi; 
    private static IGoodsApi goodsApi; 
    private static IOrderApi orderApi; 

    @Autowired
    private IUserApi userApiInstance; 
    @Autowired
    private IGoodsApi goodsApiInstance; 
    @Autowired
    private IOrderApi orderApiInstance; 

    @PostConstruct
    public void init() {
        userApi = userApiInstance;
        goodsApi = goodsApiInstance;
        orderApi = orderApiInstance;
    }
3.2 projdb

该工程响应feignclient的controller均实现于feignclient接口,这样可以省略映射地址,保证方法一一对应。代码流程类似,统一使用ResultResponse作为返回值。

1)UserApi
@RestController
public class UserApi implements IUserApi {

    @Autowired
    private UserMapper userMapper;

    @Override
    public ResultResponse<User> getUserById(@PathVariable int id) {
        try {
            User user = userMapper.selectById(id);
            return new ResultResponse<>(ResponseCode.SUCCESS.getCode(), "获取用户成功", user);
        } catch (Exception e) {
            return new ResultResponse<>(ResponseCode.FAIL.getCode(), "获取用户失败: " + e.getMessage(), null);
        }
    }

    @Override
    public ResultResponse<Integer> insert(@RequestBody User user) {
        try {
            int result = userMapper.insert(user);
            return new ResultResponse<>(ResponseCode.SUCCESS.getCode(), "插入用户成功", result);
        } catch (Exception e) {
            return new ResultResponse<>(ResponseCode.FAIL.getCode(), "插入用户失败: " + e.getMessage(), null);
        }
    }
}

2)GoodsApi
@RestController
public class GoodsApi implements IGoodsApi {

    @Autowired
    private GoodsMapper goodsMapper;

    @Override
    public ResultResponse<Good> getGoodById(@PathVariable int id) {
        try {
            Goods goods = goodsMapper.selectById(id);
            return new ResultResponse<>(ResponseCode.SUCCESS.getCode(), "获取商品成功", good);
        } catch (Exception e) {
            return new ResultResponse<>(ResponseCode.FAIL.getCode(), "获取商品失败: " + e.getMessage(), null);
        }
    }

    @Override
    public ResultResponse<Integer> delete(@PathVariable int id) {
        try {
            int result = goodsMapper.delete(id);
            return new ResultResponse<>(ResponseCode.SUCCESS.getCode(), "删除商品成功", result);
        } catch (Exception e) {
            return new ResultResponse<>(ResponseCode.FAIL.getCode(), "删除商品失败: " + e.getMessage(), null);
        }
    }
}

3)OrderApi
@RestController
public class OrderApi implements IOrderApi {

    @Autowired
    private OrderMapper orderMapper;

    @Override
    public ResultResponse<Order> getOrderById(@PathVariable int id) {
        try {
            Order order = orderMapper.selectById(id);
            return new ResultResponse<>(ResponseCode.SUCCESS.getCode(), "获取订单成功", order);
        } catch (Exception e) {
            return new ResultResponse<>(ResponseCode.FAIL.getCode(), "获取订单失败: " + e.getMessage(), null);
        }
    }

    @Override
    public ResultResponse<Integer> update(@RequestBody Order order) {
        try {
            int result = orderMapper.update(order);
            return new ResultResponse<>(ResponseCode.SUCCESS.getCode(), "更新订单成功", result);
        } catch (Exception e) {
            return new ResultResponse<>(ResponseCode.FAIL.getCode(), "更新订单失败: " + e.getMessage(), null);
        }
    }
}

3.3 proj1/proj2/proj3

所有准备工作都已完成,现在只需要使用即可,以proj3为例,如果在某个方法中要使用feignclient,则只需要在相应类中写ApiFactory.xxxx(xxx)即可,比如:


@Service
class UserService{
	public void userBiz(int id){
		// ……
		User user = ApiFactory.getUserById(id);
		// ……
	}
}

class GoodsService{
	public void goodsBiz(int id){
		// ……
		int res = ApiFactory.deleteGoodsById(id);
		// ……
	}
}

综上就是本篇博客的所有内容,这种架构使得调用feignclient的流程对于业务代码来说是相对无感知的,业务代码执行ApiFactory静态方法并获取结果的流程就好像自己直接访问了数据库一样。

你可能感兴趣的:(架构,spring,cloud,微服务,springboot,http)