Java 后端和 Typescript 前端虽然都是类型语言,但传统上这两个域上的类型之间存在脱节。本文推荐的这个工具让我们在一个地方修改一个方法或类,并立即在其他地方直接使用它,或者在我们误用它时在编译时看到错误。

这个工具捕获了如此多的错误并使开发速度如此之快,以至于我现在无法想象没有它可以工作。

我知道有一些类似的工具,但它们要么增加了大量的复杂性(OpenAPI),要么被锁定在某个堆栈中(Remix)。构建我们自己的以相对较小的成本提供了显着的控制(即它并不那么复杂)。

通常,我们有一个 Java 后端,它公开 HTTP 端点供我们的前端调用。,这个后端提供简单的CRUD创建/读取/更新/删除,有些做特定的请求,还有很多要记住的返回许多不同类型的对象。

因为我们非常依赖这些 API 调用,所以我们在工具上投入了大量精力,以使我们的开发人员尽可能无缝地进行前端/后端通信。

在我详细介绍之前,这里有一个简短的概述:

  • Java 接口描述了每个 API 及其端点
  • 每个接口都通过一个特殊的 Javalin 处理程序连接到其后端实现,该处理程序通过 HTTP 公开每个端点,解析参数,然后调用实现
  • 一些自定义工具循环遍历每个接口并为每个接口生成一个 Typescript 类,以及调用每个端点的方法
  • Java 库typescript  -generator  为 API 的参数和返回值中使用的所有类生成 Typescript 定义

最终结果是,当我们向后端添加端点时,我们的 API 客户端会自动生成调用它的方法,从而使前端到后端的调用几乎与本机调用一样简单。

继续阅读以了解其工作原理,或查看 Github 上的  演示 。

API 定义

这是简单的部分。我们的 API 接口如下所示:

<b>interface</b> UserApi {UserDto getUser(<b>int</b> userId);
}

我们 API 的后端实现

Javalin 是一个出色的网络服务器,它提供了我们所需的功能与简单性之间的平衡。后端调用可能如下所示:

POST /api/UsersApi/getUser
{'userId': 1001
}

因此,我们为每个调用创建一个处理程序。它涉及一些反射,这有点毛茸茸,但让开发人员的事情变得更容易:

<b>public</b> <b>static</b> <b>void</b> main(){<b>var</b> app = Javalin.create();<font><i>// UsersApi is the interface that defines the endpoints.</i></font><font></font><font><i>// UsersService is the backend implementation of UsersApi.</i></font><font></font><font><i>// We repeat the below for every API we want to expose.</i></font><font>expose(app,UsersApi.<b>class</b>, <b>new</b> UsersService())app.start()
}<b>private</b> <T> <b>void</b> expose(Javalin app, Class<T> api, T implementation) {String apiName = api.getSimpleName();<b>for</b> (Method method : api.getMethods()) {</font><font><i>// handle calls to, for example, POST /api/UsersAPI/getUser</i></font><font>app.post(</font><font>"/api/"</font><font> + apiName + </font><font>"/"</font><font> + method.getName(), (ctx) -> {Map<String, String> body = ctx.bodyAsClass(Map.<b>class</b>);List<Object> args = <b>new</b> ArrayList<>();<b>for</b> (Parameter param : method.getParameters()) {String json = body.get(param.getName());<b>var</b> arg = GSON.fromJson(json, param.getParameterizedType());args.add(arg);}<b>try</b> {Object result = method.invoke(implementation, args.toArray());String json = objectMapper.writeValueAsBytes(result);ctx.result(json);} <b>catch</b> (Exception e) {<b>throw</b> <b>new</b> RuntimeException(</font><font>"Failed to invoke "</font><font> + apiName + </font><font>"/"</font><font> + method, e);}});}
}
</font>

差不多就是这样。对于每个 API,然后是每个方法,公开一个对给定参数进行反序列化的端点,然后使用这些参数调用实际实现的方法。

我在这里要注意的唯一特别之处是,我们的请求主体不是我们可以立即反序列化的单个对象,而是最好将其视为 JSON 字符串的参数名称的键值对。所以它本质上是双重序列化的 JSON。

所以!我们的后端已准备好接收请求。接下来是 API 客户端。

Typescript客户端

这里的代码和上面的代码有点相似——给定一个像UsersAPI这样的接口,迭代它的方法,并迭代它的参数。但是,在此过程中,我们通过向字符串附加一些 Typescript 来构建字符串。这里的代码有点难看,所以我要写一些伪代码来描述它:

String toTypescript(Class... api) {<b>for</b> each api:typescript += <font>"class ${api.getSimpleName()} {"</font><font><b>for</b> each method:typescript += </font><font>"${method.getName()}("</font><font><b>for</b> each parameter:typescript += </font><font>"${parameter.getName()}: ${getType(parameter)}, "</font><font>typescript += </font><font>"): Promise<${getType(method.returnType)}"</font><font>><b>var</b> body = Map<String, String>typescript += </font><font>"return fetch('/api/${api}/${method}', {"</font><font>typescript += </font><font>"   method: 'POST',"</font><font>typescript += </font><font>"   headers: {'Content-Type': 'application/json', 'Accept': 'application/json'},"</font><font>typescript += </font><font>"   body: JSON.stringify({"</font><font><b>for</b> each parameter:typescript += </font><font>"${parameter.getName()}: JSON.stringify(${parameter.getName}), typescript += </font><font>"   }"</font><font>typescript += </font><font>"}).then(res => res.json())typescript += </font><font>"}"</font><font><b>return</b> typescript
}
</font>

