Nodejs 使用依赖注入模式解决 Express.js 中路由中间件依赖的问题

Nodejs 使用依赖注入模式解决 Express.js 中路由中间件依赖的问题

http://zihua.li/2014/03/using-dependency-injection-to-optimise-express-middlewares/

8 回复

Nodejs 使用依赖注入模式解决 Express.js 中路由中间件依赖的问题

在使用 Express.js 构建 Web 应用程序时,我们常常需要处理各种中间件(如认证、日志记录等)。这些中间件通常会引入一些依赖项,比如数据库连接、缓存服务等。如果没有妥善管理这些依赖项,代码可能会变得难以维护和测试。依赖注入(Dependency Injection, DI)是一种有效的方法来解耦这些依赖项。

什么是依赖注入?

依赖注入是一种设计模式,通过它我们可以将对象的依赖关系从代码内部提取出来,并通过外部手段提供给对象。这样可以使得代码更加模块化、可测试且易于维护。

示例场景

假设我们正在开发一个简单的博客应用,其中有一个中间件用于检查用户是否已登录。这个中间件需要访问数据库来验证用户的登录状态。如果不使用依赖注入,我们的代码可能看起来像这样:

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

const app = express();

function checkAuth(req, res, next) {
    const userId = req.headers['user-id'];
    db.getUser(userId)
        .then(user => {
            if (user && user.loggedIn) {
                next();
            } else {
                res.status(401).send('Unauthorized');
            }
        })
        .catch(err => {
            res.status(500).send('Internal Server Error');
        });
}

app.use(checkAuth);

app.get('/', (req, res) => {
    res.send('Welcome to the blog!');
});

app.listen(3000);

使用依赖注入优化

我们可以使用依赖注入模式来解耦 checkAuth 中间件和数据库连接。具体做法是将数据库对象作为参数传递给中间件函数。

首先,我们需要创建一个依赖注入容器来管理这些依赖项:

// container.js
class Container {
    constructor() {
        this.dependencies = {};
    }

    register(name, factory) {
        this.dependencies[name] = factory;
    }

    resolve(name) {
        return this.dependencies[name]();
    }
}

module.exports = new Container();

然后,我们可以在应用启动时注册这些依赖项:

// main.js
const container = require('./container');
const db = require('./db');

container.register('db', () => db);

const app = express();

const checkAuth = (req, res, next) => {
    const userId = req.headers['user-id'];
    container.resolve('db').getUser(userId)
        .then(user => {
            if (user && user.loggedIn) {
                next();
            } else {
                res.status(401).send('Unauthorized');
            }
        })
        .catch(err => {
            res.status(500).send('Internal Server Error');
        });
};

app.use(checkAuth);

app.get('/', (req, res) => {
    res.send('Welcome to the blog!');
});

app.listen(3000);

通过这种方式,我们将 checkAuth 中间件与数据库的直接依赖解耦,使得中间件更易于测试和复用。

总结

依赖注入模式可以帮助我们更好地管理和解耦 Express.js 应用中的中间件依赖项。通过将依赖项的创建和管理分离出来,我们可以使代码更加模块化、可测试和易于维护。


文章举的例子实际上是滥用中间件而已,比如loadUser和role(‘admin’)这两个中间件可以重构为一个rbac的中间件来解决

我感觉滥用中间件才是问题的核心

摘录express 4.0文档里的一段话

The “mount” path is stripped and is not visible to the middleware function. The main effect of this feature is that mounted middleware may operate without code changes regardless of its “prefix” pathname.

除了文字原本的意思之外,我的理解就是中间件不应该对上下文环境太敏感,一旦出现了几个中间件之间依赖的关系,那就应该检讨是否中间件的设计和抽象出问题了,或者就不该用中间件来解决

确实如此。不过这两个中间件都是摘自 TJ 写的演示代码,虽然考虑到他本来就是为了演示 Route Specific Middleware 的特性所以才把载入用户和验证权限分成两个中间件来写,但就我自身体验而言,中间件的依赖关系并不少见。至于“滥用”其实很难界定,越细分的中间件越灵活。

是的,这也是我开发 express-di 的目的——使用另一种模式代替中间件。

不错!其实也可以考虑以trait或mixin的方式组合中间件。

听起来很有意思,有实现可以看吗?

依赖注入(Dependency Injection, DI)是一种设计模式,用于将对象的依赖关系从代码内部转移到外部。在Express.js中,路由中间件通常会直接引用一些服务或功能模块,这使得测试变得困难且代码耦合度高。通过使用依赖注入模式,我们可以更灵活地管理和替换这些依赖。

下面是一个简单的例子来展示如何在Express.js应用中使用依赖注入模式来管理路由中间件的依赖:

示例代码

假设我们有一个简单的Express应用,其中有一个路由需要使用一个数据库服务。

定义数据库服务

// dbService.js
class DatabaseService {
  constructor() {
    // 这里可以是连接到数据库的逻辑
  }

  async findUserById(userId) {
    // 模拟数据库查询
    return { id: userId, name: 'John Doe' };
  }
}

module.exports = DatabaseService;

定义路由中间件

// userMiddleware.js
const DatabaseService = require('./dbService');

function getUserById(dbService) {
  return (req, res, next) => {
    const userId = req.params.id;
    dbService.findUserById(userId)
      .then(user => {
        req.user = user;
        next();
      })
      .catch(next);
  };
}

module.exports = getUserById;

创建Express应用并使用中间件

// app.js
const express = require('express');
const getUserById = require('./userMiddleware');
const DatabaseService = require('./dbService');

const app = express();

const dbService = new DatabaseService();

app.get('/users/:id', getUserById(dbService), (req, res) => {
  res.json(req.user);
});

app.listen(3000, () => console.log('Server running on port 3000'));

解释

  1. 定义数据库服务DatabaseService 是一个简单的模拟数据库服务。
  2. 定义路由中间件getUserById 函数接受 dbService 作为参数,并返回一个中间件函数。这样我们可以在创建中间件时传入不同的服务实例。
  3. 创建Express应用:在创建Express应用时,我们实例化了 DatabaseService 并将其传递给路由中间件。

通过这种方式,我们可以在不修改中间件逻辑的情况下,轻松更换或模拟数据库服务,从而提高代码的可测试性和灵活性。

回到顶部