原文地址,https://scoutapm.com/blog/nodejs-architecture-and-12-best-practices-for-nodejs-development

尽管仅有 11 年的历史,Node.js 已经成为在上个世纪最流行的 web 开发框架之一。我是 JavaScript 的绝对粉丝,感谢 Node.js 让我可以在浏览器之外,使用 JavaScript 写服务端 web 应用程序,它是非阻塞、轻量、快速、健壮、可扩展的。

这篇文章中,我会讨论 Node.js 的两个方面,框架的内部机制和创建高效可维护的 Node.js 应用的最佳开发实践。

通过有意识的努力去了解框架的内部工作机制,我们不紧对框架本身的方式和手段、而且对于流行编程范式和他们的设计决策都会有所了解。随着时间推移,这些更底层的思想和知识将反映到我们写代码的方式,推动我们理解如何在速度和性能上优化我们的应用。Node.js 如何在底层工作的一个重要方面是它是单线程、基于事件循环的设施,用于实现异步行为。我们将这篇文章的前半部分深入解释。

这篇文章的后半部分将专注于光谱的另一端,重点介绍在开始新的 Node.js 项目时要牢记的 12 个最佳实践。这些构成了创建一个健壮应用的各种方面,包括整体架构、目录结构、模块化、写整洁代码、依赖管理等。在某种程度上,这将是对于 Node.js 构建块的理解的推断,来建立特定的基本规则和指南,为我们的项目奠定坚实的基础。

通过这篇文章,我希望能够回答下面的问题:

  • Node.js 如何在底层工作?
  • 如何实现并发?
  • 和其他多线程 web 框架对比,他表现如何?
  • 一个好的 Node.js 项目设置应该是什么样?
  • 当你配置一个 Node.js 应用时,什么是你脑中的最重要的一个实践?

使用下面的链接切换到或者跳过教程:

  • Node.js 简史📜
  • 背景:入门,Node.js 如何在后台工作⚙️
    • Web 框架中的传统多线程处理♨️
    • Node.js 中的单线程实践循环架构🔄
  • 为什么一个好的设置对于 Node.js 应用很重要🏠
  • 12 个 Node.js 开发的最佳实践
    • 最佳实践 #1:采用分层方法🧁 🌈
    • 最佳实践 #2:目录结构🗂
    • 最佳实践 #3:发布订阅模型📡
    • 最佳实践 #4:整洁代码和易读性👓
    • 最佳实践 #5:写异步代码🤝
    • 最佳实践 #6:配置文件和环境变量🌳
    • 最佳实践 #7:测试、日志和错误处理🕵🏻‍♂️
    • 最佳实践 #8:代码压缩和文件大小📦
    • 最佳实践 #9:依赖注入🧳
    • 最佳实践 #10:第三方解决方案🎉
    • 最佳实践 #11:遵循编程原则🌟
    • 最佳实践 #12:使用应用监控工具📈

让我们开始吧!

Node.js 简史📜

可以了解一下时间线,万维网开始于约30年前。JavaScript 诞生于约25年前,和PHP差不多(26年)。另一方面,Node.js 仅11岁。尽管他的运行时间相对较短,但 Node.js 为世界各地的开发者组织创造了奇迹。
Node.js 架构和 12 个Node.js最佳开发实践 - 图1
从 JavaScript 诞生开始,已经有尝试在后端利用它,例如,Netscape(网景)使用它做和 Netscape Livewire 类似的事。然而这些都被证明是不成功的。2004 年前后,当第一波 Web 2.0 浪潮开始时,由于现代 Web 开发愿景,JavaScript 开始受到许多关注。由于 JavaScript 曾(当前正)是一门广泛使用的客户端编程语言,浏览器竞争并推动去创建最优的 JavaScript 引擎是实现最佳性能。其中一个是 V8 引擎,后来在此之上建立了 Node.js。由于这个势头,JavaScript 繁荣了,V8 也繁荣了。

