You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

NodeJS/Express微服务中工厂类实例通过中间件传递的优化问询

优化Express中间件依赖传递的TypeDI方案

看起来你已经在用TypeDI做依赖注入,但不想靠res.locals来传递中间件间的依赖——这个痛点太常见了,耦合Express的响应对象不仅让代码难测试,还会给后续维护埋坑。下面给你几个更优雅的方案,完全基于TypeDI的特性来优化你的代码结构:


方案一:请求作用域服务 + 控制器类注入(最推荐)

这个方案彻底解耦Express的res.locals,让TypeDI全权管理依赖传递,代码更干净、更易测试。

1. 把AbstractFactory改为请求作用域

首先给你的工厂类加上请求作用域配置,确保每个请求都能拿到独立的工厂实例:

import { Service, Scope } from 'typedi';

@Service({ scope: Scope.REQUEST })
export default class AbstractFactory {
  constructor(
    private _Client: extClient,
    private _Class: extClass,
    private _Response: extResponse,
  ) {}

  getClient(): extClient {
    return this._Client;
  }

  getClass(): extClass {
    return this._Class;
  }

  getResponse(): extResponse {
    return this._Response;
  }
}

2. 重构控制器为类,直接注入工厂

把控制器改成类,让TypeDI自动注入当前请求的工厂实例,无需手动传递:

import { Service } from 'typedi';
import { Request, Response, NextFunction } from 'express';

@Service()
export class ModelController {
  // TypeDI会自动注入当前请求的AbstractFactory实例
  constructor(private readonly factory: AbstractFactory) {}

  validateData() {
    return (req: Request, res: Response, next: NextFunction) => {
      const classInstance = this.factory.getClass();
      const isValid = classInstance.validate(req.body);
      
      if (!isValid) {
        return res.status(400).json({ error: 'Invalid data' });
      }
      next();
    };
  }

  sendData() {
    return (req: Request, res: Response, next: NextFunction) => {
      const clientInstance = this.factory.getClient();
      res.locals.processedData = clientInstance.process(req.body);
      next();
    };
  }

  sendResponse() {
    return (req: Request, res: Response) => {
      const responseInstance = this.factory.getResponse();
      const formattedResponse = responseInstance.format(res.locals.processedData);
      res.json(formattedResponse);
    };
  }
}

3. 配置Express请求容器中间件(关键步骤)

必须给每个请求创建独立的TypeDI容器,否则请求作用域的服务会串用:

import express from 'express';
import { Container } from 'typedi';
import crypto from 'crypto';

const app = express();

app.use((req, res, next) => {
  // 生成唯一请求ID作为容器标识
  req.id = crypto.randomUUID();
  const requestContainer = Container.of(req.id);
  req.container = requestContainer;

  // 请求结束后清理容器,避免内存泄漏
  res.on('finish', () => Container.reset(req.id));
  next();
});

4. 简化路由配置

从TypeDI容器中获取控制器实例,直接使用其方法:

import { Container } from 'typedi';
import ModelRoute from './your-route-file';
import { ModelController } from './model-controller';

const modelController = Container.get(ModelController);

ModelRoute.post(
  '/',
  modelController.validateData(),
  modelController.sendData(),
  modelController.sendResponse()
);

优点:

  • 完全解耦Express的res.locals,依赖注入逻辑清晰
  • 请求作用域确保每个请求的实例独立,避免并发问题
  • 控制器类易于单元测试,可轻松mock工厂依赖

方案二:高阶函数包装 + 请求级依赖传递(快速适配方案)

如果你暂时不想重构控制器结构,可以用这个方案快速替换res.locals,减少耦合。

1. 修改控制器方法,让它们接收依赖参数

把控制器的静态方法改成接收依赖的函数,返回中间件:

import { Request, Response, NextFunction } from 'express';
import { Container } from 'typedi';
import AbstractFactory from './abstract-factory';

export class ModelController {
  static factoryCreate() {
    return (req: Request, res: Response, next: NextFunction) => {
      const factory = Container.get(AbstractFactory);
      // 把依赖挂载到req的专属属性,避免污染res.locals
      req.deps = {
        classInstance: factory.getClass(),
        clientInstance: factory.getClient(),
        responseInstance: factory.getResponse()
      };
      next();
    };
  }

  static validateData(classInstance: extClass) {
    return (req: Request, res: Response, next: NextFunction) => {
      const isValid = classInstance.validate(req.body);
      if (!isValid) return res.status(400).json({ error: 'Invalid data' });
      next();
    };
  }

  static sendData(clientInstance: extClient) {
    return (req: Request, res: Response, next: NextFunction) => {
      res.locals.processedData = clientInstance.process(req.body);
      next();
    };
  }

  static sendResponse(responseInstance: extResponse) {
    return (req: Request, res: Response) => {
      res.json(responseInstance.format(res.locals.processedData));
    };
  }
}

2. 创建包装函数提取依赖

写一个简单的包装函数,从req.deps中提取依赖并传递给控制器方法:

const wrapMiddleware = <T>(
  controllerFn: (dep: T) => (req: Request, res: Response, next: NextFunction) => void,
  depExtractor: (deps: any) => T
) => {
  return (req: Request, res: Response, next: NextFunction) => {
    const dep = depExtractor(req.deps);
    const middleware = controllerFn(dep);
    middleware(req, res, next);
  };
};

3. 路由配置

用包装函数把依赖注入到后续中间件:

ModelRoute.post(
  '/',
  ModelController.factoryCreate(),
  wrapMiddleware(ModelController.validateData, deps => deps.classInstance),
  wrapMiddleware(ModelController.sendData, deps => deps.clientInstance),
  wrapMiddleware(ModelController.sendResponse, deps => deps.responseInstance)
);

优点:

  • 不需要改动控制器的静态方法结构,快速替换res.locals
  • 依赖传递更明确,不会和res.locals中的其他数据混淆

缺点:

  • 仍依赖Express的req对象,耦合度比方案一高
  • 测试时需要模拟req.deps,不如类控制器好测试

总结

如果你的项目需要长期维护,优先选方案一,它完全发挥了TypeDI的优势,代码可维护性和可测试性都更高。如果只是临时优化,方案二可以快速见效,最小化代码改动。

内容的提问来源于stack exchange,提问作者David Zhang

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.09 13:47:35