如何让JavaScript更优雅地访问Spring Boot REST API

如何让JavaScript更优雅地访问Spring Boot REST API

技术杂谈小彩虹2021-07-10 5:12:2940A+A-

如花是前端开发同学,精通各种框架和JS工具链,雷卷是Java程序猿,对Spring Boot比较熟悉而已,他们经常配合做一些项目开发。一天

产品经理
要求开发一个新的Social项目,准备试水一下, 项目之旅由此开始啦。

前后端分离

如花主要负责前端,如界面、交换、REST

API
调用等,雷卷主要负责后端,数据库、Redis、输出REST API等,分工非常明确。 这没有什么难的,访问 start.aliyun.com/ 创建项目,然后编写对应的Controller,输出REST API, 第一个Controller不到5分钟就出炉啦,如下:

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

    @GetMapping("/nick/{id}")
    public Mono<String> findNickById(@PathVariable("id") Integer id) {
        return Mono.just("nick: " + 1);
    }
}

Copy

雷卷钉钉给如花,告诉该REST API如何调用。 如花也很迅速,创建一个Node项目,然后webpack等都设置好,他选择了axios这个package,告诉雷卷这个JS的http client多么好,什么浏览器和Node自适配,支持Promise,而且API简洁,一个demo,确实非常简单。

const axios = require('axios').default;

(async () => {
    let userId = 1;
    let nick = await axios.get('/user/nick/' + userId)
            .then(function (response) {
                return response.data;
            })
    console.log(nick);
})()
Copy

OpenAPI 3来啦

没过几天,REST API已经有十几个啦,钉钉发来发去,已经不行啦。 经常URL要调整、参数要变更、返回的JSON对象字段也在调整,钉钉上说不明白,而且如果有其他同学加入怎么办? 没有文档说明。
他们决定使用OpenAPI 3规范,只要有该规范,如花就不用钉钉雷卷,只要参考标准OpenAPI文档就可以啦。 这个也不麻烦,当然不会手写这个规范的json,有springdoc-openapi项目,可以直接输出OpenAPI规范文件,非常easy。 pom.xml中添加一下依赖,如下:

<dependency>
            <groupId>org.springdoc</groupId>
            <artifactId>springdoc-openapi-webflux-ui</artifactId>
            <version>1.3.0</version>
</dependency>
Copy

然后对应的Controller方法上添加一下@Operation进行说明,就可以啦。

    @GetMapping("/nick/{id}")
    @Operation(description = "find nick by id")
    public Mono<String> findNickById(@PathVariable("id") Integer id) {
        return Mono.just("nick: " + 1);
    }

Copy

代码稍作调整,雷卷将OpenAPI规范地址发给如花,http://xxxx:8080/swagger-ui.html ,你自己根据看这个规范就可以啦。 界面如下:

image.png

如花中途找到雷卷,主要是描述完善、增加tag等,方便定位等。 在此过程中,如花有点受伤,API一旦调整,雷卷就会钉钉给如花,XXX API调整,你OpenAPI Console上看一下新的规范,你自己调整代码就可以啦。

OpenAPI不那么简单

API越来越多,已经有50个啦,一天如花来找到雷卷,讨论一下HTTP请求约定的问题。 如花抱怨到,现在的这些REST API,太灵活啦,一些参数要放到URL的path中,一些参数要在query中,还有一些是放在http header中,GET和POST不停的在切换,一些API返回的是text/plain,一些返回的是json,虽然OpenAPI上也多明确说明啦,如花还觉得自己非常多的时间都花在构建axios请求的config对象上啦,而且代码非常不好看,到处都是这些config对象的构建,而且这些参数和返回值类型,都没法做代码提示,一次不小心把参数名写错啦,花费了一个上午来调试才找到。 ”你钉钉上说一句API调整啦,我要花半天看哪些调整啦,又不能代码diff,

IDE
又不能提示,我眼睛都累死啦。” 如花有一堆的抱怨。

雷卷告诉如花, 你根据OpenAPI规范生成Node.js代码不就可以啦,json文件就在那里,自己搞呗。 你知道的,我也没法统一,token必须在header中,考虑URL友好型,一些变量放在URL的Path中还是有道理的,还有一些安全考量,没有办法,同时还有性能要求,Servlet Filter规范等,这就是HTTP。

如花铁了心不想看什么OpenAPI规范,对照着写这些无聊的代码,API调整后还要去找不同,简直是拿前端不当人。和雷卷讨论一上午,即便有OpenAPI规范,要从OpenAPI json中自动生成这些固定格式代码,还是有些麻烦,需要后端配合,输出非常多的信息,才能做到代码提示等需求。而且如何配合webpack自动化这个流程也麻烦,下载最新的OpenAPI json,调用脚本生成js代码,也不方便自动化等。

让Spring Boot生成npm package

最后两个觉得能否让Spring Boot应用自动生成一个npm package,然后如花在代码调用该package提供的API就可以。package.json添加一个依赖,该依赖关联到Spring Boot应用提供的URL,如下:

 "dependencies": {
    "@UserService/UserController": "http://localhost:8080/npm/@UserService/UserController"
  }
Copy

接下来就是要定服务接口啦。既然JavaController已经提供了API,为何不复用该API作为Service API,反正不能让如花再构建什么http请求,写那么多axios无聊代码。 最终结论如下:

  • JavaScript的代码应该这样:
const userController = require("@UserService/UserController");

(async () => {
    let nick = await userController.findNickById(1);
    console.log(nick);
})()
Copy
  • TypeScript的代码应该这样:
