Node.JS服务层设计
Node.JS service layer design
我有一个非常简单的 express js 服务器,它接受来自客户端的请求,执行一些业务逻辑并响应客户端。请求-响应管道由控制器处理,业务逻辑在服务层内执行。该代码可以正常运行,但我不确定我从服务层向控制器发送 return 错误的方式是否正确。
控制器看起来像这样:
async createAnOrder(req, res, next) {
try {
// Input validations
if (!req.body.name){
throw new ClientFacingError(ERROR_CODES.BAD_REQUEST, "Name is missing")
}
let newOrder = new Order();
let validationDictionary = new ValidationDictionary();
if (!await orderService.createOrder(newOrder, validationDictionary)){
return next(new ClientFacingError(ERROR_CODES.BAD_REQUEST, validationDictionary.getErrorMessage()));
}
res.json({
orderId: newOrder.id
})
} catch (err) {
if (err instanceof ClientFacingError) {
res.status(err.code).json({ message: err.message })
} else {
res.status(500).json({ message: "Internal Error" })
}
}
}
订单服务看起来像这样:
async createOrder(newOrder, validationDictionary) {
// Business logic validation
if (await orderDb.hasOrder(newOrder)) {
validationDictionary.addError("Invalid Order", "Order already exists");
return false;
}
await orderDb.createOrder(newOrder);
return true;
}
我正在努力将业务层与其他所有事物之间的关注点分离。我还想要服务层方法和控制器之间的智能合约。
我想我想做的是:
- 输入验证发生在控制器(基础设施)层,业务相关验证仅发生在服务层
- 服务层方法return如果操作成功则为真,如果存在业务验证错误则为假。
- 服务层方法抛出的任何错误都是运行时错误,这些错误会被控制器捕获并return作为内部错误发送给客户端。
另一种方式是坚持CQS原则。服务层方法不应该 return 一个值,而是抛出一个异常来指示失败。这种方法的问题是控制器没有关于错误消息是否可以 returned 到客户端的上下文。我也许能够从服务层抛出 ClientFacingError 异常,控制器可以使用该异常来决定是否可以将错误消息 returned 发送给客户端,但这感觉就像我将服务层耦合到基础设施层。
您的关注点很好,方法也不错,我补充几点:
validation
可以作为服务的一部分,与主逻辑分离。所以沿着 createOrder
你也会有一个 validateOrder
(在你的服务中)。从您的控制器中,您可以像这样使用它:
控制器:
try {
...
orderService.validateOrder(order);
orderService.createOrder(order);
...
} catch (e) {
//handle e
}
解决此问题的方法是抛出异常(如我的示例),return 布尔值 + 错误代码、消息...的混合,或者简单地将其嵌入 createOrder
.这取决于您的项目需求和您的品味。
如果出现问题,我会通过 orderService
中的简明消息和类型抛出异常。代码会更优雅。如果一切顺利,return 实际新创建的订单对象也会很棒,但布尔值一开始就足够了。 (甚至无效,考虑到你抛出异常)。
在您的控制器中捕获异常,记录它们,向客户端发送适当的响应。
我有一个非常简单的 express js 服务器,它接受来自客户端的请求,执行一些业务逻辑并响应客户端。请求-响应管道由控制器处理,业务逻辑在服务层内执行。该代码可以正常运行,但我不确定我从服务层向控制器发送 return 错误的方式是否正确。
控制器看起来像这样:
async createAnOrder(req, res, next) {
try {
// Input validations
if (!req.body.name){
throw new ClientFacingError(ERROR_CODES.BAD_REQUEST, "Name is missing")
}
let newOrder = new Order();
let validationDictionary = new ValidationDictionary();
if (!await orderService.createOrder(newOrder, validationDictionary)){
return next(new ClientFacingError(ERROR_CODES.BAD_REQUEST, validationDictionary.getErrorMessage()));
}
res.json({
orderId: newOrder.id
})
} catch (err) {
if (err instanceof ClientFacingError) {
res.status(err.code).json({ message: err.message })
} else {
res.status(500).json({ message: "Internal Error" })
}
}
}
订单服务看起来像这样:
async createOrder(newOrder, validationDictionary) {
// Business logic validation
if (await orderDb.hasOrder(newOrder)) {
validationDictionary.addError("Invalid Order", "Order already exists");
return false;
}
await orderDb.createOrder(newOrder);
return true;
}
我正在努力将业务层与其他所有事物之间的关注点分离。我还想要服务层方法和控制器之间的智能合约。
我想我想做的是:
- 输入验证发生在控制器(基础设施)层,业务相关验证仅发生在服务层
- 服务层方法return如果操作成功则为真,如果存在业务验证错误则为假。
- 服务层方法抛出的任何错误都是运行时错误,这些错误会被控制器捕获并return作为内部错误发送给客户端。
另一种方式是坚持CQS原则。服务层方法不应该 return 一个值,而是抛出一个异常来指示失败。这种方法的问题是控制器没有关于错误消息是否可以 returned 到客户端的上下文。我也许能够从服务层抛出 ClientFacingError 异常,控制器可以使用该异常来决定是否可以将错误消息 returned 发送给客户端,但这感觉就像我将服务层耦合到基础设施层。
您的关注点很好,方法也不错,我补充几点:
validation
可以作为服务的一部分,与主逻辑分离。所以沿着createOrder
你也会有一个validateOrder
(在你的服务中)。从您的控制器中,您可以像这样使用它:
控制器:
try {
...
orderService.validateOrder(order);
orderService.createOrder(order);
...
} catch (e) {
//handle e
}
解决此问题的方法是抛出异常(如我的示例),return 布尔值 + 错误代码、消息...的混合,或者简单地将其嵌入 createOrder
.这取决于您的项目需求和您的品味。
如果出现问题,我会通过
orderService
中的简明消息和类型抛出异常。代码会更优雅。如果一切顺利,return 实际新创建的订单对象也会很棒,但布尔值一开始就足够了。 (甚至无效,考虑到你抛出异常)。在您的控制器中捕获异常,记录它们,向客户端发送适当的响应。