在2009年,合适的地点、合适的时间,Node.js 诞生了。从那时起,Node.js 开发飙升。尽管有来自先驱者像 PHP 和先进的 Java,但由于他的异步 I/O,事件驱动架构,轻量级,速度,可扩展性和它使用了像 JavaScript 这样最流行的编程语言这个事实,在今天对于许多应用来说,Node.js 已经出现在服务端选择的更优选中。今天,Node.js 服务器在应用程序和服务于世界范围的数百万用户的企业像 Netflix、Linkedin、Microsoft、GoDaddy、Paypal 等许多公司的生产环境中被使用。给一个关于流行程度的估计,Node 的包管理器,NPM,注册者每周数十亿次下载。

感谢它的巨大的社区用户和开发者,Node.js 被积极维护。这意味着如果卡在某个问题需要在代码上帮忙或者一般开发建议,网络上有丰富的支持。

先在让我们了解是什么让 Node.js 达到这个阶段,也就是它底层是如何工作的。

背景:入门,Node.js 如何在后台工作⚙️

Node.js 流行是归功于它的异步事件驱动、非阻塞 I/O处理。它的大部分并发和异步能力来自 JavaScript 的单线程事件循环模型。

大多数 Web 开发的备选方案,像 ASP.NET,JSP,Spring 使用多线程处理架构来处理并发客户端请求。在将它们于 Node.js 带来的内容进行对比之前,让我们仔细看下这个多线程模型。

Web 框架中的传统多线程处理♨️

在多线程处理设置中,每个服务器都有个有限的线程池可供使用。每次服务器收到客户端请求时,它从线程池选个线程并将请求给它处理。这个线程将处理请求相关的全部事情。在线程内部,处理本质上是顺序、同步的,即一次执行一个操作。无论如何,当一个新的并发请求到服务器时,它能够从线程池中选择任何可用的线程并让它工作。
Node.js 架构和 12 个Node.js最佳开发实践 - 图2
这能够运行直到全部的线程都被占用。当这个发生时,你的服务器将强制等待到至少一个繁忙的线程被释放来处理这个新的请求。如果不负责任的考虑,这会使应用缓慢且效率低下。除此之外,线程中的自然同步处理意味着尽管我们针对并发启动多线程,但每个单独的线程遇到阻塞代码时将被减速。这样的多线程支持也带来了处理同步和管理多线程的困难。也有死锁的风险,在处理时等他其他线程释放资源时多线程被永远阻塞。

让我们看看 Node.js 如何处理并发。

Node.js 中的单线程实践循环架构🔄

有个困惑是 Node.js 是否能仅用一个线程做每件事情。这如何实现的?仅用一个线程如何于其他多线程框架竞争?

我们知道,Node.js 是构建在 V8 JavaScript 引擎上的一个重要的 JavaScript 运行时。这意味着它是基于 JavaScript 的单线程架构。因此每次客户端请求来时,都被单个主线程处理。事件循环是一个主要组件,让 Node.js 以非阻塞的方式运行阻塞 I/O 操作。它不断的跟踪异步任务状态(例如,在回调函数中的代码),在他们完成时,将他们移回执行队列。它操作的使我们上面讨论的同一个主线程。

有趣的是,虽然表面上只有一个主线程,但是系统内核中有一堆辅助线程,使 Node.js 能够利用磁盘和基于网络进行异步操作。这组线程构成了构成了(所谓的)worker 池。
Node.js 架构和 12 个Node.js最佳开发实践 - 图3
事件循环本身能够进行基本的处理,但是对于异步 I/O 操作、涉及例如文件系统(I/O 密集)和加密(CPU 密集)的这种模块,它能够将其给系统内核中的worker 池处理。worker 池通过 libuv 实现,可以根据需要生成和管理多个线程。这些线程能够以同步的方式单独运行他们各自被授予的任务,当完成时将响应返回到事件循环。当这些线程运行被授予的任务时,事件循环能够正常运行,并发的处理其他请求。当线程完成任务处理,它们能将输出返回到事件循环,能够返回放到执行队列中继续执行或者返回给客户端。