嗯……这样更易读吗?如果您愿意,可以改为阅读  实际代码 。

以下是您可能希望在生成的 Typescript 中看到的内容,例如:

<b>class</b> UsersAPI {getUser(userId: number): UserDto {<b>return</b> fetch('/api/UsersApi/getUser', {method: 'POST',headers: {'Content-Type': 'application/json', 'Accept': 'application/json'},body: JSON.stringify({userId: JSON.stringify(userId)})}).then(res => res.json())}
}

我们将上述文件存储在target/ts/api.ts中,并使用Exec Maven插件生成该文件,该插件让我们在运行mvn package时运行客户端生成器。

我跳过的一个魔法是getType(parameter)的调用。这可以将Java类转换为Typescript的等价物。这里基本上是转换的工作原理。

  • String -> string
  • int, Integer, float, Float, double, Double, long, Long -> number
  • Object -> any
  • Array<T>, List<T>, Set<T>, Collection<T> -> Array<T>
  • Map<K, V> -> Record<K, V>
  • 否则,就使用对象的类名(如UserDto)。

现在你几乎已经准备好调用new UsersApi().getUser(1001) - 我们只是缺少返回的UserDto的Typescript类型。

为我们的Java类型提供Typescript定义

这个问题很简单。我们有一个Java包,里面有我们想在前端使用的所有类型(com.company.dtos),我们把Maven插件typescript-generator指向它。

<plugin><groupId>cz.habarta.typescript-generator</groupId><artifactId>typescript-generator-maven-plugin</artifactId><version>2.32.889</version><executions><execution><id>generate</id><goals><goal>generate</goal></goals><phase>compile</phase></execution></executions><configuration><classPatterns><classPattern>com.company.dto.**</classPattern></classPatterns><outputFile>target/ts/types.ts</outputFile></configuration>
</plugin>

假设我们有一个这样的 UserDto 类:

<b>public</b> record UserDto(<b>int</b> userId, String username) {}

我们最终会得到一个types.ts像这样的文件:

<b>interface</b> UserDto {userId: number,username: string
}

把它们放在一起

所以,现在我们有:

  • 一个 Javalin 服务器,其端点准备好反序列化我们每个后端方法的参数
  • api.ts准备好查询每个端点的类的文件
  • types.ts描述这些端点的参数和返回的类型的文件

最终结果是添加新端点如下所示:

  1. 添加void changeUsername(int userId, String newUsername)对接口的调用,并在后端实现
  2. 运行mvn package以更新我们的 Typescript 文件
  3. 在前端,写new UsersService().changeUsername(1001, "foo")- 就是这样!

注意事项

在此过程中,我们学到了一些值得注意的教训,包括:

  • JavaMap比 Javascript 对象灵活得多。特别是在 Javascript 对象只能有字符串作为键,所以不要返回 Map<MyRecord, String>
  • Javascript没有方法重载,所以如果你声明a getUser() ,getUser(int userId)你会遇到问题。

你可以在 Github 上找到完整的演示:  crummy/java-typescript-api-generator

