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. 一条数字链路连接的端口无法UP及后续相应故障的排除
  2. 很多人很想知道怎么扫一扫二维码就能打开网站,就能添加联系人,就能链接wifi,今天做个demo(续集)...
  3. JIT编译器杂谈#1:JIT编译器的血缘(一)
  4. PYQT中QThread输出到textBrowser
  5. leetcode93. 复原 IP 地址
  6. Linux下SSH Session复制
  7. 为什么Java中只有值传递
  8. 比特币区块链将分道扬镳、Libra苦难继续,2020区块链进入关键时期!
  9. php笔试完就让我回去了,昨晚hr给了我一个面试题,说过了就安排我面试
  10. Android 资源(resource)学习小结
  11. javascript与php数据交换之json
  12. Atitit 远程存储与协议 mtp ptp rndis midi nfs smb webdav ftp Atitit mtp ptp rndis midi协议的不同区别 1. PTP: 图
  13. ableton 中文_Ableton live 中文 PDF.pdf
  14. donet 微服务开发 学习-AOP框架基础
  15. 网关 路由器 防火墙
  16. MySQL 生成指定长度的随机字符串
  17. 发现自己很失败。。。
  18. Bluetooth协议学习
  19. nekohtml解析html(string或是文件流)
  20. 51单片机通过WIFI模块ESP8266控制LED灯

热门文章

  1. html不兼容的原因,浏览器不兼容的原因和解决方法
  2. 1001: 植树问题 Java 某学校植树节开展植树活动,已知树苗有m株,参加植树的同学有n人(且mn),请问每位同学平均可以植树几株?还有几株剩余?
  3. pynq 环境搭建_PYNQ系列学习(一): Pynq开发环境配置
  4. 全媒体运营师胡耀文教你:如何提升线上活动参与度?分享5个技巧
  5. 本机与虚拟机WIN7之间互传文件
  6. 动态水印跟踪去除_视频动态水印如何去除 视频中不定时出现的图片加文字广告如何尽量模糊处理...
  7. 无言的结局......
  8. 计算机网络 | 网络层 | 什么是网络层 | 详解
  9. 销售谈判技巧培训分享
  10. js百度地图获取当前定位经纬度及省市区