采用这样的架构背后的处理思想能够对应典型的 Web 负载情况,单个主线程与传统“每个线程处理一个请求”的架构相比能够执行和扩展的更好。所以,Node.js 成为了许多人的首选,因为他的处理速度和可扩展性的优点。然后有个警告是,对于复杂的、内存密集型操作,如图像处理的矩阵乘法,数据科学和机器学习应用,Node.js的性能会受到影响。他们会阻塞仅有的主线程,使服务器无响应。所以对于这种情况,Node.js 也引入了工作线程,开发者可以利用它们创建高效的多线程 Node.js 应用程序。

如果你想了解更多关于使用 JavaScript 写异步代码,你可以访问我们blog的文章,异步 JavaScript:从 Promises 到 Async/Await.

为什么一个好的设置对于 Node.js 应用很重要🏠

现在我们对于 Node.js 的底层机制已经有了一个清晰的了解,让我们转到应用方面,看下是什么构成了一个智能的 Node.js 项目。

好的项目结构设置是任何软件工程流程的关键并且为了有效应用奠定了坚实的基础。当开始一个新的 Node.js 项目时,一个明确的、预先安排好的结构提供了一个系统工作的鸟瞰图。它能够帮助你以系统的方式组织业务逻辑、服务、API 路由、数据模型等。这会使项目中各种组件位置和角色更连贯和清晰。

一个健全的架构允许你分解、简化一个复杂的系统到更小、更清晰的模块,他描述了应用内部如何运行的清晰的图。下面包含了一个理想项目设置的关键点:

  • 连贯,结构清晰明确
  • 重用性,模块化、关注点分离
  • 简单,易于理解
  • 易于调试和维护
  • 自动化测试,日志记录机制
  • 采用最佳的编程、开发原则

当建立 Node.js 应用时,需要去设置一系列基础规则和开发指南,让我们深入到下一节,讨论一下开发 Node.js 项目时的最佳实践。

Node.js 开发的最佳实践

网络上有各种教程、文档、博客和视频包含了 Web 开发的基础内容。但通常来说,对于最佳实践的重要性是随着我们构建更多应用,随着我们失败和成功,我们一路学习。

在这个部分,我想将 Web 开发中的最重要的部分提取为一系列在使用Node.js构建应用时需要考虑的要点。这些观点带来了关于某些设计决策如何在你的 Web 开发生命周期中产生巨大红利的见解。

最佳实践 #1:采用分层方法 🧁 🌈

关注点分离

流行的 Node.js 框架,比如 Express.js 允许你定义路由处理者(总觉得直接用 handler 更合适)作为回调函数,当收到客户端请求时它们将被执行。由于这些框架提供的灵活性,你可能会天真的尝试直接在这些函数中定义你的全部业务逻辑。如果你这么做,你会注意到,在你意识到之前其中的内容将迅速变得臃肿,本来应该很简洁的服务器路由文件成为了笨重、笨拙、凌乱的代码块,这样不紧难以阅读、维护、管理,而且也很难进行单元测试。

所以,最好是去实现著名的“关注点分离”编程原则。如上所述,我们应使用不同的模块解决我们程序相关的不同问题。就服务端应用程序而言,不同的模块(或者层)负责处理客户请求,处理响应的不同方面。大多数情况下,会像这样展开 -

客户端请求 ➡️ 一些业务逻辑处理 + 一些数据(库)操作 ➡️ 返回响应

这些方面能够通过程序的三个不同的层来处理,如下 -
Node.js 架构和 12 个Node.js最佳开发实践 - 图4

  • 控制层(Controller)
    • (API 路由 和 端点(endpoints 翻译成这个貌似不太好))
  • 服务层(Service)
    • (为了业务逻辑)
  • 数据访问层(Data access layer)
    • (数据库操作)

      控制层 🎮

      这是定义路由的模块。仅仅是定义路由的地方。在路由处理函数中,你可以解构请求对象,选择重要的数据,将他们出入服务层处理。

服务层 👩🏽‍🏭 🏭

放业务逻辑的地方,你的程序最核心的部分。包含了许多承担单一职责、可重用(也遵循其他 S.O.L.I.D 编程原则)的类和方法。这层允许你有效解耦路由中定义的处理逻辑。

