真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

詳解NodejsExpress.js項(xiàng)目架構(gòu)

視頻教程推薦:nodejs 教程

成都創(chuàng)新互聯(lián)服務(wù)項(xiàng)目包括瓊結(jié)網(wǎng)站建設(shè)、瓊結(jié)網(wǎng)站制作、瓊結(jié)網(wǎng)頁(yè)制作以及瓊結(jié)網(wǎng)絡(luò)營(yíng)銷(xiāo)策劃等。多年來(lái),我們專(zhuān)注于互聯(lián)網(wǎng)行業(yè),利用自身積累的技術(shù)優(yōu)勢(shì)、行業(yè)經(jīng)驗(yàn)、深度合作伙伴關(guān)系等,向廣大中小型企業(yè)、政府機(jī)構(gòu)等提供互聯(lián)網(wǎng)行業(yè)的解決方案,瓊結(jié)網(wǎng)站推廣取得了明顯的社會(huì)效益與經(jīng)濟(jì)效益。目前,我們服務(wù)的客戶(hù)以成都為中心已經(jīng)輻射到瓊結(jié)省份的部分城市,未來(lái)相信會(huì)繼續(xù)擴(kuò)大服務(wù)區(qū)域并繼續(xù)獲得客戶(hù)的支持與信任!
引言

在 node.js 領(lǐng)域中,Express.js 是一個(gè)為人所熟知的 REST APIs 開(kāi)發(fā)框架。雖然它非常的出色,但是該如何組織的項(xiàng)目代碼,卻沒(méi)人告訴你。

通常這沒(méi)什么,不過(guò)對(duì)于開(kāi)發(fā)者而言這又是我們必須面對(duì)的問(wèn)題。

一個(gè)好的項(xiàng)目結(jié)構(gòu),不僅能消除重復(fù)代碼,提升系統(tǒng)穩(wěn)定性,改善系統(tǒng)的設(shè)計(jì),還能在將來(lái)更容易的擴(kuò)展。

多年以來(lái),我一直在處理重構(gòu)和遷移項(xiàng)目結(jié)構(gòu)糟糕、設(shè)計(jì)不合理的 node.js 項(xiàng)目。而這篇文章正是對(duì)我此前積累經(jīng)驗(yàn)的總結(jié)。

目錄結(jié)構(gòu)

下面是我所推薦的項(xiàng)目代碼組織方式。

它來(lái)自于我參與的項(xiàng)目的實(shí)踐,每個(gè)目錄模塊的功能與作用如下:

src
  │   app.js          # App 統(tǒng)一入口
  └───api             # Express route controllers for all the endpoints of the app
  └───config          # 環(huán)境變量和配置信息
  └───jobs            # 隊(duì)列任務(wù)(agenda.js)
  └───loaders         # 將啟動(dòng)過(guò)程模塊化
  └───models          # 數(shù)據(jù)庫(kù)模型
  └───services        # 存放所有商業(yè)邏輯
  └───subscribers     # 異步事件處理器
  └───types           # Typescript 的類(lèi)型聲明文件 (d.ts)

而且,這不僅僅只是代碼的組織方式...

3 層結(jié)構(gòu)

這個(gè)想法源自 關(guān)注點(diǎn)分離原則,把業(yè)務(wù)邏輯從 node.js API 路由中分離出去。

因?yàn)閷?lái)的某天,你可能會(huì)在 CLI 工具或是其他地方處理你的業(yè)務(wù)。當(dāng)然,也有可能不會(huì),但在項(xiàng)目中使用API調(diào)用的方式來(lái)處理自身的業(yè)務(wù)終究不是一個(gè)好主意...

不要在控制器中直接處理業(yè)務(wù)邏輯!!

在你的應(yīng)用中,你可能經(jīng)為了圖便利而直接的在控制器處理業(yè)務(wù)。不幸的是,這么做的話很快你將面對(duì)相面條一樣復(fù)雜的控制器代碼,“惡果”也會(huì)隨之而來(lái),比如在處理單元測(cè)試的時(shí)候不得不使用復(fù)雜的 requestresponse模擬。

