服务端接口设计模式(一)
服务端接口设计模式(二)
服务端接口设计模式(三)
服务端接口设计模式(四)

1、常见的服务端接口设计模式是采用restful形式对外提供服务,我以用户登录、获取个人信息等接口作为示例,这里只展示主要的示例代码:

@RequestMapping("/user")
@RestController
public class UserController {

    @Autowired
    private UserService userService;

    /**
     * 登录
     * @return
     */
    @PostMapping("/login")
    public Result login(@RequestParam String username,
                        @RequestParam String password){
        //校验参数
        //验证登录信息
        //返回登录结果
        return null;
    }

    /**
     * 获取个人信息
     * @param userId
     * @return
     */
    @GetMapping("/getProfile")
    public Result<User> getProfile(@RequestParam String userId){
        //查询用户数据
        //返回查询结果
        return null;
    }
}

这种模式运行起来也挺好用,如果接口需要身份验证,可以在filter中对token(采用token验证的做法比较常见)进行有效性验证,对于不需要验证的接口,也可以在filter中进行url的排除,如果有网关,这些可以在网关中处理,这一切运行起来也都没有问题,我工作中有些项目也是这么做的。

今天我大家分享一下另外的一种设计方式,也是我在项目开发中总结出来的一些设计经验,相比restful风格的接口形式,自认为有一些优点,仅供与大家交流。下图是我画的一个简单的UML图,从图中可以看到,接口的入口仍然是一个Controller,它只有一个doRequest方法。它依赖一个服务工厂类BusiServiceFactory,它负责从Spring容器中获取具体的接口实现实例,然后执行业务。

2、我以用户登录接口为例:
接口请求的报文定义,报文分为公共参数和业务参数,我将data中的参数定义业务参数,data外层的参数定义为公共参数,目前公共参数定义了serviceName和token。

{
 #公共参数
  "serviceName": "userLogin",    #接口名称
  "token": "",   #认证的token
 #业务参数
  "data": {
    "username": "admin",  #用户名
    "password": "123456"  #密码
  }
}

3、与之对应的,我定义了一个RequestMessage的参数对象,便于在程序中传递参数,并且也可以根据需要扩充参数,如请求的终端设备类型、版本号等等,就不一一列出,下面是RequestMessage的定义,之所以data参数是JSONObject类型,也是方便在后面的程序中使用。RequestMessage定义如下:

@Setter
@Getter
public class RequestMessage {

   /**
    * 接口名称,也是每个接口实现中bean的名称
    */
   private String serviceName;

   /**
    * 认证信息
    */
   private String token;

   /**
    * 请求者IP
    */
   private String remoteIp;

   /**
    * 具体业务参数
    */
   private JSONObject data;
}

4、再来看一下请求入口的MainController类型的实现,在doRequest方法中,将请求的json报文转换成了RequestMessage 对象,然后通过BusiServiceFactory 获取具体的接口实现bean,最后处理业务并将结果返回。具体代码如下:

@RequestMapping("/api/service")
@Controller
public class MainController {

   @Autowired
   private BusiServiceFactory busiServiceFactory;

   /**
    * 请求入口
    * @param request
    * @return
    */
   @PostMapping("/v1")
   @ResponseBody
   public String doRequest(HttpServletRequest request){
      Result result = Result.successResult();
      try {
         // 读取请求内容
         String reqBody = IOUtils.toString(request.getInputStream(), StandardCharsets.UTF_8);
         JSONObject reqJson = JSON.parseObject(reqBody);
         //转换成RequestMessage
         RequestMessage reqMessage = JSON.toJavaObject(reqJson, RequestMessage.class);
         //设置请求者的IP地址,此处仅做示例
         reqMessage.setRemoteIp(request.getRemoteAddr());
         //根据接口名称获取具体的接口实现bean,最后处理业务将结果返回
         BusinessService businessService = busiServiceFactory.getBusiService(reqMessage.getServiceName());
         result = businessService.doHandle(reqMessage);

      } catch (IOException e) {
         e.printStackTrace();
         result = Result.errorResult();
      }
      return JSON.toJSONString(result);
   }
}

BusiServiceFactory 的实现相当简单,它实现了ApplicationContextAware接口,获取到了Spring容器的上下文,用于从容器中根据Bean名称取得Bean。

@Component
public class BusiServiceFactory implements ApplicationContextAware {

   private ApplicationContext applicationContext;

   @Override
   public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
      this.applicationContext = applicationContext;
   }

   /**
    * 从spring上下文中获取接口实现的bean
    * @param beanName
    * @return
    */
   public BusinessService getBusiService(String beanName){
      return this.applicationContext.getBean(beanName, BusinessService.class);
   }
}

当然,MainController也可以实现ApplicationContextAware接口,不用单独写一个BusiServiceFactory来实现,这也是可以的。但从设计的角度来讲,Controller中并不关心这个实现到底从哪来,它只需要知道接口可调用即可。本例中这个bean是在Spring容器中,如果接口实现使用SPI的方式来配置,只需要改变一下BusiServiceFactory 的实现方式即可。这也是一种可扩展的原则,即在设计的时候要充分考虑其它场景,给扩展留有余地。

作者原创,转载请注明出处。

发表评论