Я создаю серверную часть с помощью 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)
}
}
}
Примечание. Я думал, что лучший способ справиться с этим — непосредственно в Службе, потому что вы можете более конкретно указать тип ошибки, но некоторые люди рекомендуют делать это в Контроллере, так как же правильно с этим справиться?
Этот вопрос касается потока управления и обработки исключений, он не относится только к упомянутым технологиям, его следует задать на сайте Softwareengineering.stackexchange.com
Это то, чему я уделил много времени. Я пробовал и экспериментировал с множеством разных подходов, и именно этот мне подходит лучше всего. Я делюсь примером регистрации арендатора по электронной почте, чтобы вы могли спланировать ее соответствующим образом для вашего варианта использования/API.
Поначалу это может показаться пугающим, поскольку для настройки требуется много шагов, но самое приятное то, что вам придется сделать это только один раз. После этого вы можете просто скопировать и вставить файлы кода в разные проекты, и они будут работать безупречно, поскольку эти файлы кода будут работать независимо.
{
"success": true,
"message": "",
"data": {...}
}
success
будет true
или false
в зависимости от операции, message
будет правильным сообщением, объясняющим ответ/результат, и data
будет содержать любые виды полезных данных, связанных с ответом, если это необходимо.
// 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
для отправки правильного ответа.
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
для выдачи ошибок.
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
// 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
разобраться с этим. Вы поймете это, когда я покажу вам код контроллера.
//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
довольно сбивают с толку, поскольку ни один из них ничего не отправляет — они возвращают объект или выдают исключение.
Вам следует использовать .then(…, …) вместо .then(…).catch(…)!
ОП уже использует http-ошибки, нет необходимости определять HttpError
самостоятельно. Кстати, captureStackTrace
больше не нужен при подклассах ES6, и я бы рекомендовал сначала сохранить параметр message
для замены Лискова.
Рассматривать нормальные ответы как ошибки в среде expressjs странно. Не уверен насчет последствий для производительности, но думаю, что позже это вам аукнется. Почему бы просто не использовать .then(handleResponse, handleError)
?
Я не думаю, что это отвечает на актуальный вопрос: следует ли выдавать (или sendError
вызывать) ошибки в службе или в контроллере?
@BlackList96, спасибо за ответ, и я не сомневаюсь, что это сработает для вас, я дал вам положительный голос, но не думаю, что это хороший способ поддерживать его в течение длительного времени, учитывая специфику, которую я хочу создать проект. Я имею в виду, что вы обрабатываете вещи, которые должны обрабатываться в service
непосредственно в controller
, и не очень хорошо их разделяете, не используете classes
и создаете функции, которые уже предоставляются пакетами, которые я использую.
@Bergi, вы создали комментарий, в котором говорится, что, как правило, служба не должна зависеть от того, где она используется, но контроллер должен иметь подсказку о статусе и ошибке. Можете ли вы привести мне пример в качестве ответа?
@Kobra Это очень простой пример. Конечно, я не помещаю все внутрь контроллера. Я использую контроллер скорее как менеджера, в распоряжении которого есть всевозможные необходимые провайдеры, а точнее сервисы.
@Bergi Спасибо за ваш вклад. Я не эксперт по JS, поэтому не знал понятий, которые вы объяснили. Но будем рады использовать их при необходимости.
@BlackList96, да, но это все равно не ответ на мой вопрос, друг :/
Ошибка 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 и внести небольшой запах кода.
Да, в моем случае я использую сервер только для HTTP. Поэтому, если вы считаете, что у вас есть лучший ответ/пример, дайте его как ответ, пожалуйста, я могу удалить свой ответ, если считаю, что он лучше моего.
В общем, служба должна быть независимой от того, где она используется, и она не должна знать, что она используется в конечной точке http или где-либо еще. На практике, если вы знаете, что реализуете микросервис только для http, вы можете уже дать контроллеру подсказку, какой код состояния использовать в его ответе, чтобы избежать ненужного дублирования.