前后端分离:分离开发,一体发布

前后端分离开发实践了很久了,前两天需要把一个项目上线,准备 SSL 证书时发现,居然需要申请 3 个证书(1 年期免费的证书只能对单个子域名申请),这 3 个域名是:
  • sys.project.cn,Web 应用前端
  • api.project.cn,应用后端 RESTful API
  • m.project.cn,移动端

屁大点个项目,需要搞得这么复杂?用子路径不行吗?就像这样:

  • //sys.project.cn/
  • //sys.project.cn/api/
  • //sys.project.cn/m

回答我说,不行!因为是前后端分离方式开发的,每一项都是单独的项目,单独发布出来,只能分别作为网站发布……我那感觉,就像喝了两瓶二锅头,不仅醉了,而且上头,就差发酒疯了!

且不说是不是一定要分离发布的问题,就算各自独立发布,至少有三种方案可以发布成子路径:

  • 虚拟目录或纯静态的子目录
  • IIS 站点上“添加应用程序”
  • 使用 Nginx 反向代理

但这不是重点,重点是:

分离开发就一定得分离发布吗

自从应用前后端分离开发模式以来,分工明确,合作愉快。最近后端一般是用 NET Core 开发,前端使用的 Vue 技术栈。如果偶尔后端人力不足,有点数据库基础的前端工程师还可以拿 Node.js 帮着实现一部分后端需求。移动端的 Android 团队已经精简得只剩下一个人了,只需要维护一个框架应用,处理点硬件调用,把移动端页面往里一套就能解决问题。

各团队已经习惯了分离开发,除了讨论接口设计,其他时候团队间交流最多的可能就是:“API 测试地址是啥,我需要联调一下。”所以各团队也习惯了自己发布,公布地址给其他团队测试、联调。久而久之,居然形成了分离开发就得分离发布的印象。

对于较大型一些的项目来说,分离发布可能是必要的:纯静态的前端部分可以发布到 CDN,后端部分可以发布到多个服务器上外加一层负载均衡。但是对于使用人数不过几百人,并发最多几十人的小型应用来说,就一台应用服务器,把所有东西揉吧揉吧,放一起就能当作一体式开发的 Web 应用发布出来,真没必要去分离。

就上面的例子来说,除了应用后端需要跑程序,需要 NET Core Runtime,另外两项全是纯静态。然而,

A: 另外两项不是静态的,因为要通过构建生成!
B: 什么构建?
A: Vue 框架写的,需要通过 npm run build 构建了才能发布。
B: 那么,构建结果是不是纯静态的?
A: 构建结果应该不是纯静态的吧,需要在 IIS 上建站点发布。
B: 那么,构建结果直接用浏览器可以打开吗?不用 IIS,只用静态 http-server 可以部署吗?
A: 好像可以
B: 那就是纯静态!

这是一个插曲,不过这得强调一下,“构建”这一过程的结果,不一定就非得是动态的 Web 应用。我们已经在前端工程化上实践了这么久,应该了解:前端工程化之后,构建的结果是静态的,不需要在服务器上跑程序,只需要服务器按 URL 提供静态资源。

分析下后端应用的发布内容

现在来看一下后端发布的结果(部分)

