我正在使用Express制作API,它集成了两个不同的平台,例如桥接器。假设我有一个产品控制器,它具有您期望的默认CRUD操作。
让我们创造一个产品。通常的方法是创建一个路由,自动注入req和res,如下所示:
app.post('/api/products', productsController.create)
在我的控制器中,我会创建一个这样的函数:
const create = (req, res) =>
...
从外部调用此方法会起作用,因为在函数内部我将从请求中提取参数,然后创建产品。
但是,我有另一个路由和另一个将产品从一个地方同步到另一个地方的功能,所以我需要在内部使用productsController.create,但我不能使用它,因为它需要req和res来创建产品。
哪种方法可以解决这个问题?
我可以改变我的控制器功能只接受处理过的字段,然后在路径内,我可以处理参数并调用函数。唯一的问题是路线会变得更大更丑,而现在每条路线只有一条路线。
或者我可以创建另一个模块来处理字段然后调用create函数,如下所示。路线内部有点小,但仍然比一条线路更糟糕。
const create = product =>
...
app.post('/api/products', (req, res) => {
let product = treatFields(req.params)
productsController.create(product)
// send the response
})
无论如何,有没有人知道这样做的好方法?
答案 0 :(得分:3)
在非平凡的Node.js API中,除了控制器之外,您通常还有一个服务层。因此,控制器只提取参数,有时可能验证它们。您的服务层执行业务逻辑。
像这样的结构:
server/
controllers/
products-controller - the REST router[1]
something-else-controller - another router[1]
services/
products-service - the "business logic" [2]
现在,您的路由器(上面标记为[1]),它们采用参数。例如。要获取产品,他们会获取产品ID或产品名称:
const router = require('express').Router();
const productsService = require('../services/products-service');
router.get('/products', (req, res, next) => {
const count = req.query.count ;
// maybe validate or something
if (!count) {
return next(new Error('count param mandatory'));
}
productsService.getAllProducts(count)
.then(products => res.json(products))
.catch(err => next(err));
});
router.get('/products/:id', (req, res, next) => {
const id = req.params.id;
if (id.length !== whatever ) {
return next(new Error('Id not lookin good'));
}
productsService.getProductById(id)
.then(product => res.json(product))
.catch(err => next(err));
});
// and "another" router file, maybe products in a category
router.get('/categories/:catId/products', (req, res, next) => {
const catId = req.params.catId;
productsService.getProductByCategory(catId)
.then(products => res.json(products))
.catch(err => next(err));
});
您的服务层执行所有数据库逻辑并可能进行“业务”验证(例如,确保电子邮件有效或产品在更新等时具有有效价格):
const productService = {
getAllProducts(count) {
return database.find(/*whatever*/)
.then(rawData => formatYourData(rawData)); // maybe strip private stuff, convert times to user's profile, whatever
// important thing is that this is a promise to be used as above
},
getProductById(id) {
if (!id) {
// foo
return Promise.reject(new Error('Whatever.'));
}
return database.findSomethingById(id)
.then(rawData => formatData(rawData)); // more of the same
},
getProductByCategory() {
return []:
}
}
现在,我已将双方的参数验证混合在一起。如果您希望REST(“web”)图层更干净,只需传递参数而无需检查,例如productService.getProducts(req.query.page, req.query.limit).then(result => res.json(result);
。并进行更多的服务检查。
我经常将我的服务分解为几个文件,有点像这样:
services/
product-service/
index.js // "barrel" export file for the whole service
get-product.js
get-products.js
create-product.js
delete-product.js
update-product.js
product.-utilsjs // common helpers, like the formatter thingy or mabye some db labels, constants and such, used in all of the service files.
这种方法使得whiole的东西更加可测试和可读。但是,更多的文件,但与通常的node_modules相比,它没有什么不妥便的东西:)
答案 1 :(得分:1)
首先,控制器不应该处理任何业务逻辑,它们旨在为用户提供与您的服务交互的接口,仅此而已,它们应该只验证接口本身(即查询或路径参数已设置)正确并在出现错误时决定HTTP响应代码)。我会处理逻辑,以返回承诺,例如:
function createProduct(Product){
return new Promise((resolve, reject)=>{
// run your validations and if there is an error
// if no error
resolve(product.id);
// else
reject(MY_ERROR_REASON);
});
}
// version 1
function createRoute(req, res){
const product = treatFields(req.params)
createProduct(product).then((id)=>{
res.send(id);
}).catch((e)=>{
res.status(400).send(e);
});
}
//version 2 (for node >7)
async function createRoute(req, res){
const product = treatFields(req.params)
try{
const id = await createProduct(product);
res.send(id);
}
catch(e){
res.status(400).send(e);
}
}
app.post('/api/products', productsController.createRoute);
答案 2 :(得分:1)
这个问题更多的是关于架构。一种可能的选择是您可以遵循分层体系结构并创建以下层。
现在您可以从任何控制器/测试中调用服务的功能。您可以根据需要推进应用程序的架构。
注意:您还可以合并我们在Node项目中主要执行的路由和控制器。