import userController from "@UserService/UserController"

userController.findUserById(1).then(user => {
    console.log(user.nick)
});
Copy

基于接口通讯,你就是想查找会员nick,那么findNickById(id) 这个是最好的API,鬼才想知道背后使用什么技术实现的,以后URL调整,参数调整,http还是https,哪怕是HTTP/2,都不能让我改什么代码。

Spring Boot生成npm package背后的技术

技术上完全没有问题,而且相当靠谱。 对于REST Controller,我们已经使用@GetMapping, @PostMapping, @PathVariable, @RequestParam,@RequestBody等,这些元信息已经存在啦,我们只需要找到对应的Controller Spring Bean,然后使用反射机制将这些信息转换为JS Code代码就可以啦。

一些技术实现点:

  • npm package的核心文件三个: package.json、index.js和index.d.ts,package.json不用说,index.js主要是根据controller生成的js代码,而index.d.ts是给TypeScript用的。
  • package是tar.gz格式的,package.json和index.js生成后,调用commons-compress输出tgz格式
  • index.js要整合axios,核心一个JS Class, Java Controller Method到 JS class的method,最后是融合axios的一些代码。
  • 处理JSDoc,要能自动生成对应的tags,方便进行代码提示,同时IDE也能进行验证和纠错。
  • 一些缺失的信息,调用OpenAPI的annotion补全,如@Operation, @ApiResponse 等。

详细代码实现,如何从Java是生成JS,类型如何匹配等,这里不细说,这里有一个问题要澄清一下,对于生成的代码,主要是强烈流程正确,而不太讲究代码的可阅读性,这些生成的代码都是给机器阅读的,如你看protobuf生成的代码,没有人能读懂。 但是我会竭力将代码可阅读性提供,毕竟还有一些调试问题。

让我们看一下最终的效果。

最终效果

  • 代码提示完美啦,函数提示和参数提示都没有问题

image.png

  • 返回值提示也支持,主要是 JSDoc的@typedef 在起作用

image.png

  • 函数中参数对象的属性值也能提示,想犯错都不那么容易

image.png

通过Spring Boot生成npm package和基于服务接口调用,将所有的底层细节全部屏蔽。 如花不用再去看那个OpenAPI控制台,直接调用接口就可以, 也不问这个参数是path变量,还是请求变量,还是在http header中的name,这些全部不用考虑,至于是GET还是POST,更不要考虑。 所以的这些细节都从后端的Controller直接生成即可。

当然特性还不止这些,包括OpenAPI的annotation整合,JS函数的optional,default value支持,@Deprecated API废弃等,当然TypeScrip支持也是第一位的。总结一下,调用更简单,而且全面的审查和代码提示。

虽然项目还是提供OpenAPI的swagger-ui.html,但是如花基本不怎么去看啦,代码编写这么简单,看那个Swagger UI干什么,虽然swagger控制台可以做REST API测试,但是怎么比得过jest来的简单和快捷。

test('findNickById', async () => {
    let nick = await userController.findNickById(1);
    console.log(nick);
});
Copy

考虑到一些同学可能不知道这个HTTP URL地址,这些如花也想好啦,提交到npm repository就可以,生成的package.json中version是基于时间点的,我们测试完成后,命令行自动提交一下就可以啦,其他同学想用,就是标准的package name就可以。

总结

最后说明一下, 这个项目的灵感来自于Vaadin两天前的blog,不少同学都在使用TypeScript编写浏览器端应用,那么TypeScrip如何后后端通讯?

最好的方式当然是import后直接API调用,而不是如何封装XMLHttpRequest,提供更便捷的API,axios已经提供非常好的API啦。 基于接口的代码如下:

import * as helloEndpoint from './generated/HelloEndpoint'
async makeCall() {
  const greeting = await helloEndpoint.sayHello('Vaadin');
  console.log(greeting);
}
Copy

Java服务端的代码如下:

@Endpoint
public class HelloEndpoint {
  public String sayHello(String name) {
    return "Hello " + name;
  }
}
Copy

这种基于接口方式写代码非常简洁,完全是无脑的那种。 Vaadin能做到这点,主要就是在编译过程中,如HelloEndpoint生成对应的npm package,然后提供给TS调用。

Spring Boot npm package generator是在运行期生成,node开发同学不用安装JDK,执行Maven编译,然后将npm package拷贝过来等,现在只需一个依赖声明就可以。

借鉴于这个思想,RSocket的npm package也是自动生成。如果我是前端同学,我只想调用一下findNickById(id)实现功能,至于底层通讯是REST API, gRPC还是RSocket,管我什么事情。 即便再好的http client封装,也没有接口约定来的最直接。

当然如果是Python,Ruby等语言,可以使用同样的机制,完全为开发者屏蔽各种http client, gRPC client, RSocket Client等。

最后代码在这里: gitlab.alibaba-inc.com/leijuan/npm… 欢迎大家提供宝贵意见。


点击这里复制本文地址 以上内容由权冠洲的博客整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!

支持Ctrl+Enter提交

联系我们| 本站介绍| 留言建议 | 交换友链 | 域名展示
本站资源来自互联网收集,仅供用于学习和交流,请遵循相关法律法规,本站一切资源不代表本站立场,如有侵权、后门、不妥请联系本站删除

权冠洲的博客 © All Rights Reserved.  Copyright quanguanzhou.top All Rights Reserved
苏公网安备 32030302000848号   苏ICP备20033101号-1

联系我们