查看: 2576|回复: 0

[DIV/CSS] 高级 Node.js 项目结构教程 | @RisingStack

发表于 2017-7-22 08:00:02
句号论坛

项目结构是一个重要话题,因为你创建应用程序的方式可以决定整个项目生命周期的开发体验。

在这个Node.js项目结构教程中,我将回答我们在[RisingStack]( https://trace.risingstack.com/ 收到的关于结构化高级Node应用程序的一些最常见的问题,并帮助您构建一个复杂项目。

以下是我们的目标:

编写易于扩展和维护的应用程序。

项目配置与业务逻辑完全分离。

项目可以包含多个进程类型。

Node.js at Scale是一组文章集合,关注具有更多需求的Node.js安装和高级Node开发人员的公司。 章节:

使用 npm

npm技巧和最佳实践

SemVer和模块发布

了解模块系统,CommonJS和require

深入了解Node.js底层

Node.js事件循环

Node.js垃圾回收机制

编写本地Node.js模块

新建项目

高级Node.js项目结构 (本文)

JavaScript 简介代码最佳实践

异步处理

事件源

命令查询责任分离

测试

单元测试

端对端测试

生产环境中的Node.js

监控Node.js应用程序

调试Node.js应用程序

分析Node.js应用程序

微服务

请求签名

分布式跟踪

API 网关

Node.js项目结构

我们的示例应用程序正在侦听Twitter推文并跟踪某些关键字。在关键字匹配的情况下,该推文将被发送到RabbitMQ队列,该队列将被处理并保存到Redis。 我们还将有一个REST API输出我们保存的tweets。

你可以访问[GitHub]上的代码( https://github.com/RisingStack/multi-process-nodejs-example ). 此项目的文件结构如下所示:

  1. .
  2. |-- config
  3. | |-- components
  4. | | |-- common.js
  5. | | |-- logger.js
  6. | | |-- rabbitmq.js
  7. | | |-- redis.js
  8. | | |-- server.js
  9. | | `-- twitter.js
  10. | |-- index.js
  11. | |-- social-preprocessor-worker.js
  12. | |-- twitter-stream-worker.js
  13. | `-- web.js
  14. |-- models
  15. | |-- redis
  16. | | |-- index.js
  17. | | `-- redis.js
  18. | |-- tortoise
  19. | | |-- index.js
  20. | | `-- tortoise.js
  21. | `-- twitter
  22. | |-- index.js
  23. | `-- twitter.js
  24. |-- scripts
  25. |-- test
  26. | `-- setup.js
  27. |-- web
  28. | |-- middleware
  29. | | |-- index.js
  30. | | `-- parseQuery.js
  31. | |-- router
  32. | | |-- api
  33. | | | |-- tweets
  34. | | | | |-- get.js
  35. | | | | |-- get.spec.js
  36. | | | | `-- index.js
  37. | | | `-- index.js
  38. | | `-- index.js
  39. | |-- index.js
  40. | `-- server.js
  41. |-- worker
  42. | |-- social-preprocessor
  43. | | |-- index.js
  44. | | `-- worker.js
  45. | `-- twitter-stream
  46. | |-- index.js
  47. | `-- worker.js
  48. |-- index.js
  49. `-- package.json
复制代码

在这个例子中,我们有3个进程:

twitter-stream-worker : 此进程在Twitter上侦听关键字,并将推文发送到RabbitMQ队列。

social-preprocessor-worker : 此进程正在侦听RabbitMQ队列并将这些推文保存到Redis并删除旧的。

web : 此进程正在使用单个端点提供REST API: GET /api/v1/tweets?limit&offset .

我们将得到一个 web 和一个 worker 进程的区别,但是让我们从配置开始。

如何处理不同的环境和配置?

从环境变量加载特定部署的配置,并且不要将它们作为常量添加到代码库中。这些配置可以在部署和运行时环境(如CI,分段或生产)之间变化。基本上,你用相同的代码在任何地方运行。

对于配置是否与应用程序内部正确分离的一个好的测试是代码库可以随时公开。 这意味着你可以防止意外泄露的秘密或损害版本控制的凭据。

如果你的代码库可以随时公开,就说明你的配置与应用底层正确分离。

环境变量可以通过 process.env 对象来访问。不要忘了,所有的值都是String类型的,所以你可能需要使用类型转换。

  1. // config/config.js
  2. 'use strict'
  3. // required environment variables
  4. [
  5. 'NODE_ENV',
  6. 'PORT'
  7. ].forEach((name) => {
  8. if (!process.env[name]) {
  9. throw new Error(`Environment variable ${name} is missing`)
  10. }
  11. })
  12. const config = {
  13. env: process.env.NODE_ENV,
  14. logger: {
  15. level: process.env.LOG_LEVEL || 'info',
  16. enabled: process.env.BOOLEAN ? process.env.BOOLEAN.toLowerCase() === 'true' : false
  17. },
  18. server: {
  19. port: Number(process.env.PORT)
  20. }
  21. // ...
  22. }
  23. module.exports = config
复制代码
验证配置

验证环境变量也是一个非常有用的技术。它可以帮助你在启动时捕获配置错误,然后应用程序才会执行其他操作。你可以阅读更多关于Adrian Colyer在[此博客] ( https://blog.acolyer.org/2016/11/29/early-detection-of-configuration-errors-to-reduce-failure-damage/ ) 中配置的早期错误检测的好处。

这是我们改进的配置文件看起来像模式验证使用了 joi ( https://github.com/hapijs/joi)验证器:

  1. // config/config.js
  2. 'use strict'
  3. const joi = require('joi')
  4. const envVarsSchema = joi.object({
  5. NODE_ENV: joi.string()
  6. .allow(['development', 'production', 'test', 'provision'])
  7. .required(),
  8. PORT: joi.number()
  9. .required(),
  10. LOGGER_LEVEL: joi.string()
  11. .allow(['error', 'warn', 'info', 'verbose', 'debug', 'silly'])
  12. .default('info'),
  13. LOGGER_ENABLED: joi.boolean()
  14. .truthy('TRUE')
  15. .truthy('true')
  16. .falsy('FALSE')
  17. .falsy('false')
  18. .default(true)
  19. }).unknown()
  20. .required()
  21. const { error, value: envVars } = joi.validate(process.env, envVarsSchema)
  22. if (error) {
  23. throw new Error(`Config validation error: ${error.message}`)
  24. }
  25. const config = {
  26. env: envVars.NODE_ENV,
  27. isTest: envVars.NODE_ENV === 'test',
  28. isDevelopment: envVars.NODE_ENV === 'development',
  29. logger: {
  30. level: envVars.LOGGER_LEVEL,
  31. enabled: envVars.LOGGER_ENABLED
  32. },
  33. server: {
  34. port: envVars.PORT
  35. }
  36. // ...
  37. }
  38. module.exports = config
复制代码
配置分离

通过组件拆分配置可以是放弃单个增长的配置文件的良好解决方案。

  1. // config/components/logger.js
  2. 'use strict'
  3. const joi = require('joi')
  4. const envVarsSchema = joi.object({
  5. LOGGER_LEVEL: joi.string()
  6. .allow(['error', 'warn', 'info', 'verbose', 'debug', 'silly'])
  7. .default('info'),
  8. LOGGER_ENABLED: joi.boolean()
  9. .truthy('TRUE')
  10. .truthy('true')
  11. .falsy('FALSE')
  12. .falsy('false')
  13. .default(true)
  14. }).unknown()
  15. .required()
  16. const { error, value: envVars } = joi.validate(process.env, envVarsSchema)
  17. if (error) {
  18. throw new Error(`Config validation error: ${error.message}`)
  19. }
  20. const config = {
  21. logger: {
  22. level: envVars.LOGGER_LEVEL,
  23. enabled: envVars.LOGGER_ENABLED
  24. }
  25. }
  26. module.exports = config
复制代码

然后在 config.js 文件中,我们只需要组合组件。

  1. // config/config.js
  2. 'use strict'
  3. const common = require('./components/common')
  4. const logger = require('./components/logger')
  5. const redis = require('./components/redis')
  6. const server = require('./components/server')
  7. module.exports = Object.assign({}, common, logger, redis, server)
复制代码

你不应该将你的配置一起组成所有环境特定的文件,如 config / production.js 用于生产。因为当你的应用程序随着时间的推移扩展到更多的部署时它不能很好地扩展。

不要将你的配置全部分组到特定环境的文件,它不利于扩展#nodejs

如何组织一个多进程的应用程序?

该进程是现代应用的主要构建块。一个应用程序可以有多个无状态进程,就像我们的例子。HTTP请求可以由Web进程处理,并由工作者长时间运行或调度的后台任务处理。 它们是无状态的,因为需要持久化的任何数据都存储在有状态的数据库中。 因此,添加更多并发进程非常简单,而且这些进程可以基于负载或其他度量独立地缩放。

在上一节中,我们看到了如何将配置分解成组件。 当有不同的进程类型时会非常方便。 每种类型都可以有自己的配置,只需要组合它所需的组件,而不需要使用无意义的环境变量。

在 config/index.js 文件中:

  1. // config/index.js
  2. 'use strict'
  3. const processType = process.env.PROCESS_TYPE
  4. let config
  5. try {
  6. config = require(`./${processType}`)
  7. } catch (ex) {
  8. if (ex.code === 'MODULE_NOT_FOUND') {
  9. throw new Error(`No config for process type: ${processType}`)
  10. }
  11. throw ex
  12. }
  13. module.exports = config
复制代码

在根文件 index.js 中,我们开始使用 PROCESS_TYPE 环境变量选择过程:

  1. // index.js
  2. 'use strict'
  3. const processType = process.env.PROCESS_TYPE
  4. if (processType === 'web') {
  5. require('./web')
  6. } else if (processType === 'twitter-stream-worker') {
  7. require('./worker/twitter-stream')
  8. } else if (processType === 'social-preprocessor-worker') {
  9. require('./worker/social-preprocessor')
  10. } else {
  11. throw new Error(`${processType} is an unsupported process type. Use one of: 'web', 'twitter-stream-worker', 'social-preprocessor-worker'!`)
  12. }
复制代码

有趣的是,我们仍然有一个应用程序,但我们已经设法将它分成多个独立的进程。 每个进程都可以单独启动和缩放而不影响其他部分。 你可以实现这一点而不牺牲你的DRY代码库,因为代码的部分,如模型,可以在不同的进程之间共享。

如何组织测试文件?

使用某种命名约定将测试文件紧挨着测试模块,例如 .spec.js 和 .e2e.spec.js 。 你的测试应与测试模块一起保持同步,否则当测试文件与业务逻辑完全分离时,很难找到并维护测试和相应的功能。

使用某种命名约定将测试文件放在测试模块旁边,如module_name.spec.js

一个单独的 / test 文件夹可以保存应用程序本身未使用的所有附加测试设置和实用程序。

在哪里放置你的构建和脚本文件?

我们倾向于创建一个 / scripts 文件夹,其中我们将bash和node脚本用于数据库同步,前端构建等。 此文件夹将它们与应用程序代码分离,并阻止您将太多的脚本文件放入根目录。 在您的[npm scripts]( https://docs.npmjs.com/misc/scripts)中列出它们,以方便使用。

总结

希望你喜欢这篇关于项目结构的文章。 强烈建议查看我们上一篇关于这个主题的文章,我们在这里阐述了 Node.js项目结构的5个基本原理 .

如果您有任何疑问,欢迎在评论中告知。 在Scale系列的Node.js的下一章中,我们将深入探讨[JavaScript简洁编码]( https://blog.risingstack.com/javascript-clean-coding-best-practices-node-js -at-scale /)。 下周见!

来自:http://www.zcfy.cc/article/advanced-node-js-project-structure-tutorial-risingstack-2501.html



太阳http代理AD
回复

使用道具 举报

关闭

站长推荐上一条 /1 下一条