同時(shí),在決定何時(shí)向客戶(hù)端返回響應(yīng),或希望在發(fā)送響應(yīng)之后再進(jìn)行一些處理的時(shí)候,將會(huì)變得很復(fù)雜。

請(qǐng)不要像下面例子這樣做.

route.post('/', async (req, res, next) => {

    // 這里推薦使用中間件或Joi 驗(yàn)證器
    const userDTO = req.body;
    const isUserValid = validators.user(userDTO)
    if(!isUserValid) {
      return res.status(400).end();
    }

    // 一堆義務(wù)邏輯代碼
    const userRecord = await UserModel.create(userDTO);
    delete userRecord.password;
    delete userRecord.salt;
    const companyRecord = await CompanyModel.create(userRecord);
    const companyDashboard = await CompanyDashboard.create(userRecord, companyRecord);

    ...whatever...

    // 這里是“優(yōu)化”,但卻搞亂了所有的事情
    // 向客戶(hù)端發(fā)送響應(yīng)...
    res.json({ user: userRecord, company: companyRecord });

    // 但這里的代碼仍會(huì)執(zhí)行 :(
    const salaryRecord = await SalaryModel.create(userRecord, companyRecord);
    eventTracker.track('user_signup',userRecord,companyRecord,salaryRecord);
    intercom.createUser(userRecord);
    gaAnalytics.event('user_signup',userRecord);
    await EmailService.startSignupSequence(userRecord)
  });
使用服務(wù)層(service)來(lái)處理業(yè)務(wù)

在單獨(dú)的服務(wù)層處理業(yè)務(wù)邏輯是推薦的做法。

這一層是遵循適用于 node.js 的 SOLID 原則的“類(lèi)”的集合

在這一層中,不應(yīng)該有任何形式的數(shù)據(jù)查詢(xún)操作。正確的做法是使用數(shù)據(jù)訪問(wèn)層.

從 express.js 路由中清理業(yè)務(wù)代碼。

服務(wù)層不應(yīng)包含 request 和 response。

服務(wù)層不應(yīng)返回任何與傳輸層關(guān)聯(lián)的數(shù)據(jù),如狀態(tài)碼和響應(yīng)頭。

示例

route.post('/', 
    validators.userSignup, // 中間件處理驗(yàn)證
    async (req, res, next) => {
      // 路由的實(shí)際責(zé)任
      const userDTO = req.body;

      // 調(diào)用服務(wù)層
      // 這里演示如何訪問(wèn)服務(wù)層
      const { user, company } = await UserService.Signup(userDTO);

      // 返回響應(yīng)
      return res.json({ user, company });
    });

下面是服務(wù)層示例代碼。

import UserModel from '../models/user';
  import CompanyModel from '../models/company';

  export default class UserService {

    async Signup(user) {
      const userRecord = await UserModel.create(user);
      const companyRecord = await CompanyModel.create(userRecord); // 依賴(lài)用戶(hù)的數(shù)據(jù)記錄 
      const salaryRecord = await SalaryModel.create(userRecord, companyRecord); // 依賴(lài)用戶(hù)與公司數(shù)據(jù)

      ...whatever

      await EmailService.startSignupSequence(userRecord)

      ...do more stuff

      return { user: userRecord, company: companyRecord };
    }
  }

在 Github 查看示例代碼

https://github.com/santiq/bulletproof-nodejs

使用發(fā)布/訂閱模式

嚴(yán)格來(lái)講發(fā)布/訂閱模型并不屬于 3 層結(jié)構(gòu)的范疇,但卻很實(shí)用。

