如何避免在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
相关产品推荐
相关产品推荐

