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

如何避免在Express.js控制器的每个方法中重复实例化服务与仓库?

优化Express项目中Controller层重复实例化Repository/Service的方案

我正在用Express.js开发餐厅食材CRUD的学习项目,基于Prisma实现了Repository、Service、Controller三层,但每个Controller方法里都重复实例化Repository和Service,代码冗余,想找更优的实例化方式。


现有代码

IngredientRepository

const prisma = require("../../prisma/client");

class IngredientRepository {
  async create({ name, price, image }) {
    return await prisma.ingredient.create({
      data: {
        name,
        price,
        image,
      },
    });
  }

  async findByName({ name }) {
    return await prisma.ingredient.findUnique({ where: { name } });
  }

  async findById({ id }) {
    return await prisma.ingredient.findUnique({
      where: { id },
    });
  }

  async updateImage({ id, image }) {
    return await prisma.ingredient.update({
      where: { id },
      data: {
        image,
      },
    });
  }
}

module.exports = IngredientRepository;

IngredientService

const Error = require("../middlewares/Error");
const DiskStorage = require("../providers/DiskStorage");

class IngredientService {
  constructor(ingredientRepository, userRepository) {
    this.ingredientRepository = ingredientRepository;
    this.userRepository = userRepository;
  }

  async create({ name, price, image, loggedUser }) {
    const userIsAdmin = await this.userRepository.findById({ loggedUser });

    if (userIsAdmin.admin) {
      throw Error("", 401);
    }

    const ingredient = await this.ingredientRepository.findByName({
      name: name.toLowerCase(),
    });

    if (ingredient) {
      throw new Error("Ingredient already exists");
    }

    return await this.ingredientRepository.create({
      name: name.toLowerCase(),
      price,
      image,
    });
  }

  async updateImage({ id, image }) {
    const diskStorage = new DiskStorage();

    const ingredient = await this.ingredientRepository.findById({ id: id.id });

    if (!ingredient) {
      throw new Error("This ingredient doesn't exist", 401);
    }

    if (ingredient.image) {
      await diskStorage.deleteFile(ingredient.image);
    }

    const filename = await diskStorage.saveFile(image);

    ingredient.image = filename;

    const updatedIngredient = await this.ingredientRepository.updateImage({
      id: id.id,
      image: ingredient.image,
    });
    return updatedIngredient;
  }
}

module.exports = IngredientService;

IngredientController(现有冗余版本)

const IngredientRepository = require("../repositories/IngredientRepository");
const UserRepository = require("../repositories/UserRepository");
const IngredientService = require("../services/IngredientService");

class IngredientController {
  async create(request, response) {
    const ingredientRepository = new IngredientRepository();
    const userRepository = new UserRepository();
    const ingredientService = new IngredientService(
      ingredientRepository,
      userRepository
    );

    const loggedUser = request.user.id;

    const { name, price, image } = request.body;

    const ingredientCreated = await ingredientService.create({
      loggedUser,
      name,
      price,
      image,
    });
    return response.json(ingredientCreated);
  }

  async updateImage(request, response) {
    const ingredientRepository = new IngredientRepository();
    const userRepository = new UserRepository();
    const ingredientService = new IngredientService(
      ingredientRepository,
      userRepository
    );

    const loggedUser = request.user.id;
    const id = request.params;
    const image = request.file.filename;

    const ingredientWithImageUpdated = await ingredientService.updateImage({
      loggedUser,
      id,
      image,
    });
    return response.json(ingredientWithImageUpdated);
  }
}

module.exports = IngredientController;

优化方案

方案1:Controller构造函数内初始化依赖

把Repository和Service的实例化逻辑放到Controller的构造函数中,每个Controller实例仅初始化一次,所有方法共享同一个Service实例。

修改后的IngredientController:

const IngredientRepository = require("../repositories/IngredientRepository");
const UserRepository = require("../repositories/UserRepository");
const IngredientService = require("../services/IngredientService");

class IngredientController {
  constructor() {
    const ingredientRepository = new IngredientRepository();
    const userRepository = new UserRepository();
    this.ingredientService = new IngredientService(ingredientRepository, userRepository);
  }