还有一方面需要考虑的是数据库,为了单独处理它,我们需要新的一层。

数据访问层 🌐

数据访问层有跟数据库对话的责任 - 获取、写入、更新。所有的 SQL 查询,数据库连接,模型,ORM(数据关联映射)等,都应该定义在这里。

这三层设置对于多数 Node.js 应用是可依赖的架构,使你的应用易于编写,维护,调试和测试。现在我们看下如何在项目中实现这些层。

最佳实践 #2:目录结构

适当组织你的代码文件

前面的部分,我们看到如何从逻辑上将项目模块化为三个分离的层。这个抽象架构能够通过适当的目录结构实现,它们分离了不同的模块到不同目录中。

这提供了清晰的关于功能被管理在哪里的说明,允许我们组织类和方法到分离的容器中,这样易于管理。下面是通用的(也是有效的)目录结构,在开始新的 Node.js 项目时能够作为模板。

  1. src
  2. ├── app.js app entry point
  3. ├── /api controller layer: api routes
  4. ├── /config config settings, env variables
  5. ├── /services service layer: business logic
  6. ├── /models data access layer: database models
  7. ├── /scripts miscellaneous NPM scripts
  8. ├── /subscribers async event handlers
  9. └── /test test suites

目录 - /api (控制层),/service, /models (数据访问层) 代表我们前面讨论的三层。/scripts 目录存储工作流自动化脚本(例如,部署)用于构建流水线,/test 目录用于存储测试用例。/config/subscriber 的用处我们稍后看,在说到配置文件、环境变量、发布/订阅模型的时候。

作为开发者,没有比阅读(写)结构清晰和组织良好的代码更快乐的事了。下面一个应该记住重要的开发实践 - 整洁代码和易于维护。

最佳实践 #3:发布者 订阅者 模型 📡

发布者/订阅者模型是个流行的数据交换模式,实现两个实体沟通 - 发布者和订阅者。发布者(消息发送者)通过特定频道发送消息,不需要了解接收的实体。订阅者(消息接收方),则仅关注一个或多个这样的频道,不需要了解消息发送的实体。

在你的项目中包含这种模型是很好的,用于管理多个子操作对应于一个单一操作的情况。例如,注册创建新用户时,需要做一堆事 - 数据库中创建实体,生成授权key,发送确认邮件等等。如果这些全部通过一个服务函数处理,不仅处理时间比通常长,破坏了单一职责原则。这里是简单的代码示例 -

  1. export default class UserService() {
  2. async function signup(user) {
  3. // 1. Create user record
  4. // 2. Generate auth key
  5. // 3. Send confirmation email
  6. // ...
  7. }
  8. }

让我们看如何使用发布/订阅模式,有效的简化和模块化这个。

Node.js 中,发布/订阅模式能够在使用 Events API 时设置。上面的例子中,当收到请求时,你可以通过代码去触发 “signup” 事件。这种情况下,你的服务模块只需要进行一次调用即可触发相应的事件,而不是在非发布/订阅模式中调用多个函数。

  1. var events = require('events');
  2. var eventEmitter = new events.EventEmitter();
  3. export default class UserService() {
  4. async function signup(user) {
  5. // emit 'signup' event
  6. eventEmitter.emit('signup', user.data)
  7. }
  8. }

处理这样的事件触发,你可以有多个订阅者,它们本质上是事件监听者,等待特定事件来触发。这些订阅者能够基于使用目的,被组织到多个分开的文件中,存储在目录结构章节提到的 /subscribers 目录中。让我们针对上面的例子创建一个简单的订阅者 -

  1. // email.js
  2. // ...
  3. eventEmitter.on('signup', async ({ data }) => { // event listener
  4. // send email
  5. })
  6. // ...
  1. // auth.js
  2. // ...
  3. eventEmitter.on('signup', async ({ data }) => { // event listener
  4. // generate auth key
  5. })
  6. // ...

如你所见,这个方法更加清晰,更弹性,并且易于维护和扩展。