這里有一個(gè)簡(jiǎn)單的 node.js API 用來(lái)創(chuàng)建用戶(hù),于此同時(shí)你可能還需要調(diào)用外部服務(wù)、分析數(shù)據(jù)、或發(fā)送一連串的郵件。很快,這個(gè)簡(jiǎn)單的原本用于創(chuàng)建用戶(hù)的函數(shù),由于充斥各種功能,代碼已經(jīng)超過(guò)了 1000 行。

現(xiàn)在是時(shí)候把這些功能都拆分為獨(dú)立功能了,這樣才能讓你的代碼繼續(xù)保持可維護(hù)性。

import UserModel from '../models/user';
  import CompanyModel from '../models/company';
  import SalaryModel from '../models/salary';

  export default class UserService() {

    async Signup(user) {
      const userRecord = await UserModel.create(user);
      const companyRecord = await CompanyModel.create(user);
      const salaryRecord = await SalaryModel.create(user, salary);

      eventTracker.track(
        'user_signup',
        userRecord,
        companyRecord,
        salaryRecord
      );

      intercom.createUser(
        userRecord
      );

      gaAnalytics.event(
        'user_signup',
        userRecord
      );

      await EmailService.startSignupSequence(userRecord)

      ...more stuff

      return { user: userRecord, company: companyRecord };
    }

  }

同步的調(diào)用依賴(lài)服務(wù)仍不是的解決辦法.

更好的做法是觸發(fā)事件,如:一個(gè)新用戶(hù)使用郵箱方式注冊(cè)了。

就這樣,創(chuàng)建用戶(hù)的Api 完成了它的功能,剩下的就交給訂閱事件的處理器來(lái)負(fù)責(zé)。

import UserModel from '../models/user';
  import CompanyModel from '../models/company';
  import SalaryModel from '../models/salary';

  export default class UserService() {

    async Signup(user) {
      const userRecord = await this.userModel.create(user);
      const companyRecord = await this.companyModel.create(user);
      this.eventEmitter.emit('user_signup', { user: userRecord, company: companyRecord })
      return userRecord
    }

  }

并且你可以把事件處理器分割為多個(gè)獨(dú)立的文件。

eventEmitter.on('user_signup', ({ user, company }) => {

    eventTracker.track(
      'user_signup',
      user,
      company,
    );

    intercom.createUser(
      user
    );

    gaAnalytics.event(
      'user_signup',
      user
    );
  })
eventEmitter.on('user_signup', async ({ user, company }) => {
    const salaryRecord = await SalaryModel.create(user, company);
  })
eventEmitter.on('user_signup', async ({ user, company }) => {
    await EmailService.startSignupSequence(user)
  })

你可以使用 try-catch 來(lái)包裹 await 語(yǔ)句,或 通過(guò) process.on('unhandledRejection',cb)的形式注冊(cè) 'unhandledPromise' 的事件處理器

依賴(lài)注入

依賴(lài)注入或者說(shuō)控制反轉(zhuǎn)(IoC) 是一個(gè)通用的模式,通過(guò) ‘注入’ 或者傳遞類(lèi)或函數(shù)中涉及的依賴(lài)項(xiàng)的構(gòu)造器,幫助你組織代碼。

例如,當(dāng)你為服務(wù)編寫(xiě)單元測(cè)試,或者在另外的上下文中使用服務(wù)時(shí),通過(guò)這種方式,注入依賴(lài)項(xiàng)就會(huì)變得很靈活。

不使用依賴(lài)注入的代碼

import UserModel from '../models/user';
  import CompanyModel from '../models/company';
  import SalaryModel from '../models/salary';  
  class UserService {
    constructor(){}
    Sigup(){
       //調(diào)用 UserMode, CompanyModel,等
      ...
    }
  }

手動(dòng)依賴(lài)注入的代碼

export default class UserService {
    constructor(userModel, companyModel, salaryModel){
      this.userModel = userModel;
      this.companyModel = companyModel;
      this.salaryModel = salaryModel;
    }
    getMyUser(userId){
      // 通過(guò)this獲取模型
      const user = this.userModel.findById(userId);
      return user;
    }
  }

