Куда выдавать ошибки в модели MVC: сервис или контроллер?

Я создаю серверную часть с помощью Express, используя модель MVC, но до сих пор не знаю, куда выдавать ошибки.

Я использую express-async-errors и http-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)
    }
  }
}

Примечание. Я думал, что лучший способ справиться с этим — непосредственно в Службе, потому что вы можете более конкретно указать тип ошибки, но некоторые люди рекомендуют делать это в Контроллере, так как же правильно с этим справиться?

В общем, служба должна быть независимой от того, где она используется, и она не должна знать, что она используется в конечной точке http или где-либо еще. На практике, если вы знаете, что реализуете микросервис только для http, вы можете уже дать контроллеру подсказку, какой код состояния использовать в его ответе, чтобы избежать ненужного дублирования.

Bergi 05.04.2024 08:22

Этот вопрос касается потока управления и обработки исключений, он не относится только к упомянутым технологиям, его следует задать на сайте Softwareengineering.stackexchange.com

Alykam Burdzaki 12.04.2024 14:41
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
1
2
281
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Это то, чему я уделил много времени. Я пробовал и экспериментировал с множеством разных подходов, и именно этот мне подходит лучше всего. Я делюсь примером регистрации арендатора по электронной почте, чтобы вы могли спланировать ее соответствующим образом для вашего варианта использования/API.

Поначалу это может показаться пугающим, поскольку для настройки требуется много шагов, но самое приятное то, что вам придется сделать это только один раз. После этого вы можете просто скопировать и вставить файлы кода в разные проекты, и они будут работать безупречно, поскольку эти файлы кода будут работать независимо.

  1. Придумайте правильную/фиксированную структуру ответа, независимо от того, является ли это ответом об ошибке или успешным ответом. Для меня это было
{
    "success": true,
    "message": "",
    "data": {...}
}

success будет true или false в зависимости от операции, message будет правильным сообщением, объясняющим ответ/результат, и data будет содержать любые виды полезных данных, связанных с ответом, если это необходимо.

  1. Создайте функцию промежуточного программного обеспечения, которая сможет обрабатывать любую ошибку или любой успешный объект, выданный/возвращенный из контроллера (и да, для меня контроллер — лучшее место, поскольку он содержит общую бизнес-логику) и сможет отправлять правильный ответ. после его обработки.
// 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
}

sendError в пункте 2 в конечном итоге будет использовать HttpError для выдачи ошибок.

  1. Подключите промежуточное ПО handleResponse в app.js.
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 ошибка блока) и успешный ответ.

Итак, как только вы настроите базовые файлы, от проекта к проекту будут меняться только маршруты и контроллеры.

Надеюсь, это поможет вам подумать о структуре, которая подойдет вам лучше всего.

Имена sendSuccess и sendError довольно сбивают с толку, поскольку ни один из них ничего не отправляет — они возвращают объект или выдают исключение.

Bergi 05.04.2024 08:16

Вам следует использовать .then(…, …) вместо .then(…).catch(…)!

Bergi 05.04.2024 08:16

ОП уже использует http-ошибки, нет необходимости определять HttpError самостоятельно. Кстати, captureStackTrace больше не нужен при подклассах ES6, и я бы рекомендовал сначала сохранить параметр message для замены Лискова.

Bergi 05.04.2024 08:17

Рассматривать нормальные ответы как ошибки в среде expressjs странно. Не уверен насчет последствий для производительности, но думаю, что позже это вам аукнется. Почему бы просто не использовать .then(handleResponse, handleError)?

Bergi 05.04.2024 08:17

Я не думаю, что это отвечает на актуальный вопрос: следует ли выдавать (или sendError вызывать) ошибки в службе или в контроллере?

Bergi 05.04.2024 08:17

@BlackList96, спасибо за ответ, и я не сомневаюсь, что это сработает для вас, я дал вам положительный голос, но не думаю, что это хороший способ поддерживать его в течение длительного времени, учитывая специфику, которую я хочу создать проект. Я имею в виду, что вы обрабатываете вещи, которые должны обрабатываться в service непосредственно в controller, и не очень хорошо их разделяете, не используете classes и создаете функции, которые уже предоставляются пакетами, которые я использую.

Kobra 05.04.2024 14:03

@Bergi, вы создали комментарий, в котором говорится, что, как правило, служба не должна зависеть от того, где она используется, но контроллер должен иметь подсказку о статусе и ошибке. Можете ли вы привести мне пример в качестве ответа?

Kobra 05.04.2024 14:04

@Kobra Это очень простой пример. Конечно, я не помещаю все внутрь контроллера. Я использую контроллер скорее как менеджера, в распоряжении которого есть всевозможные необходимые провайдеры, а точнее сервисы.

BlackList96 06.04.2024 17:00

@Bergi Спасибо за ваш вклад. Я не эксперт по JS, поэтому не знал понятий, которые вы объяснили. Но будем рады использовать их при необходимости.

BlackList96 06.04.2024 17:07

@BlackList96, да, но это все равно не ответ на мой вопрос, друг :/

Kobra 06.04.2024 23:46
Ответ принят как подходящий

Ошибка throwed зависит от того, является ли это полным HTTP приложением или нет.

1 — Полный HTTP API/сервер.

Если это API, используемый только для HTTP запросов, вам следует throw Ошибки в Service Layer, как и в первом примере моего вопроса, потому что:

  • Можно кинуть более конкретные status и messages

  • Ваш Controller станет чище

  • Controller не обязательно всегда иметь блок try/catch для устранения ошибки.

  • Ошибки Service обрабатываются одинаково во всем вашем приложении.

  • Пусть слой Controller просто обрабатывает requisition/response, потому что для этого он и создан!

2. Не полный HTTP API/сервер

Если он используется не только для HTTP запросов, лучший способ - это throw Ошибка на Service Layer только с конкретными Error Code и message, и позволить Controller обработать переданную ему ошибку и решить, что status дать и как это сделать. Отправь это.

В данном случае это лучший вариант, потому что Service можно использовать в других местах вашего приложения, а не только в HTTP Controllers.

Более конкретные сообщения, да. Коды ошибок тоже. Но код состояния ответа HTTP обычно должен находиться на уровне запроса/ответа, который является контроллером, поэтому смешивайте его только в том случае, если а) ваш сервис используется только контроллерами HTTP или б) если преимущества от № 2 и № 3 достаточно велики. чтобы нарушить SRP и внести небольшой запах кода.

Bergi 18.04.2024 22:40

Да, в моем случае я использую сервер только для HTTP. Поэтому, если вы считаете, что у вас есть лучший ответ/пример, дайте его как ответ, пожалуйста, я могу удалить свой ответ, если считаю, что он лучше моего.

Kobra 18.04.2024 22:44

Другие вопросы по теме