最佳实践 #4:整洁代码 & 易于阅读 👓

使用代码校验器(linters),格式化工具 和 样式指南;增加注释

校验工具 & 格式化工具 🔍

这的主要目标是改善代码质量和使其易于阅读。多数代码设置工作流总是包含代码校验工具和格式化工具。代码校验工具寻找和警告关于句法(和甚至语义)错误代码,而代码格式化工具(正如名字所指)作用于代码的格式上,来确保整个项目的一系列格式和样式指南一致性。一些流行的 JavaScript 校验工具如 ESLintJSLintJSHint。对于代码格式化工具,我可以看 Prettier。多数 IDE/代码编辑器,比如 Visual Studio Code (VSCode),Atom 等明白写高质量代码和提供校验工具和格式化工具插件的重要性,插件非常直观且易于配置,这是一个好的事情。
Node.js 架构和 12 个Node.js最佳开发实践 - 图5
这些 IDE 也提供了有用的功能,像智能代码编译,自动导入,悬停文档支持,调试工具,代码导航,重构等等这些功能。所以我强烈建议使用这样的 IDE(推荐 VSCode)作为你的开发工具。

样式指南 🧣

除了校验工具和格式化工具,你也可以参考像被 Google 和 Airbnb 这样的巨人使用的 JavaScript 代码样式和标准。这些指南包括了从名称转换(针对文件,变量,类等等)到格式化特定的文件编码等。这能帮助你写符合世界顶级开发者实践和标准的高质量代码。
Node.js 架构和 12 个Node.js最佳开发实践 - 图6

添加注释 💬

写代码时,另一个重要的事是更勤奋的添加有用的注释,这样团队中的其他开发者将受益。仅需要 5 到 6个词组成的句子就可以让你的队友明白甚至是最复杂的代码片段的目的。这节省了每个人很多时间,避免困惑,所以是个双赢的局面。

有一点需要注意,我们也应该处理好注释,即不太多,也不太少。不知道如何平衡?用 Michael Gary Scott 的话说,”You’ll learn baby. You’ll learn!”.

注释也可以作为记录项目 API 的一种方式(包括 全局视角,版权注意,作者信息等等),它的类(描述,参数),方法和命名函数(描述,参数,返回值等)。当然通过使用 API 文档生成工具,如 JSDoc 也能实现。

最佳实践 #5: 写异步代码 🤝

使用 Promise,async/await 语法

JavaScript 因回调函数(函数能够作为参数传到其他函数中)而广为人知。JavaScript 中也可以定义异步行为。回调的问题是 - 随着链式操作增加,你的代码将变得膨胀和笨重,导致著名的回调地狱。为了解决这个问题,ES 6 (ECMASCRIPT 2015) 推出了 Promise API, 它能够使你更容易的在 JavaScript 中编写异步代码。正如标题提到的,ES 8 (2017) 中 async/await 语法引入了,使异步更加简单,同时让 API 更加然。

因此,在 Node.js 应用中,提倡抛弃笨重的回调函数,采用 async/await 和基于 promise 的语法。这使你能够写更整洁、易读,易于错误处理和测试的代码;所有这些同时保持清晰的控制流程和更连贯的功能编程设置。

让你意识到 async/await 是如何使你的生活更美好,这里有两种写异步代码的比较。

  1. <script>
  2. function get_data() {
  3. $.get('https://url.com/one', () => {
  4. $.get('https://url.com/two', () => {
  5. $.get('https://url.com/three', (res) => {
  6. console.log(res)
  7. })
  8. })
  9. })
  10. }
  11. </script>

回调函数例子

  1. <script>
  2. async function get_data() { // async function
  3. await $.get('https://url.com/one')
  4. await $.get('https://url.com/two')
  5. let res = await $.get('https://url.com/three')
  6. console.log(res)
  7. }
  8. </script>

async/await 例子

正如上面提到的,我们已经包括每个事情 发布在我们博客中 JavaScript 异步编程。如果你感兴趣,你可以点击这里 -> 异步编程:从 Promises 到 Async/Await。

