现代应用程序对前端开发人员提出了很高的要求。 Web 应用程序需要复杂的功能,而这些工作的大部分都落到了前端开发人员身上
- 构建现代、可访问的用户界面
- 创建交互式元素和复杂的动画
- 管理复杂应用程序状态
- 元编程:构建脚本、转译器、打包器、代码检查工具等。
- 从 REST、GraphQL 和其他 API 读取数据
- 中间层编程:代理、重定向、路由、中间件、身份验证等。
仅此列表就令人望而生畏,但如果您的技术栈没有针对简化进行优化,情况会变得非常糟糕。复杂的架构会引入隐藏的责任,从而导致风险、减慢速度并造成挫败感。
根据我们选择的架构,我们也可能无意中将服务器配置、发布管理和其他 DevOps 职责添加到前端开发人员的职责范围内。
软件架构会直接影响团队的生产力。 选择避免隐藏复杂性的工具,以帮助您的团队完成更多工作,并减少负担。
隐蔽的中间层——前端任务复杂度可能在此激增的地方
让我们来看一下我见过分配给多个前端团队的任务:创建一个简单的 REST API,将来自几个服务的数据合并到一个请求中,供前端使用。如果您对着电脑大喊:“但这并不是前端任务!”——我同意!但我又怎么会让事实妨碍积压的工作呢?
仅前端需要的 API 属于中间层编程。例如,如果前端将来自多个后端服务的数据组合起来并派生一些额外的字段,一种常见的方法是添加一个代理 API,以便前端不会发出多个 API 调用并在客户端执行大量业务逻辑。
对于哪个后端团队应该拥有这样的 API,并没有明确的界限。将其添加到另一个团队的积压工作中——并在将来进行更新——可能是一场官僚噩梦,因此前端团队最终承担了责任。
这个故事的结局取决于我们做出的架构选择。让我们看看处理此任务的两种常见方法
- 在 Node 上构建 Express 应用程序以创建 REST API
- 使用无服务器函数创建 REST API
Express + Node 带来了令人惊讶的隐藏复杂性和开销。无服务器允许前端开发人员快速部署和扩展 API,以便他们可以回到其他前端任务。
方案 1:使用 Node 和 Express(以及 Docker 和 Kubernetes)构建和部署 API
在我职业生涯的早期,标准操作程序是使用 Node 和 Express 来建立 REST API。从表面上看,这似乎相对简单。我们可以在名为server.js
的文件中创建整个 REST API
const express = require('express');
const PORT = 8080;
const HOST = '0.0.0.0';
const app = express();
app.use(express.static('site'));
// simple REST API to load movies by slug
const movies = require('./data.json');
app.get('/api/movies/:slug', (req, res) => {
const { slug } = req.params;
const movie = movies.find((m) => m.slug === slug);
res.json(movie);
});
app.listen(PORT, HOST, () => {
console.log(`app running on http://${HOST}:${PORT}`);
});
此代码与前端 JavaScript 的差别并不太大。这里有一些样板代码,如果前端开发人员以前从未见过,可能会让他们感到困惑,但还是可以管理的。
如果我们运行node server.js
,我们可以访问http://localhost:8080/api/movies/some-movie
,并看到一个包含具有 slug some-movie
的电影详细信息的 JSON 对象(假设您已在data.json
中定义了它)。
部署引入了大量额外的开销
然而,构建 API 仅仅是开始。我们需要以一种能够处理大量流量而不会崩溃的方式来部署此 API。突然之间,事情变得复杂多了。
我们需要更多工具
- 某个可以部署此 API 的地方(例如 DigitalOcean、Google Cloud Platform、AWS)
- 一个容器,用于保持本地开发环境和生产环境的一致性(即 Docker)
- 一种确保部署保持活动状态并能够处理流量峰值的方法(即 Kubernetes)
此时,我们已经完全超出了前端的范围。我之前做过这类工作,但我的解决方案是从教程或 Stack Overflow 答案中复制粘贴。
Docker 配置在某种程度上是可以理解的,但我不知道它是否安全或经过优化
FROM node:14
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 8080
CMD [ "node", "server.js" ]
接下来,我们需要弄清楚如何将 Docker 容器部署到 Kubernetes 中。为什么?我不太确定,但这是公司后端团队使用的,所以我们应该遵循最佳实践。
这需要更多配置(全部复制粘贴)。我们将命运托付给 Google,并找到了Docker 将容器部署到 Kubernetes 的说明。
我们最初的“建立一个快速的 Node API”任务已经发展成了一套与我们的核心技能不符的任务。我第一次收到这样的任务时,花了几天时间进行配置,并等待后端团队的反馈,以确保我没有造成比解决的问题更多的问题。
一些公司有 DevOps 团队来检查这项工作,并确保它不会做任何糟糕的事情。其他公司则最终信任 Stack Overflow 的集体智慧,并寄希望于一切顺利。
使用这种方法,事情一开始可以通过一些 Node 代码进行管理,但很快就会发展成多层配置,涵盖了远远超出我们对前端开发人员期望的专业领域。
方案 2:使用无服务器函数构建相同的 REST API
如果我们选择无服务器函数,故事可能会大不相同。无服务器是 Jamstack Web 应用程序的绝佳伴侣,它使前端开发人员能够处理中间层编程,而无需费心弄清楚如何部署和扩展服务器。
有多个框架和平台使部署无服务器函数变得轻而易举。我首选的解决方案是使用 Netlify,因为它能够自动持续交付前端和无服务器函数。在本例中,我们将使用Netlify 函数来管理我们的无服务器 API。
使用函数即服务(一种描述处理无服务器函数的架构和扩展的平台的花哨方式)意味着我们可以只专注于业务逻辑,并知道我们的中间层服务可以处理大量流量而不会崩溃。我们不需要处理 Docker 容器或 Kubernetes,甚至不需要处理 Node 服务器的样板代码——它可以正常工作™,因此我们可以交付解决方案并继续执行下一个任务。
首先,我们可以在netlify/functions/movie-by-slug.js
中定义我们的 REST API 无服务器函数
const movies = require('./data.json');
exports.handler = async (event) => {
const slug = event.path.replace('/api/movies/', '');
const movie = movies.find((m) => m.slug === slug);
return {
statusCode: 200,
body: JSON.stringify(movie),
};
};
为了添加正确的路由,我们可以在项目的根目录中创建一个netlify.toml
[[redirects]]
from = "/api/movies/*"
to = "/.netlify/functions/movie-by-slug"
status = 200
这比 Node/Express 方法所需的配置少得多。我更喜欢这种方法的原因是,这里的配置仅限于我们关心的内容:API 应该处理的特定路径。其余部分——构建命令、端口等——都使用良好的默认值进行处理。
如果我们安装了Netlify CLI,我们可以立即使用命令ntl dev
在本地运行它,该命令知道在netlify/functions
目录中查找无服务器函数。
访问http://localhost:888/api/movies/booper
将显示一个包含有关“booper”电影详细信息的 JSON 对象。
到目前为止,这与 Node 和 Express 设置的差别并不太大。但是,当我们准备部署时,差异很大。以下是将此站点部署到生产环境所需的步骤
- 将无服务器函数和
netlify.toml
提交到仓库,并将其推送到 GitHub、Bitbucket 或 GitLab 上 - 使用 Netlify CLI 创建一个连接到您的 Git 仓库的新站点:
ntl init
就是这样!API 现在已部署,并且能够根据需要扩展到数百万次点击。每当将更改推送到主仓库分支时,都会自动部署更改。
您可以在https://serverless-rest-api.netlify.app查看实际效果,并在GitHub 上查看源代码。
无服务器为前端开发人员释放了巨大的潜力
无服务器函数并非所有后端的替代方案,但它们是处理中间层开发的极其强大的选项。 无服务器避免了可能导致组织瓶颈和严重效率问题的意外复杂性。
使用无服务器函数允许前端开发人员完成中间层编程任务,而无需承担会带来风险并降低生产力的额外样板代码和 DevOps 开销。
如果我们的目标是赋予前端团队快速自信地交付软件的能力,那么选择无服务器函数就能将生产力融入基础设施。 自从将此方法作为我的默认 Jamstack 起点以来,我能够比以往更快地交付软件,无论我是在独自工作、与其他前端开发人员合作,还是跨职能地与公司内部的团队合作。
感谢这篇文章!
你的文章非常有趣(而且你准确地描述了我现实生活中的噩梦……)。
我想知道你是否有更多资源或好的教程链接来学习这些无服务器函数以及如何创建/使用它们。
有趣的是,根据我的经验,我发现无服务器函数最终会带来更多工作,因为如果团队已经有一堆容器化服务,那么日志记录和调试方式会不同,并且没有预先设置。 由于您仍在构建服务,因此您应该坚持团队正在执行的操作,因为他们可能已经拥有一个良好的模式来处理错误监控、日志记录等……
我实际上一直反对无服务器,但在这篇文章之后,我改变了主意。
我在生产环境中使用过无服务器,这简化了一些无服务器的实际开销,这些开销被一笔带过了。
它促进了奇怪的设计模式,连接池和简单缓存头的设置变得非常困难。
请求不能超过 30 秒,这意味着任何长时间运行的操作都会中断。
如果您在 VPC 中,那么每次冷启动都会有 8 秒的延迟。
Web API 网关很糟糕
在没有针对本地开发进行大量前期设置的情况下,可观察性较低
没有简单的方法可以在本地轻松模拟 SNS 或 SQS。
它根本不是一把金枪,如果您有 PaaS 服务,那么使用容器工作可能会容易得多!
由于其更高的效率和更低的基础设施成本,函数即服务正成为越来越受欢迎的选择。 云网络是 FaaS 服务的一个闪亮示例。感谢分享有价值的内容
我见过无服务器与服务器-docker-kubernetes 架构一起使用,并且引入了一个非常关键的本地开发故障——想象一下在本地(localhost)运行集群,而您的系统的一部分只能在开发/登台/生产环境中运行,例如只能“与非本地数据库和 API 交谈”。 这不利于开发和测试