  async create(request, response) {
    const loggedUser = request.user.id;
    const { name, price, image } = request.body;

    const ingredientCreated = await this.ingredientService.create({
      loggedUser,
      name,
      price,
      image,
    });
    return response.json(ingredientCreated);
  }

  async updateImage(request, response) {
    const loggedUser = request.user.id;
    const id = request.params;
    const image = request.file.filename;

    const ingredientWithImageUpdated = await this.ingredientService.updateImage({
      loggedUser,
      id,
      image,
    });
    return response.json(ingredientWithImageUpdated);
  }
}

module.exports = IngredientController;

方案2:全局单例模式

如果Repository和Service不需要请求级别的隔离(无需每个请求生成新实例),可以将它们封装为单例,模块加载时初始化,后续直接引用。

1. 修改Repository导出为单例

// IngredientRepository.js
const prisma = require("../../prisma/client");

class IngredientRepository {
  // 原有方法不变
}

// 导出单例实例
module.exports = new IngredientRepository();

同理修改UserRepository.js的导出逻辑。

2. 修改Service导出为单例

// IngredientService.js
const Error = require("../middlewares/Error");
const DiskStorage = require("../providers/DiskStorage");
const ingredientRepository = require("../repositories/IngredientRepository");
const userRepository = require("../repositories/UserRepository");

class IngredientService {
  // 原有方法不变
}

// 导出单例实例
module.exports = new IngredientService(ingredientRepository, userRepository);

3. 简化后的Controller

const ingredientService = require("../services/IngredientService");

class IngredientController {
  async create(request, response) {
    const loggedUser = request.user.id;
    const { name, price, image } = request.body;

    const ingredientCreated = await ingredientService.create({
      loggedUser,
      name,
      price,
      image,
    });
    return response.json(ingredientCreated);
  }

  async updateImage(request, response) {
    const loggedUser = request.user.id;
    const id = request.params;
    const image = request.file.filename;

    const ingredientWithImageUpdated = await ingredientService.updateImage({
      loggedUser,
      id,
      image,
    });
    return response.json(ingredientWithImageUpdated);
  }
}

module.exports = IngredientController;

方案3:依赖注入容器(进阶)

若项目规模较大、依赖关系复杂,可引入轻量DI容器(如awilix)管理依赖的实例化与注入,彻底解耦各层依赖。

1. 安装依赖

npm install awilix

2. 创建DI容器配置

// src/container.js
const { createContainer, asClass, asValue } = require("awilix");
const prisma = require("./prisma/client");
const IngredientRepository = require("./repositories/IngredientRepository");
const UserRepository = require("./repositories/UserRepository");
const IngredientService = require("./services/IngredientService");

const container = createContainer();

container.register({
  prisma: asValue(prisma),
  ingredientRepository: asClass(IngredientRepository).singleton(),
  userRepository: asClass(UserRepository).singleton(),
  ingredientService: asClass(IngredientService).singleton()
});

module.exports = container;

3. 修改Repository支持构造函数注入

// src/repositories/IngredientRepository.js
class IngredientRepository {
  constructor({ prisma }) {
    this.prisma = prisma;
  }

  async create({ name, price, image }) {
    return await this.prisma.ingredient.create({
      data: {
        name,
        price,
        image,
      },
    });
  }

  // 其他方法同步替换为this.prisma调用
}

module.exports = IngredientRepository;

4. Controller中注入Service

// src/controllers/IngredientController.js
const container = require("../container");

class IngredientController {
  constructor({ ingredientService }) {
    this.ingredientService = ingredientService;
  }

  async create(request, response) {
    const loggedUser = request.user.id;
    const { name, price, image } = request.body;

    const ingredientCreated = await this.ingredientService.create({
      loggedUser,
      name,
      price,
      image,
    });
    return response.json(ingredientCreated);
  }

  async updateImage(request, response) {
    const loggedUser = request.user.id;
    const id = request.params;
    const image = request.file.filename;

    const ingredientWithImageUpdated = await this.ingredientService.updateImage({
      loggedUser,
      id,
      image,
    });
    return response.json(ingredientWithImageUpdated);
  }
}

// 从容器解析Controller实例
module.exports = container.resolve(IngredientController);

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 14:09:26