最佳实践 #6: 配置文件和环境变量 🌳

配置它!

随着应用变大,你会有特定的全局配置选项和能够被跨模块访问到的设置的需求。将它们存储到你项目中配置目录的分开的文件中是最佳实践。在这篇文章的前面的目录结构部分要我们已经看到了目录。这个目录能够包含全部的不同的配置选项,这些选项根据它们的使用情况分组到文件中。

  1. /config
  2. ├── index.js
  3. ├── module1.js
  4. └── module2.js

配置目录例子

这些配置选项不仅包含通用的,基础配置,还包含安全 API 秘钥,数据库连接地址等。后者计划作为环境变量存储在 .env 文件中。下面是 .env 文件以键-值对方式存储数据 -

  1. DB_HOST=localhost
  2. DB_USER=root
  3. DB_PASS=my_password_123

.env 文件例子

这些 .enu 文件是秘钥文件,他们不会放到 Git 中管理,所以不需要提交或推送(除了第一次使用空值)。

这些环境变量能够使用 npm dotenv 包访问的到,如下所示 -

  1. // app.js
  2. require('dotenv').config()
  3. console.log(process.env.DB_HOST)
  4. console.log(process.env.DB_USER)

在代码中访问环境变量

一个非常通用的开发实践是导入配置文件中的全部变量(以及其他预定义的选项和配置),作为对象暴露给应用的其他部分。这种方式,如果需要,你可以仅改变一处的通用配置,将会反映到你的整个应用。下面是这个过程是如何完成的代码段 -

  1. // config/database.js
  2. require('dotenv').config()
  3. export default {
  4. host: process.env.DB_HOST,
  5. user: process.env.DB_USER,
  6. pass: process.env.DB_PASS,
  7. }

使用 env 变量导出配置对象

这个方式你不需要在代码中笨重的使用 process.env.key_name 调用,因此更加整洁。继续!

最佳实践 #7: 测试,日志记录 & 错误处理 🕵🏻‍♂️

错误处理是个很重要的部分

测试你的代码 🔬

对于刚开始软件开发的人,漠视为代码写单元测试的重要性非常常见。然而,测试是任何软件应用不可缺少的部分 - 它允许你测试代码的有效性,准确性和稳健性,即使是最小的错误也能暴露出来 - 不仅在集成系统中,甚至隔离的原子成分中。测试让你做到这些,以一种便捷的自动化的方式。

单元测试是大多数测试设置的基础。这里,单个的单元/组件被独立测试从代码的其余部分来验证正确性。这允许你代码被验证以(逻辑的)低层级来确保每个内部组件按预期准确工作。下面是个非常基础的单元测试例子 -

  1. // example.test.js
  2. const assert = require('assert');
  3. describe('Basic addition test', () => {
  4. it('should add up to 3', () => {
  5. assert.equal(2 + 1, 3);
  6. });
  7. it('should equal 8', () => {
  8. assert.equal(4 * 2, 8);
  9. });
  10. });

输出:
Node.js 架构和 12 个Node.js最佳开发实践 - 图7

这些单元测试用例检查你的组件代码是否按预期返回。对于开发这样的测试用例,有许多测试框架可供 Node.js 开发人员选择。其中最流行的有 Mocha, JestJasmine

全部记录下来 📝

Node.js 架构和 12 个Node.js最佳开发实践 - 图8

日志在任何软件应用的全流水线中扮演重要角色:从开发到测试到预发布到生产,一个良好实现的日志系统允许你记录重要信息,了解你的应用的各个方面的准确性和性能指标。它不仅允许你更好的了解和管理你的应用,也使调试更加容易。

JavaScript 提供了许多函数来打印和记录日志信息。对于打印常规信息和调试信息,你可以使用 -

  • console.log()
  • console.info()
  • console.debug()

对于记录错误和警告信息 ⚠️ -

  • console.error()
  • console.warn()

它也允许你用管道的方式记录信息通控制台或者文件流中(使用 ‘>’ 操作符)。
然而,如果想要功能齐全和方便的日志设置,你可以考虑在代码中使用第三方日志库。一些最常用的 Node.js 日志框架如 - Winston, BunyanMorgan.

