Рефакторинг додатку за MVC архітектурою
У backend розробці прийнято не писати весь код в одному файлі, а розділяти його на блоки в залежності від функціонала. Реалізують це у вигляді MVC-архітектури (модель-вид-контроллер). Тобто розділяють бізнес-логіку і презентацію.
Модель - це блоки роботи з базами даних і різні серверні обробки
Вид - блоки, які комунікують із зовнішнім світом. (повернення json, route, html тощо). Тобто те, що віддаємо назовні.
Контролер - забезпечує взаємодію моделі та виду, які не комунікують напряму між собою.
Проведемо рефакторинг за визначеною архітектурою код розглянутий раніше.
Створимо проєкт такої структури:
project
├── index.js
├── package.json
├── usersdb.json
├── enviroments
│ ├── development.env
│ └── production.env
├── models
├── controllers
│ └──userController.js
├── middlewares
│ └──userMiddlewares.js
├──routes
│ └──userRoutes.js
├──views
├──utils
│ ├──appError.js
│ ├──catchAsync.js
│ └──index.js
└──servises
index.js - головний файл роботи сервера.
package.json - файл конфігурації проєкту.
usersdb.json - файл, який тимчасово поки виконує роль бази даних.
enviroments - каталог в якому зберігаються файли зі змінними оточення.
models - моделі даних для збереження в базу даних
controllers - каталог, який містить контролери обробки запитів.
middlewares - каталог, який містить проміжне пз.
routes - каталог, який містить роути запитів.
views -
utils - каталог із допоміжними функціями (ваділатори, обробники тощо).
servises - каталог зі стороннім функціоналом (бази даних, функції-конфігуратори, розсилки тощо.
Винесення функцій-обробників (Controllers)
З головного файлу index.js винесемо окремо в userController.js обробники запитів. розглянемо на прикладі створення користувача.
Переносимо в цей файл використовувані бібліотеки
А також саму функцію контроллера (назвемо її, наприклад createUser):
В index.js імпортуємо контролери й підключаємо до роуту
За аналогією виносимо всі інші контролери також у файл userController.js. Назви їм дамо, наприклад такі: getAllUsers, getOneUser, updateUser, deleteUser.
Нижче розгорни, щоб побачити весь код цього файлу.
А в нашому index.js лишаються гарні лаконічні роути:
А також після них пропишемо обробку запиту за неіснуючим роутом і виникнення непередбачуваної помилки сервера.
Роутери (Router)
Як ми побачили вище, в index.js лишилися обробники роутів користувача. Але сервер може обробляти запити не тільки користувача, але й інші запити й згодом їх перелік стане довгим. Видно, що спільним для них є базовий запит users. У express передбачена можливість винесення однотипних роутів завдяки Router. Підключаємо і створюємо екземпляр роута:
Переносимо імпорт контроллерів з index.js до userRoutes.js
Переносимо всі роути з index.js до userRoutes.js і замість app вказуємо router. Крім того прибираємо базовий урл users, бо його ми вкажемо в index.js. І експортуємо наш роутер:
Після цього нам потрібно в index.js заімпортувати роути повʼязані з користувачами і вказати який роут оброблятиме запити за users (синтаксис як у middleware):
ПОКРАЩЕННЯ КОДУ: У файлі роутів можна деструктуризувати наші контроллери і відтак упростити код для читання.
Альтернативний синтаксис роутів через метод route:
Винесення проміжних функцій (Middlewares)
Проміжні функції (middleware) також доречно виносити в окремі файли. Наприклад ми стоврювали в index.js кастомний middleware отримання обʼєкту користувача. Давайте його винесемо в окремий файл userMiddlewares.js. Порядок такий само як і при роботі з контроллерами. Підключаємо у файлі необхідні модулі і створюємо функцію та експортуємо її.
Утім підключати цю функцію ми будемо не в index.js, а в userRoutes.js, адже саме на цьому маршруті використовується ця проміжна функція.
У роуті ми перераховуємо по порядку всі middleware, а останнім вказуємо контроллер обробки запиту. Примітно, що цю проміжну функцію треба використовувати тільки там де в маршруті присутній id:
Або можна прописати цей middleware перед тим як будуть опрацьовані роути, де він використовується.
Кастомний обробник помилок
Можна написати клас специфічної обробки помилок.
Для цього створимо на базі класу Error наш клас AppError. Він прийматиме статус помилки і повідомлення:
Для полегшення підключення цих модулів створимо в папці utils файл index.js, який буде хабом для модулів в цій папці:
Для наочності використання цього класу застосуємо його в нашому кастомному middleware. Підключаємо:
Пропишемо всередині якусь перевірку. наприклад на довдину id, щоб вона була не короштою за 10 символів, інакше згенеруємо помилку:
В такому разі помилку буде опрацьовувати глобальний хендлер помилки. Для цього потрібно просто замість повернення статусу помилки в секції case(err) {} передати естафету функції next і як параметр передати їй помилку.
Тоді в обробнику помилки в index.js потрібно прописати повернення нового коду помилки і повідомлення, або за замовчанням статус 500.
Перевіримо відпрацювання цієї помилки:

Рефакторинг повторюваних паттернів try{}catch(){}
Як бачимо у наших проміжних обробниках і контроллерах постійно повторюється конструкція try{}catch(){}. Підхід Don't repeate yourself каже, що цей код треба покращити.
Напишемо в utils в файлі catchAsync.js функцію-обгортку, яка прийматиме нашу функцію і повертатиме таку саму функцію, а у разі виникнення помилки передаватиме на обробник помилок нашу еррорку.
Експортуємо цю функцію через хаб index.js
Тепер випробуємо цей код на прикладі нашого кастомного middleware.
Імпортуємо:
Тепер ми огортаємо функцію в catchAsync і прибираємо try{}catch(){}
так само можна переписати всі асинхронні функції в контролерах.
Покликання:
Last updated