現(xiàn)在你可以注入自定義的依賴(lài)。

import UserService from '../services/user';
  import UserModel from '../models/user';
  import CompanyModel from '../models/company';
  const salaryModelMock = {
    calculateNetSalary(){
      return 42;
    }
  }
  const userServiceInstance = new UserService(userModel, companyModel, salaryModelMock);
  const user = await userServiceInstance.getMyUser('12346');

一個(gè)服務(wù)可以擁有的依賴(lài)項(xiàng)數(shù)量是無(wú)限的,當(dāng)您添加一個(gè)新的服務(wù)時(shí)重構(gòu)它的每個(gè)實(shí)例是一個(gè)無(wú)聊且容易出錯(cuò)的任務(wù)。

這就是創(chuàng)建依賴(lài)注入框架的原因。

其思想是在類(lèi)中聲明依賴(lài)項(xiàng),當(dāng)需要該類(lèi)的實(shí)例時(shí),只需調(diào)用「服務(wù)定位器」。

我們可以參考一個(gè)在 nodejs 引入 D.I npm庫(kù)typedi的例子。

你可以在官方文檔上查看更多關(guān)于使用 typedi的方法

https://www.github.com/typestack/typedi

警告 typescript 例子

import { Service } from 'typedi';
  @Service()
  export default class UserService {
    constructor(
      private userModel,
      private companyModel, 
      private salaryModel
    ){}

    getMyUser(userId){
      const user = this.userModel.findById(userId);
      return user;
    }
  }

services/user.ts

現(xiàn)在,typedi將負(fù)責(zé)解析 UserService 所需的任何依賴(lài)項(xiàng)。

import { Container } from 'typedi';
  import UserService from '../services/user';
  const userServiceInstance = Container.get(UserService);
  const user = await userServiceInstance.getMyUser('12346');

濫用服務(wù)定位器是一種反面模式

在 Express.js 中使用依賴(lài)注入

在express.js中使用 D.I. 是 node.js 項(xiàng)目架構(gòu)的最后一個(gè)難題。

路由層

route.post('/', 
    async (req, res, next) => {
      const userDTO = req.body;

      const userServiceInstance = Container.get(UserService) // Service locator

      const { user, company } = userServiceInstance.Signup(userDTO);

      return res.json({ user, company });
    });

太棒了,項(xiàng)目看起來(lái)很棒!
它是如此有組織,以至于我現(xiàn)在就想寫(xiě)代碼了。

在github上查看源碼

https://github.com/santiq/bulletproof-nodejs

一個(gè)單元測(cè)試的例子

通過(guò)使用依賴(lài)注入和這些組織模式,單元測(cè)試變得非常簡(jiǎn)單。

您不必模擬 req/res 對(duì)象或要求(…)調(diào)用。

例子:注冊(cè)用戶(hù)方法的單元測(cè)試

tests/unit/services/user.js

import UserService from '../../../src/services/user';

  describe('User service unit tests', () => {
    describe('Signup', () => {
      test('Should create user record and emit user_signup event', async () => {
        const eventEmitterService = {
          emit: jest.fn(),
        };

        const userModel = {
          create: (user) => {
            return {
              ...user,
              _id: 'mock-user-id'
            }
          },
        };

        const companyModel = {
          create: (user) => {
            return {
              owner: user._id,
              companyTaxId: '12345',
            }
          },
        };

        const userInput= {
          fullname: 'User Unit Test',
          email: 'test@example.com',
        };

        const userService = new UserService(userModel, companyModel, eventEmitterService);
        const userRecord = await userService.SignUp(teamId.toHexString(), userInput);

        expect(userRecord).toBeDefined();
        expect(userRecord._id).toBeDefined();
        expect(eventEmitterService.emit).toBeCalled();
      });
    })
  })
定時(shí)任務(wù)