如果你有兴趣了解更多关于 Node.js 日志相关的,你可以查看 Node.js 日志:一份实践指南 这篇发布在我们博客上的文章,我们可以更加细节的讨论。

捕获异常 ⚠️

一个违反直觉的真相是,对于开发者来说报错是好的。它们允许他们了解代码中的不准确和漏洞,当代码暂停并且提示时。它们也提供了报了什么错,出错位置,和需要做什么来修正的相关信息。

但不是让 Node.js 抛出错误,打断代码执行,有时甚至失败,我们应该通过处理这些错误情况来掌握应用程序的控制流程。这是我们能够通过使用 try/catch 代码快进行异常处理来达到的目的。通过给开发者能力来使用程序管理这些异常,它保持程序稳定,便于调试,也能防止糟糕的最终用户体验。下面是 Node.js 中基础的 try/catch 块例子。

  1. try {
  2. if (xyzHappens) {
  3. throw "my error message ⚠️"; // 🛫
  4. }
  5. }
  6. catch (e) {
  7. console.log(e); // 🛬
  8. }
  9. finally {
  10. console.log("Finally executed! 🏁");
  11. }

Node.js 异常处理例子

最佳实践 #8: 代码压缩和文件大小 📦

Gzip 压缩!

Gzip 是无损的文件格式,为了更快的网络传输(也是一个软件应用)用于压缩(和解压)文件。因此它对于 Node.js 服务器托管的 Web 页面极其有益。

像 Express.js 框架使用 Gzip 压缩难以置信的简单,通过压缩中间件。使用 Gzip 压缩也是 Express.js 文档用于改善应用性能的第一个建议。看下示例代码 -

  1. var compression = require('compression')
  2. var express = require('express')
  3. var app = express()
  4. app.use(compression())

Express.js 中开启 gzip 压缩

这个本质上是压缩对于每个请求服务端返回的响应主体,来减少延迟,使网站更快速。

当你致力于优化服务端性能时,始终检查前端代码也很重要 - 警惕托管的 web 页面大小。因此,在提供服务前,你应该确保使用 HTMLMinifierCSSNanoUglifyJS 这样的工具来最小化前端 HTML,CSS,Javascript 代码。这些最小化工具移除了文件中不需要的空白和注释,做一些简单的编译器优化,通过这些来减少文件大小。

所以,使用 Gzip 压缩来缩小前端代码是可行的方法。

最佳实践 #9: 依赖注入

依赖注入是一种软件设计模式,它提倡将(注入)依赖(或者服务)作为参数传入模块中,而不是在内部引入或者创建特定的依赖。

对于一个非常基础的概念来说,这个术语很奇特,它使你的模块更加弹性,独立、可重用、可扩展,并且可在您的应用中轻松测试。让我们通过代码看下它究竟意味着啥。

我们代码中有个模块,它暴露了两个用于处理任意表情类的函数。它也使用了黄色-表情模块,这个模块假设是用来处理黄色表情符号的数据库。

  1. const Emoji = require('./Emoji');
  2. const YellowEmojis = require('./yellow-emojis');
  3. async function getAllEmojis() {
  4. return YellowEmojis.getAll(); // 🌕 🌟 💛 🎗 🌼
  5. }
  6. async function addEmoji(emojiData) {
  7. const emoji = new Emoji(emojiData);
  8. return YellowEmojis.addEmoji(emoji);
  9. }
  10. module.exports = {
  11. getAllEmojis,
  12. addEmoji
  13. }

如上的设置仅这对特定的(黄色的这种)表情集合。所以,如果你想要改变代码来用于其他表情集合(或者多种集合),你需要创建分离的函数或者修改很多当前的设置。