.../api_publish
  |-- wwwroot/
  `-- *.dll

程序中,所有 API 都是通过路由中间件解析 URL 之后转发到各 Controller 的。假如发布后绑定了域名 api.project.cn,那么:

  • //api.project.cn/ 打开的是项目模板提供的一个默认页面,这个页面在 wwwroot 中 —— 对了,wwwroot 就是这个 Web 应用的静态资源目录
  • //api.project.cn/api/... 这个子路径下提供全套 Web API 服务

因为应用后端目前只提供 Web API 服务,wwwroot 里只不过放了一些没用的静态资源 —— 都是创建项目时模板提供的静态资源,完全可以删得一个不剩。wwwroot 中的内容删干净之后,访问 http://api.project.cn/ 会得到一个 404,但没关系,因为 API 完好!

那么,如果把 wwwroot 里放上前端构建的结果呢?

看看前端项目结构

.../project_root
  |-- src/    <-- 源文件
  |-- dist/   <-- 构建结构(发布目录)
  |   |-- index.html       <-- 入口页面
  |   |-- assets/          <-- 资源(图片等)
  |   `-- *.js;*.js.map    <-- 构建出来的 js 脚本等
  |-- node_modules/        <-- npm 包缓存
  `-- *       <-- 项目配置、说明等

这个结构中,dist 目录是 npm run build 构建出来的,这是一个发布目录,只有 dist 中的内容需要部署到 Web 服务器上。

揉一下子

现在把 dist 目录放在后端发布目录 api_publish 中去,改名为 wwwroot,替换掉原来的 wwwroot,Api 的发布目录就变成了这样:

.../publish
  |-- wwwroot/    <-- 前端构建结果:dist
  |   |-- index.html
  |   |-- assets/
  |   `-- *.js;*.js.map
  `-- *.dll

这个目录在 IIS 里部署出来,直接访问 //api.project.cn/(之前绑定的域名),我们会毫无悬念地看到前端页面出来了。由于前端页面中 Ajax 调用的 Base URL 都是 //api.project.cn/,所以 API 调用也没有问题。

不过是 Web 应用的主页一般不会通过 //api.project.cn/ 来访问,所以绑定 sys.project.cn 域名来访问。//sys.project.cn/ 没有问题,可以打开页面。之前绑定的 api.project.cn 并未取消,所以 Ajax 调用也没有问题。

注:从 //sys.project.cn/ 通过 Ajax 调用 //api.project.cn/ 可能会存在跨域问题,不过在这个案例中,跨域问题早就处理过了,不细说。

申请 SSL 证书

接下来,开始申请 SSL 证书。如果只申请一个证书(收费证书很贵的),是该申请 api.project.cn 的,还是 sys.project.cn 的?

不管 api.project.cn 还是 sys.project.cn 都可能在接收到的请求中包含敏感信息,也可能在响应中包含敏感数据。别的不说,Ajax 调用就已经涉及到了两个部分的信息交换,任何一方不安全,整体都是不安全的。

但是只有一个证书,就得放弃一个域名,放弃哪一个比较好?

sys.project.cn 是应用入口,应该告知用户,而 api.project.cn 是在页面中隐含调用的,所以应该放弃 api.project.cn。而放弃 api.project.cn,就意味着需要把 Web API 部署为 sys.project.cn 的子路径中,即 //sys.project.cn/api/。然后把前端 Ajax 调用的 Base URL 改为 //sys.project.cn/api/ 即可。

这不,//sys.project.cn///sys.project.cn/api 就把前后端揉合在一起了,搞成一体式发布。

再来个移动端

对了,还有一个针对移动端的前端静态资源需要发布,它和发布应用前端原理一样,但是得发布到 .../wwwroot/m/。问题是,需要以虚拟目录的形式发布吗?

其实这个问题不是难题,纯静态的东西,怎么揉都行。

一体式发布

如果,三端不是同时发布,而是各有各的生命周期,那最好发布成三个目录:

  • .../publish/api/,部署为 IIS 站点(删除掉其中的 wwwroot 目录)
  • .../publish/sys/,做成符号链接(Windows 下用 Junction)到 .../publish/api/wwwroot
  • .../publish/mobile/,可以直接在 IIS 中部署成 /m 虚拟目录,也可以 sys 那样做成一个符号链接

对于多数小项目来说,三端都是同时发布、联合测试的。这种情况下,就可以使用一个构建脚本将前端 dist 、移动端 dist 和 Web API 发布目录拷贝到一起,按如下结构发布:

.../publish/
  |-- *.dll
  `-- wwwroot/          <-- 前端构建结果:dist
      |-- index.html
      |-- assets/
      |-- *.js;*.js.map
      `-- m/            <-- 移动端构建结果:dist
          |-- index.html
          |-- assets/
          `-- *.js;*.js.map

分离式开发

一体式发布说完了,再回过头来说说分离式开发。

因为一开始的问题出现在部署的时候,所以我们反推了一体式发布的过程。但实际上,应该反过来,按正常的顺序,从项目开始开发的时候来规划。

项目开始开发,说明它的需求已经确定下来。那么,就基本上能确定用户该怎么来使用它,它应该怎样部署。所以创建项目工作区的时候会想到这样一个目录结构:

.../project
  |-- wwwroot/    <-- 前端静态资源
  |    `-- m/     <-- 移动端静态资源
  |-- **/*.cs     <-- 源代码
  `-- *           <-- 项目及其他各种配置文件等

然后进行分工计划:

  • 前端一组写 wwwroot,但要把 wwwroot/m 这个目录保留给二组
  • 前端二组写 wwwroot/m
  • 后端组写 project

如果直接在整个工作区中协作复杂度会比较高,而且前端工程师看到后端代码会头痛,后端工程师看到前端代码也头痛。所以拆分项目,同时决定构建方法:

  • wwwroot 创建一个前端项目 web,构建输出到 wwwroot
  • wwwroot/m 创建另一个前端项目 mobile,构建输出到 wwwroot/m
  • project 从源文件中删除 wwwroot,不关心前端过程
  • 构建方法:按如下顺序整体构建
    • 构建 project,并整体发布到 .../publish/
    • 构建 web,得到 .../web/dist,将其拷贝到 .../project/wwwroot/
    • 构建 mobile,得到 .../mobile/dist,拷贝到 .../project/wwwroot/m/

然后各组领任务,项目技术负责人开始创建项目文件,编写开发规范……


喜欢此文,点个赞 ?

支持作者,赏个咖啡豆 ?