因此,既然業(yè)務(wù)邏輯封裝到了服務(wù)層中,那么在定時(shí)任務(wù)中使用它就更容易了。

您永遠(yuǎn)不應(yīng)該依賴(lài) node.js的 setTimeout或其他延遲執(zhí)行代碼的原生方法,而應(yīng)該依賴(lài)一個(gè)框架把你的定時(shí)任務(wù)和執(zhí)行持久化到數(shù)據(jù)庫(kù)。

這樣你就可以控制失敗的任務(wù)和成功的反饋信息。

我已經(jīng)寫(xiě)了一個(gè)關(guān)于這個(gè)的好的練習(xí),

查看我關(guān)于使用 node.js 最好的任務(wù)管理器 agenda.js 的指南.

https://softwareontheroad.com/nodejs-scalability-issues

配置項(xiàng)和私密信息

根據(jù) 應(yīng)用程序的12個(gè)因素 的概念,我們存儲(chǔ) API 密鑰和數(shù)據(jù)庫(kù)連接配置的方式是使用 .env文件。

創(chuàng)建一個(gè) .env文件,一定不要提交 (在你的倉(cāng)庫(kù)里要有一個(gè)包含默認(rèn)值的.env 文件),dotenv這個(gè)npm 包會(huì)加載 .env 文件,并把變量添加到 node.js 的 process.env對(duì)象中。

這些本來(lái)已經(jīng)足夠了,但是我喜歡添加一個(gè)額外的步驟。
擁有一個(gè) config/index.ts文件,在這個(gè)文件中,dotenv這個(gè)npm 包會(huì)加載 .env 文件,然后我使用一個(gè)對(duì)象存儲(chǔ)這些變量,至此我們有了一個(gè)結(jié)構(gòu)和代碼自動(dòng)加載。

config/index.js

const dotenv = require('dotenv');
  //config()方法會(huì)讀取你的 .env 文件,解析內(nèi)容,添加到 process.env。
  dotenv.config();

  export default {
    port: process.env.PORT,
    databaseURL: process.env.DATABASE_URI,
    paypal: {
      publicKey: process.env.PAYPAL_PUBLIC_KEY,
      secretKey: process.env.PAYPAL_SECRET_KEY,
    },
    paypal: {
      publicKey: process.env.PAYPAL_PUBLIC_KEY,
      secretKey: process.env.PAYPAL_SECRET_KEY,
    },
    mailchimp: {
      apiKey: process.env.MAILCHIMP_API_KEY,
      sender: process.env.MAILCHIMP_SENDER,
    }
  }

這樣可以避免代碼中充斥著 process.env.MY_RANDOM_VAR指令,并且通過(guò)自動(dòng)完成,您不必知道 .env 文件中是如何命名的。

在github 上查看源碼

https://github.com/santiq/bulletproof-nodejs

加載器

加載器源于 W3Tech microframework 但不依賴(lài)于他們擴(kuò)展。

這個(gè)想法是指,你可以拆分啟動(dòng)加載過(guò)程到可測(cè)試的獨(dú)立模塊中。

先來(lái)看一個(gè)傳統(tǒng)的 express.js 應(yīng)用的初始化示例:

