【问题标题】:Node.JS service layer designNode.JS 服务层设计
【发布时间】:2021-06-24 03:04:14
【问题描述】:

我有一个非常简单的 express js 服务器,它接受来自客户端的请求,执行一些业务逻辑并响应客户端。请求-响应管道由控制器处理,业务逻辑在服务层内执行。代码可以正常运行,但我不确定从服务层向控制器返回错误的方式是否正确。

控制器看起来像这样:

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" })
        }
    }
}

orderService 看起来像这样:

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;
}

我试图将业务层与其他所有方面的关注点分离。我还想要服务层方法和控制器之间的智能合约。

我想我想做的是:

  1. 输入验证发生在控制器(基础设施)层,业务相关的验证只发生在服务层
  2. 如果操作成功,服务层方法返回 true,如果存在业务验证错误,则返回 false。
  3. 服务层方法抛出的任何错误都是运行时错误,这些错误会被控制器捕获并作为内部错误返回给客户端。

另一种方法是遵守 CQS 原则。服务层方法不应返回值,而是抛出异常以指示失败。这种方法的问题是控制器没有关于是否可以将错误消息返回给客户端的上下文。我可能能够从服务层抛出 ClientFacingError 异常,控制器可以使用该异常来决定是否可以将错误消息返回给客户端,但这感觉就像我将服务层耦合到基础设施层。

【问题讨论】:

    标签: node.js error-handling architecture domain-driven-design design-by-contract


    【解决方案1】:

    你有很好的顾虑和很好的方法,让我补充一些想法:

    • validation 可以是服务的一部分,与主逻辑分离。因此,沿着createOrder,您还将拥有validateOrder(在您的服务中)。在您的控制器中,您可以像这样使用它:

    控制器:

    try {
        ...
        orderService.validateOrder(order);
        orderService.createOrder(order);
        ...
    } catch (e) { 
        //handle e
    }
    

    解决此问题的方法是抛出异常(如我的示例中),返回布尔值 + 错误代码的混合,消息...,或简单地将其嵌入 createOrder。这取决于您的项目需求和品味。

    • 如果出现问题,我会用简洁的消息抛出异常,并从orderService 键入。代码会更优雅。如果一切顺利,返回实际新创建的订单对象也很好,但一开始布尔值就足够了。 (甚至无效,考虑到你抛出异常)。

    • 在控制器中捕获异常,记录它们,向客户端发送适当的响应。

    【讨论】:

    • 很棒的方法。您是否建议定义服务特定的异常类型,以便控制器可以区分其他错误?
    • 关于这一点,我想说控制器的类型无关紧要,但要获得正确的消息以在日志中打印并发送回客户端。如果您在其他服务中使用您的服务,它可能会根据错误采取不同的操作。因此,这将有助于扩展 Error。像 OrderDuplicateError > ServiceError > Error
    猜你喜欢
    • 1970-01-01
    • 2011-06-16
    • 2017-01-31
    • 2013-01-15
    • 2012-11-26
    • 1970-01-01
    • 1970-01-01
    • 2012-12-20
    • 2011-09-25
    相关资源
    最近更新 更多