天工开物 #2 Spring Boot + Next.js 端口唯一代码分离方案

上周开发报表的时候实现了一个 Spring Boot[1] + Next.js[2] 前后端代码分离,但是端口唯一的 WebApp 方案。这个方案既能利用好各自的生态,也能避免要起两个 server 占用两个端口的繁琐构建部署逻辑。

本文从选型到实现做一个分享,主要面向熟悉 Spring Boot 技术栈,又想跟进 React 系前端组件化方案的开发者。

选型对比

基本的目标是要做一个数据展示的网页应用。应用后端负责采集和分析数据,提供 API 接口获取特定报表需要的数据。应用前端需要利用各种 UI 组件库和 Chart 绘图库把从后端取得的数据良好地展示出来。

Next.js 全栈方案

数据展示是报表系统的一个重要部分,不管后端数据处理得再好,如果没有做好数据可视化的工作,只是简单的把数据记录全部返回,决策人员很难高效的从报表中获取信息。

基于这一点,我们的方案选型需要高度重视前沿前端技术栈的兼容性。比如使用 Next.js 框架搭建报表页面,很自然地就会考虑是否可以直接用一个框架全栈解决问题。

然而,事与愿违。Node.js 的生态比起传统做数据处理的 Java/Python 生态还是非常有限,很多关键的依赖库都是缺失的。我们的主要目标是快速搞定报表系统,而不是为生态添砖加瓦。实际上,我一开始写的是 Golang 应用,后来因为要处理非结构化的 JSON 数据,Golang 没有好的 JSONPath 库,而手动定义层层嵌套的 JSON 结构体又非常折磨,所以我才转向了 Java 生态。Node.js 就更不用说了。

除了生态以外,还有我个人的问题。我接触前端开发不到一年,做数据处理这种非常结构化的后端逻辑,用静态类型带类型系统的 Java 要顺手得多。

个人熟悉程度还带来一个考量点,那就是打包的复杂性。我之所以一开始选择 Golang 实现,也是因为要搞一个 Java 项目其实还是很麻烦的,如果能快糙猛的糊完应用,Golang 还是非常舒服的。Java 的打包对于新人来说或许还很复杂,但是我已经经历过好几个大型项目嵌套模块和 Maven/Gradle 插件开发等等需求的洗礼,基本处理一个 Spring Boot 还是手到擒来。JavaScript 全栈我遇到的第一个问题就是经常需要判断自己的环境是在浏览器还是在 Node.js 上,这两个环境需要的依赖库和能做的事情的假设是不一样的。第二个问题是基于 webpack 的打包体验,我只能说懂得都懂。每个框架和工具都有自己的一套配置逻辑,一个项目配置逻辑的复杂度几乎是依赖项的乘积,重重叠加的。第三个问题是数据分析应用的开发非常依赖类型系统,而有 JavaScript 的基础在,哪怕使用 TypeScript 来开发,各种开发工具支持还是不够。

Spring Boot 全栈方案

另外一个非常常见的方案是基于 Spring Boot 的方案。如果去掉全栈的限制,我估计 Java 生态当中开发应用和微服务的程序员,超过半数都是基于 Spring Boot 来开发的。

不过,大部分用 Spring Boot 开发的应用或微服务,只是对外提供 API 接口,另有前端开发团队来设计开发页面,调用这些 API 接口获取数据渲染页面。然而我们现在是要一个人搞定整个报表系统,出于康威定律,很自然地会考虑 Spring Boot 的全栈方案。

Spring Boot 的全栈方案其实已经很成熟了。这条技术线往上追溯,能追溯到 JSP/ASP 年代。也就是说,后端应用处理服务逻辑,加上一个模板引擎读取和 HTML 文件格式差不多又有点不一样的文件,通过扩展的方便后端填充数据的语法,在收到请求,生成好数据以后,把数据填入到模板当中,最终生成一个 HTML 页面返回给浏览器。Golang 自带的 web 方案也是这个套路,Python 生态的 Flask + Jinja2 也是这个套路。可以说,这是从后端走向前端的全栈的经典思路。

不过,这个思路会面临一个致命的问题:前端生态目前的前沿技术,依靠 Vue.js 和 React.js 的发展已经生长出一套独立的组件化开发方案,这套方案跟传统模板引擎要整合起来非常的困难。换句话说,现在已经不是用 Spring Boot 框架 + Thymeleaf 模板引擎,再从 CDN 上捞一下 Bootstrap 的 .min.css 文件和 .min.js 文件就能搞好的年代了。

要想真正用上 Vue.js 或者 React.js 的生产力,一套前端的脚手架是必不可少的。虽然最终方案其实也是一个 Spring Boot 应用加上编译出来的静态前端网页,某种意义上也可以说是 Spring Boot 的全栈方案,但是跟以往搞搞模板,或者搞搞模板加上几个手动挡的 CSS 文件、JS 脚本,还是有很大的差别。

前后端分离双进程方案

既然传统的模板 + Spring Boot 应用后端的方式不好走,那么是不是可以考虑就像组织内有团队分工那样,把前后端两个项目分成两个进程,做一个前后端分离的方案呢?

实话说,这个方案对于真的有团队分工,前后端团队是各自领域专家,同时还有人专门负责搞定部署和服务连通性的情况来说,是最佳的解耦方案。然而,做这个报表的只有我一个人,最多加上一两个搭把手的好兄弟。真的去做前后端分离,起两个进程的方案,部署起来其实还是相对麻烦的。

你要给 Node.js 的应用绑一个端口,然后再给 Spring Boot 应用绑一个端口,配置一下前端全栈的应用在访问 API 接口的时候转发到 Spring Boot 应用的端口。另外 Node.js 既然是一个全栈应用,比如你用 Next.js 来开发,那么其实就容易把后端逻辑割裂在两个应用之间,因为前端其实不完全是前端,它有一部分逻辑还是跑在服务端的,这部分前端的内容其实是全栈。从控制复杂性的角度出发,同一个团队或者说同一个人用这样的技术栈还是引入了太多知识负担。

最终方案

最终我选择的方案,如同标题和开头所描述的,是一个基于 Spring Boot + Next.js 的,代码分离但是端口唯一的方案。

具体一点说,我会用 Java 生态搞定后端数据处理的工作,基于 Spring Boot 框架完成一个 Web 应用;再仅使用 Next.js 框架前端部分的功能,利用现代化的组件化开发方案做出页面展示逻辑以后,编译出页面对应的一系列静态文件,再由 Spring Boot Web 应用把编译产生的文件作为静态资源提供给浏览器访问。

这样,Next.js 框架开发的页面不包括任何依赖 Node.js 的逻辑,自然也就不需要起一个 Node.js 进程和占用一个额外的端口来暴露服务。这些页面要想拉取数据,只需直接从 /api/xxx 路径访问同一个端口上由 Spring Boot Web 应用定义的 Controller 组件暴露出来的 API 端口,就可以实现了。

详细的实现方案请看下一节。

方案实现

完整方案实现已发布到 GitHub 平台,可以访问 tisonkun/springboot-nextjs-demo[3] 仓库阅读。

Step 1. 初始化 Spring Boot 项目

本文假设读者熟悉 Spring Boot 技术栈,这一部分就不做过多过程介绍。典型的初始化 Spring Boot 项目的方法包括:

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值