const mongoose = require('mongoose');
  const express = require('express');
  const bodyParser = require('body-parser');
  const session = require('express-session');
  const cors = require('cors');
  const errorhandler = require('errorhandler');
  const app = express();

  app.get('/status', (req, res) => { res.status(200).end(); });
  app.head('/status', (req, res) => { res.status(200).end(); });
  app.use(cors());
  app.use(require('morgan')('dev'));
  app.use(bodyParser.urlencoded({ extended: false }));
  app.use(bodyParser.json(setupForStripeWebhooks));
  app.use(require('method-override')());
  app.use(express.static(__dirname + '/public'));
  app.use(session({ secret: process.env.SECRET, cookie: { maxAge: 60000 }, resave: false, saveUninitialized: false }));
  mongoose.connect(process.env.DATABASE_URL, { useNewUrlParser: true });

  require('./config/passport');
  require('./models/user');
  require('./models/company');
  app.use(require('./routes'));
  app.use((req, res, next) => {
    var err = new Error('Not Found');
    err.status = 404;
    next(err);
  });
  app.use((err, req, res) => {
    res.status(err.status || 500);
    res.json({'errors': {
      message: err.message,
      error: {}
    }});
  });

  ... more stuff 

  ... maybe start up Redis

  ... maybe add more middlewares

  async function startServer() {  app.listen(process.env.PORT, err => {
      if (err) {
        console.log(err);
        return;
      }
      console.log(`Your server is ready !`);
    });
  }

  // 啟動(dòng)服務(wù)器
  startServer();

天吶,上面的面條代碼,不應(yīng)該出現(xiàn)在你的項(xiàng)目中對(duì)吧。

再來(lái)看看,下面是一種有效處理初始化過(guò)程的示例:

const loaders = require('./loaders');
  const express = require('express');

  async function startServer() {

    const app = express();

    await loaders.init({ expressApp: app });

    app.listen(process.env.PORT, err => {
      if (err) {
        console.log(err);
        return;
      }
      console.log(`Your server is ready !`);
    });
  }

  startServer();

現(xiàn)在,各加載過(guò)程都是功能專(zhuān)一的小文件了。

loaders/index.js

import expressLoader from './express';
  import mongooseLoader from './mongoose';

  export default async ({ expressApp }) => {
    const mongoConnection = await mongooseLoader();
    console.log('MongoDB Intialized');
    await expressLoader({ app: expressApp });
    console.log('Express Intialized');

    // ... 更多加載器

    // ... 初始化 agenda
    // ... or Redis, or whatever you want
  }

express 加載器。

loaders/express.js

import * as express from 'express';
  import * as bodyParser from 'body-parser';
  import * as cors from 'cors';

  export default async ({ app }: { app: express.Application }) => {

    app.get('/status', (req, res) => { res.status(200).end(); });
    app.head('/status', (req, res) => { res.status(200).end(); });
    app.enable('trust proxy');

    app.use(cors());
    app.use(require('morgan')('dev'));
    app.use(bodyParser.urlencoded({ extended: false }));

    // ...More middlewares

    // Return the express app
    return app;
  })

mongo 加載器

loaders/mongoose.js

import * as mongoose from 'mongoose'
  export default async (): Promise => {
    const connection = await mongoose.connect(process.env.DATABASE_URL, { useNewUrlParser: true });
    return connection.connection.db;
  }

在 Github 查看更多加載器的代碼示例

https://github.com/santiq/bulletproof-nodejs

結(jié)語(yǔ)

我們深入的分析了 node.js 項(xiàng)目的結(jié)構(gòu),下面是一些可以分享給你的總結(jié):

使用3層結(jié)構(gòu)。

控制器中不要有任何業(yè)務(wù)邏輯代碼。

采用發(fā)布/訂閱模型來(lái)處理后臺(tái)異步任務(wù)。

使用依賴(lài)注入。

使用配置管理,避免泄漏密碼、密鑰等機(jī)密信息。

將 node.js 服務(wù)器的配置拆分為可獨(dú)立加載的小文件。

前往 Github 查看完整示例

https://github.com/santiq/bulletproof-nodejs

原文地址:https://dev.to/santypk4/bulletproof-node-js-project-architecture-4epf

譯文地址:https://learnku.com/nodejs/t/38129

更多編程相關(guān)知識(shí),請(qǐng)?jiān)L問(wèn):編程入門(mén)??!
網(wǎng)站名稱(chēng):詳解NodejsExpress.js項(xiàng)目架構(gòu)
文章起源:http://weahome.cn/article/cjdihh.html

其他資訊

在線咨詢(xún)

微信咨詢(xún)

電話咨詢(xún)

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部