MVC 模型上哪里抛出错误:服务还是控制器?

问题描述 投票:0回答:1

我正在使用 MVC 模型创建 Express 后端,但我仍然不知道在哪里抛出错误。

我正在使用 express-async-errorshttp-errors ,这样我就可以在任何地方

throw
出现错误,该错误将由我创建的中间件处理。

但我的问题是:

1 - 我

throw
完整的
Error
在我的服务上如下:

export default class UserService {

  public error(){
    throw createError(404,"Error Message")
  }
}

并且不要在我的

try/catch
 上创建 
Controller

2 - 我在我的服务上

throw
一个简单的
Error
或消息,并让控制器处理它:

服务

export default class UserService {

  public error(){
    throw Error("Error Message")
  }
}

控制器

export default class UserController {
 public simpleMethod(){
  try {
    userService.error()
  } catch (error:any) {
    throw createError(404,error.message)
  }

 }
}

3 - 或者只是

return
一个
value
类似的对象,具有名为
error
的属性,并让控制器处理一切,如下所示:

服务

export default class UserService {

  public error(){
    return {error:"Error Message"}
  }
}

控制器

export default class UserController {
  public simpleMethod() {
    const message = userService.error()

    if (message.error) {
      throw createError(404, message.error)
    }
  }
}

注意:我认为处理此问题的最佳方法是直接在服务上,因为您可以更具体地了解错误类型,但有些人建议在控制器上执行此操作,所以,正确的方法是什么处理这个?

javascript node.js typescript express error-handling
1个回答
0
投票

这是我花了很多时间的事情。我尝试过很多不同的方法,这对我来说是最有效的。我正在分享一个通过电子邮件注册租户的示例,以便您可以根据您的用例/API 进行相应的规划。

一开始可能看起来很吓人,因为它需要很多步骤来设置,但最好的部分是你只需要做一次。之后,您只需将代码文件复制粘贴到不同的项目中,它们就会完美地工作,因为这些代码文件将独立工作。

  1. 提出正确/固定的响应结构,无论它是错误还是成功响应。对我来说是
{
    "success": true,
    "message": "",
    "data": {...}
}
根据操作,

success
将是
true
false
message
将是解释响应/结果的正确消息,并且
data
将包含与响应相关的任何类型的相关数据负载(如果需要)。

  1. 创建一个中间件函数,它将能够处理从Controller抛出/返回的任何错误或任何成功对象(是的,对我来说控制器是最好的地方,因为它保存了整体业务逻辑)并且能够发送处理后得到正确的响应。
// put below code in a file named as response-handler.js

const { logError, logInfo } = require('../logger');
const { HttpError } = require('./error');

function sendSuccess(code, success, message, data) {
    return {
        code,
        success,
        message,
        data
    }
}

function sendError(code = 500, message = "something went wrong!") {
    throw new HttpError(code, false, message);
}

function handleResponse(result, req, res, next) {
    const { code, success, message, data } = result;
    
    if (code) {
        logInfo(`responded with ${code}: ${message}`)

        return res
        .status(code)
        .json({
            success,
            message,
            data
        })
    } else {
        logError(message);

        return res.status(500).json({
            success: false,
            message: 'something went wrong'
        })
    }
   
}

module.exports = {
    handleResponse,
    sendSuccess,
    sendError
};

因此,在我的代码库中,在控制器中,我将使用

sendSuccess
sendError
返回成功或错误对象,最终将由
handleResponse
处理以发送正确的响应。

  1. 创建一个将用于引发错误的自定义错误类。本机
    Error
    只能处理消息。但我们需要错误/状态代码、成功标志和消息才能正确发送响应。这样
// put the below code in error.js

class HttpError extends Error {
    constructor(code, success, message) {
        super(message);
        this.code = code;
        this.success = success;
        this.name = this.constructor.name;
        Error.captureStackTrace(this, this.constructor);
    }
}

module.exports = {
    HttpError
}

第 2 点中的

sendError
最终将使用
HttpError
来抛出错误。

  1. 在app.js中安装
    handleResponse
    中间件。
const authRoutes = require('./auth/route/auth.route');
const tenantRoutes = require('./tenant/route/tenant.route');
const expenseRoutes = require('./expense/route/expense.route');
const categoryRoutes = require('./category/route/category.route');
const { handleResponse } = require('./core/http/response-handler');

app.get("/ping", (req, res) => {
    res.status(200).json({
        success: true,
        message: 'hello from euro!'
    });
})
app.use(authRoutes);
app.use(tenantRoutes);
app.use(expenseRoutes);
app.use(categoryRoutes);

app.use(handleResponse); // <- handleResponse must be the last middleware, this is important, so that it can handle all the routes defined above it
  1. 配置路线
// put below code in the route tenant.route.js

const router = require('express').Router();
const API_ENDPOINTS = require('../../support/request-mapper');
const { registerUserViaEmail } = require('../controller/tenant.controller');

router
    .post(
        API_ENDPOINTS.REGISTER_VIA_EMAIL,
        [],
        (req, res, next) => registerUserViaEmail(req,res).then(next).catch(next)
    )

module.exports = router;

registerUserViaEmail(req,res).then(next).catch(next)
是这个设置的关键。简单来说,无论
then
内部发生什么好事(
catch
)或坏事(
registerUserViaEmail
),都让
handleResponse
来处理。一旦我向您展示控制器代码,您就会明白这一点。

  1. 最后这是控制器代码
//put the below code in tenant.controller.js

const { passwordEncoder } = require('../../core/crypto/password');
const { sendSuccess, sendError } = require('../../core/http/response-handler');
const { logError } = require('../../core/logger');
const Tenant = require('../model/tenant');

async function registerUserViaEmail(req, res) {
    const tenant = await Tenant.findOne({ email: req.body.email }).exec();

    if (tenant) {
        sendError(409, "user already exists!");
    }

    try {
        await Tenant.create({
            name: req.body.name,
            email: req.body.email,
            password: await passwordEncoder(req.body.password),
            provider: 'BASIC'
        })
    } catch (error) {
        logError(error.message, error);
        sendError();
    }

    return sendSuccess(201, true, "user registered successfully!");
}

module.exports = {
    registerUserViaEmail
}

如您所见,我能够处理自定义/已处理错误(

401
重复用户错误)、未处理错误(
catch
块错误)和成功响应。

因此,一旦设置了基本文件,只有路由和控制器会因项目而异。

希望这能帮助您思考最适合您的框架。

© www.soinside.com 2019 - 2024. All rights reserved.