【 malcolmcrum】基于Java后端与Typescript前端的代码自动生成相关推荐

  1. 用java写ods系统_基于数据库的代码自动生成工具,生成JavaBean、生成数据库文档、生成前后端代码等(TableGo v7.0.0版)...

    TableGo是基于数据库的代码自动生成工具,低代码编程技术的实现,可以零代码自动生成SpringBoot项目工程.生成JavaBean.生成前后端分离的CRUD代码.生成MyBaits的Mapper ...

  2. java生成iso9660工具_基于数据库的代码自动生成工具,生成JavaBean、生成数据库文档、生成前后端代码等(TableGo v7.0.0版)...

    TableGo_20210212 v7.0.0 正式版发布,此次版本更新如下: 1.新增对DB2数据库的支持 2.新增按字段生成文件,支持把字段.JSON.XML数据转换成任何代码 3.新增大量新的自 ...

  3. Java后端以及web前端及echarts框架词云分析

    运用Java后端以及web前端及echarts框架词云分析做出数据分析统计可视化展示. 以下为我的项目展示截图: 开发环境: 集成开发环境(IDE):IntelliJ IDEA 2021 服务器:To ...

  4. freemarker mysql 生成bean_基于数据库的代码自动生成工具,生成JavaBean、生成数据库文档、生成前后端代码等(v6.6.6版)...

    TableGo_20191026 v6.6.6 正式发布,此次版本更新如下: 1.新增通过自定义模板生成Word文档的功能,可以使用FreeMarker模板生成自定义格式的数据库文档. 2.新增 Sw ...

  5. 基于数据库的代码自动生成工具,生成JavaBean、生成数据库文档、生成前后端代码等(v6.9.0版)

    TableGo_20200520 v6.9.0 正式版发布,此次版本更新如下:           1.新增对JDK9及以上版本Java环境的支持           2.生成JavaBean更名为生 ...

  6. 基于数据库的代码自动生成工具,生成JavaBean、生成数据库文档、生成前后端代码等(TableGo v7.4.0版)

    TableGo_20210921 v7.4.0 正式版发布,此次版本累计更新如下:           1.新增企业或个人的简单定制版本,为企业和个人提供软实力的增值           2.新增导入 ...

  7. mysql javabean 工具_基于数据库的代码自动生成工具,生成JavaBean、生成数据库文档、生成前后端代码等(v6.9.0版)...

    TableGo_20200520 v6.9.0 正式版发布,此次版本更新如下: 1.新增对JDK9及以上版本Java环境的支持 2.生成JavaBean更名为生成数据模型并且提供了C#.C++.Gol ...

  8. 外汇汇率接口 java_基于JAVA的货币汇率api调用代码实例

    代码描述:基于JAVA的货币汇率api调用代码实例 关联数据:货币汇率 接口地址:http://www.juhe.cn/docs/api/id/23 1.[代码][Java]代码 import jav ...

  9. java 金数据推送数据_基于JAVA的黄金数据接口调用代码实例

    代码描述:基于JAVA的黄金数据接口调用代码实例 接口地址:http://www.juhe.cn/docs/api/id/29 1.[代码][Java]代码 import java.io.Buffer ...

最新文章

  1. 分布式系统的面试题11
  2. 人工智能写散文之错位时空
  3. 按钮点击WIN8 磁贴效果
  4. SAPCAR 压缩解压软件的使用方法
  5. 计蒜客 逃生+动态规划
  6. hashmap怎么取值_HashMap?面试?我是谁?我在哪
  7. python随机函数random要导入哪个包_python学习之随机函数random
  8. Python入门--文件的读写
  9. 三维重建_基于图像的三维模型重建_稠密点云重建
  10. DF1协议的官网下载
  11. java cmd 编译jar_Java程序在命令行下编译运行打Jar包
  12. PCB个性logo设计
  13. 整理: 显示面板行业英文简称解析
  14. 剑指offer算法题028:数组中出现次数超过一半的数字
  15. 电脑族的视力保护常识
  16. kali linux2021 安装pip
  17. c#实现钉钉免登功能
  18. 【面向对象】小游戏“终结者”程序的设计与实现
  19. Facebook Rebound 弹性动画库 源码分析
  20. OneNET Studio云平台STM32物联网开发(2)- STM32+Air724UG(4G模组)数据采集显示上报

热门文章

  1. github软件安装到linux,Ubuntu下Git与Github的安装使用
  2. 1296: 植树问题(C语言)
  3. 计算机房用什么气体灭火,气体灭火系统在通信机房中用量计算方法
  4. html简单的时分秒计时器,时分秒自定义倒计时计时器
  5. 【自然语言处理】【ChatGPT系列】大模型的涌现能力
  6. 运用图像处理解决基于MRI的脑肿瘤图像分割问题
  7. 前列腺增生症的治疗行业调研报告 - 市场现状分析与发展前景预测
  8. 全国各城市经纬度,代码等
  9. 关于人性的20句箴言
  10. ES5和ES6的继承有哪些优劣?