所以,一个好的方式是创建函数,它们不依赖当前做的事,更通用更弹性。让我们看如果通过在我们的模块中注入依赖的方式来实现 -

  1. const Emoji = require('./Emoji');
  2. function EmojisService(emojiColor) {
  3. async function getAllEmojis() {
  4. return emojiColor.getAll();
  5. }
  6. async function addEmoji(emojiData) {
  7. const emoji = new Emoji(emojiData);
  8. return emojiColor.addEmoji(emoji);
  9. }
  10. return {
  11. getAllEmojis,
  12. addEmoji
  13. };
  14. }
  15. module.exports = EmojisService

使用依赖注入

这里我们为了服务创建了一个新的函数(EmojisService),它将依赖(emojiColor)作为参数,而不是固定的仅一种特定类型(黄色)。这就是依赖注入。感谢它,我们的服务成为了更通用的接口,它不仅易于使用,而且也更容易进行单元测试。这是因为前面的例子中,我们不得不创建子的 黄色-表情 来测试模块。而现在我们能直接通过传入 emojiColor 依赖来成为测试用例。

最佳实践 #10: 第三方解决方案

不用重复造轮子。也不要贪心。

Node.js 在世界范围内有巨大开发者社区。就第三方支持而言,Node.js 包管理器,NPM 足够功能丰富,维护良好,文档清晰,有你能够想到的任何需求对应的框架、库和工具。因此对于开发者来说增加已经存在的解决方案到代码中和完成 API 开发是非常方便的。

作为开发者,寻找使你的生活更轻松的工具会有所帮助。这里有一些流行的 Node.js 库,能够提升你的编程工作流的效率 -

虽然这些库和工具减轻很多负担,但是重要的是要对我们导入的每个包保持了解和负责。我们应该意识到我们导入的每个包的目的,强项和不足之处,确保我们不过度依赖他们。

最佳实践 #11: 遵循好的通用编程实践

“总是假设最终维护你的代码的人是个知道你住哪里的精神病患者” - Martin Golding

  • DR (别重复自己)
  • 单一职责原则(SRP)
  • “保持简单,愚蠢” (KISS)
  • 关注点分离
  • YAGNI (你不需要它)
  • 避免过早优化
  • S.O.L.I.D 编程原则
  • 依赖注入

你可以在网络上阅读更多的关于上面的每一项,查看你关注的。

最佳实践 #12: 使用应用监控工具

对于生产环境中的大规模应用,一个主要目标是了解用户对应用的反馈:哪个路由或者哪个功能使用的最多,最常执行的操作等。并且也应该关注执行性能指标,质量问题,瓶颈,通用错误等。使用这些信息进行必要的更改和改进。

这就要提到应用监控(APM)工具像 ScoutAPM 了。ScoutAPM 允许你解构分析和优化你的 Web 应用性能。

它给你实时监控,所以你可以迅速查明和解决问题,在用户看到他们之前。

Scout 仅是 Node.js 应用新能监控(APM)产品,它查明和优先考虑 Node.js 应用的性能和稳定性问题,像 N+1 数据库查询,数据库慢查询,性能异常等等。

总结

准备好开始你的 Node.js 项目了吗?

好,继续!这就是这篇文章。尽管有更多的方面能够智能化 Web 开发,但是我尽力去展示当需要创建 Node.js 应用时的最重要的主题。

这篇文章中,我们首先了解 Node.js 架构内部 - 我们了解了它的单线程架构,它运行异步代码的事件循环机制。然后我们转到去了解是什么能够使它能够建立强壮的,可持续和可扩展的 Node.js 应用。我们了解了 12 个最佳实践,它们包含了如何从逻辑上组织代码项目,到日志,测试,格式化,校验工具,到编写异步代码的本质等等。

现在你了解了创建可靠的、防弹的 Node.js 应用的每件事,继续并且在你的已有项目中实践今天学到的东西,或者从头创建一个,然后分享出来。加油!

保持健康,保持安全,持续学习。

Until next time! 快乐编码!

参考

S.O.L.I.D

S,单一功能原则
O,开闭原则
L,里氏替换原则
I,接口隔离原则
D,依赖反转原则

IDE

Integrated Development Environment 集成开发环境

感谢阅读

感谢你阅读到这里,翻译的不好的地方,还请指点。希望我的内容能让你受用,再次感谢。by llccing 千里