Как структурировать приложение c Express.js
Наличие интуитивно понятной файловой структуры играет огромную роль – легче добавлять новый функционал и рефакторить код. Подходящий способ структурирования выглядит так:
Такая организация позволяет ограничить размер файла примерно до 100 строк, что делает ревью и траблшутинг менее кошмарным делом.
Возьмите за правило отделять и выносить логику в отдельный файл – вы ограничите переключение контекста, которое происходит при чтении одного файла. Это также полезно при мерже в мастер – будет меньше конфликтов слияния.
Чтобы обеспечить соблюдение правил в команде, вы можете настроить линтер, который сообщит, когда вы переходите через установленный лимит строк в файле или если одна строка имеет длину более 100 символов.
1. Как повысить производительность и надёжность Express.js
1.1. Переменная окружения NODE_ENV
Установив переменную
окружения NODE_ENV
, вы примерно трёхкратно увеличите производительность. В терминале это можно
сделать следующим образом:
Если вы используйте server.js
,
добавьте следующее:
1.2. Включаем Gzip-сжатие
Установите npm-пакет компрессии:
Затем добавьте следующий фрагмент в свой код:
1.3. Асинхронные функции
Не блокируйте поток выполнения и не используйте синхронные функции. Вместо этого используйте функции Promises и Async/Await. Если у вас есть доступ только к синхронным функциям, оберните их в async-функцию, которая будет выполняться вне основного потока.
Если не избежать использования синхронных функций – запустите их в отдельном потоке. Чтобы избежать блокировки основного потока и проседания CPU, создайте дочерние процессы для обработки интенсивных задач процессора.
1.4. Система логов
Чтобы унифицировать
журналы по всему Express.js-приложению, а не использовать console.log()
, используйте агент для централизованного ведения, структурирования и сбора журналов.
Можно использовать любой инструмент управления журналами, например, Sematext, Logz.io или Datadog. Практически все агенты базируются на Winston и Morgan. Они отслеживают трафик запросов API с помощью промежуточного программного обеспечения. Это сразу же даст вам журналы и данные по каждому параметру, что важно для отслеживания производительности.
Вот так добавляется логгер и промежуточное ПО:
1.5. Обработка ошибок и исключений
При использовании в
коде Async/Await
для обработки ошибок и исключений рекомендуется применять операторы try-catch
, а также использовать для ведения журнала ошибок Express logger.
Кроме того,
рекомендуется настроить catch-all error
:
Здесь будет поймана любая ошибка, выброшенная в контроллере. А ещё можно добавить слушателей в сам процесс:
1.6. Следите за утечками памяти
Вы не сможете поймать ошибки до того, как они произойдут. Некоторые проблемы не обусловлены только «тематикой» исключения, вываливающегося при падении приложения. Все решения сводятся к тому, чтобы упредить любую возможность утечки памяти.
Заметить утечку гораздо проще, чем вы думаете. Если память процесса продолжает неуклонно расти, а не периодически сокращается в результате сборки мусора – скорее всего это она и есть. В идеале вы должны сосредоточиться на предотвращении утечек, а не на их устранении и отладке.
Добавьте в Express-приложение сборщик метрик, который будет хранить их в одном месте. Это поможет проанализировать данные и добраться до основной причины.
В чем прелесть – это
всего лишь одна строка кода. Добавьте ее в файлик app.js
.
Благодаря этому вы получите доступ к нескольким информационным панелям, дающим ключевое представление о том, что происходит с вашим Express-приложением. Данные можно фильтровать и группировать для визуализации процессов, памяти, использования CPU и HTTP-запросов/ответов. Что вы должны сделать сразу же – настроить оповещения об изменениях в работе софта.
Двигаемся дальше от Express.js к конкретным советам и рекомендациям по JavaScript и о том, как его использовать оптимизированным и надежным способом.
2. Как настроить окружение JavaScript
2.1. Чистые функции
Чистые функции – это функции, которые не изменяют внешнее состояние. Они принимают параметры, что-то делают с ними и возвращают значение.
Вместо использования
var
, применяйте только const
и полагайтесь на чистые функции для создания новых
объектов вместо изменения существующих. Это связано с использованием функций высокого
порядка в JavaScript, например .map()
, .reduce()
, .filter()
и т. д.
2.2. Параметры объекта
JavaScript – слабо типизированный язык. В вызов функции может быть передан один или несколько параметров. Даже если объявление функции имеет фиксированное число определенных аргументов. Этот огрех можно решить, используя объекты в качестве параметров функции.
Все эти вызовы функций будут работать одинаково. Вы можете принудительно указать имена параметров, при этом вы не связаны порядком, что значительно упрощает управление.
2.3. Тестирование
Используйте что-нибудь простое, например, Mocha и Chai. Mocha – это фреймворк для тестирования, а Chai – assertion библиотека.
Установите npm пакеты:
Давайте потестим
функцию. В test.js
добавьте следующее:
Добавьте это в package.json
:
Теперь запустим тесты, выполнив следующую команду в терминале:
Выведется примерно такое:
3. Использование DevOps-инструментов
3.1. Управление переменными среды в Node.js с dotenv
Dotenv – это модуль
npm, позволяющий загружать переменные среды в любое Node.js приложение. В корне вашего проекта
создайте .env
файл. Здесь вы добавите все необходимые переменные окружения.
Загрузка файл проста. В верхней части app.js
разместите dotenv:
Dotenv по умолчанию загружает файл с
именем .env
. При необходимости прочитайте руководство по настройке нескольких dotenv-файлов.
3.2. Перезапуск приложения с помощью Systemd
Systemd – часть строительных блоков ОС Linux. Он запускает и управляет системными процессами. Вам нужно запустить Node.js процесс, как системную службу, чтобы он восстанавливался после сбоев.
На виртуальной машине
или сервере создайте новый файл в разделе /lib/systemd/system/
:
Две важные строки в
этом файле – ExecStart
и Restart. ExecStart
запустит ваш server.js
с помощью
бинарника /usr/bin/node
(обязательно проверяйте абсолютный путь к файлу server.js
).
Функция Restart=on-failure
перезапустит приложение, если оно «обвалится».
После сохранения fooapp.service
,
перезагрузите демона и запустите скрипт.
3.3. Перезапуск приложения с помощью PM2
PM2
существует уже несколько лет. Эти ребята используют специальный кастомный скрипт, управляющий
и запускающий server.js
. Он проще в настройке, но обременён другим Node.js- процессом, выступающим в качестве Master-процесса
и менеджера для вашей Express.js.
Сначала нужно установить PM2:
Затем запустите приложение, выполнив следующую команду в корневом каталоге Express-проекта:
Флаг -I max
гарантирует, что приложение будет запущено в кластерном режиме, создавая
столько воркеров, сколько есть ядер у CPU.
4. Балансировка нагрузки и обратный прокси
4.1. Балансировка нагрузки с помощью кластерного модуля
Встроенный модуль Node.js
позволяет создавать рабочие процессы, обслуживающие ваше приложение. Он основан
на реализации child_process
и прост в настройке. Вам нужно лишь добавить файл cluster.js
и вставить в него
следующий код:
Когда вы запустите
cluster.js
c нодой cluster.js
, модуль кластера обнаружит, что он работает как
Master-процесс и вызовет функцию masterProcess()
. Она
подсчитает, сколько процессорных ядер имеет сервер, и вызовет cluster.fork()
. Все
эти процессы выполняются на одном и том же порту. Подробнее об этом читайте в официальном хелпе.
Слушатель событий
cluster.on('exit')
перезапустит рабочий процесс, если он завершится неудачей.
Теперь отредактируем
поле ExecStart
в приложении fooapp.service
. Замените:
На следующее:
Перезагрузите Systemd и
приложение fooapp.service
:
Вы добавили балансировку нагрузки в Express-приложение. Это будет работать только для single-server. Если необходимо несколько серверов – используйте Nginx.
4.2. Добавление обратного прокси с помощью Nginx
Одно из основных правил в работе с приложениями Node.js – не вешайте их на порты 80 и 443. Для перенаправления трафика используйте обратный прокси. Nginx – самый распространённый инструмент для достижения этой цели.
Установка Nginx довольно проста, для Ubuntu это будет выглядеть так:
Если у вас другая ОС, ознакомьтесь с инструкциями по установке Nginx.
Nginx должен стартовать сразу же, но на всякий случай проверьте:
Если не запустился – выполните эту команду:
После запуска Nginx необходимо отредактировать конфиг, чтобы включить обратный прокси. Конфиг Nginx
находится в каталоге /etc/nginx/
. Основной конфигурационный файл называется
nginx.conf
, но есть разные дополнения в каталоге etc/nginx/sites-available/
.
Конфигурация сервера по умолчанию находится здесь и называется default.
Чтобы включить обратный прокси-сервер, откройте файл конфигурации по умолчанию и отредактируйте его следующим образом:
Сохранитесь и перезапустите службу Nginx:
Эта настройка будет роутить весь трафик с порта 80 на ваше Express-приложение.
4.3. Кэширование в nginx
Кэширование важно для сокращения времени отклика ресурсов, которые редко изменяются.
Отредактируйте nginx.conf
:
Откройте дефолтный конфиг и добавьте эти строки кода:
4.4. Gzip
В серверном блоке конфига добавьте следующие строки:
Если нужно больше информации – читайте официальный хелп.
4.5. Включение кэширования на Redis
Redis – in-memory хранилище, которое часто используется в качестве кэша.
Установка на Ubuntu проста:
Откройте файл /etc/redis/redis.conf
и измените одну
важную строку:
на следующее:
Перезапустите службу Redis:
Затем установите модуль redis для доступа к Redis из приложения:
Теперь вы можете начать кэшировать запросы. Рассмотрим пример:
Этот код будет кэшировать ответ из БД в виде строки JSON в Redis в течение 3600 секунд. Вы можете изменить это в зависимости от требований.
Заключение
Идея этой статьи состояла в том, чтобы охватить лучшие практики, которых вы должны придерживаться и те, от которых следует держаться подальше.
Надеемся, что вам понравилось. Удачи в обучении!
Комментарии