Рефакторинг додатку за 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 - каталог зі стороннім функціоналом (бази даних, функції-конфігуратори, розсилки тощо.
Файли прийнято називати у форматі camelCase (наприклад userController.js), інколи модна побачити синтаксис через крапку (наприклад user.controller.js)
Винесення функцій-обробників (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 перед тим як будуть опрацьовані роути, де він використовується.
РЕМАРКА: Припустимо у нас є в системі кроистувач, який обробляється не за правилами за запитом маршруту users/bad, у разі, якшо ми його пропигемо вкінці, то він проігнорується. Тому кастомні маршрути, щоб вони опрацювалися, потрібно прописувати перд тими роутами в яких обробка іде за подібною схемою з динамічним параметром у запиті.
Кастомний обробник помилок
Можна написати клас специфічної обробки помилок.
Для цього створимо на базі класу 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