[Перевод] Эффективное использование process.env
Если вы только начинаете осваивать Node.js, то, вам, наверняка, встречались примерно такие строчки кода: app.listen(process.env.PORT)
. Зачем вбивать в редактор кода шестнадцать символов, когда того же эффекта можно добиться, просто указав номер порта, например — 3000? Предлагаем это выяснить.
Что такое process.env?
Глобальная переменная process.env
доступна приложению во время его выполнения благодаря внутренним механизмам Node. Она представляет собой состояние окружения системы в момент запуска приложения. Например, если в системе задана переменная PATH
, обратиться к ней из приложения можно посредством конструкции process.env.PATH
. Её можно использовать, например, если вам нужно узнать место, где можно найти исполняемые файлы, к которым требуется обратиться из кода.
О важности окружения, в котором работает приложение
До тех пор пока приложение не развёрнуто, будь то код, реализующий простейший сайт, или сложное API, используемое в тяжёлых вычислениях, оно совершенно бесполезно. Это — лишь строки текста, хранящиеся в файлах.
Занимаясь созданием программ, разработчик никогда точно не знает, где именно они будут работать. Например, если в процессе разработки нужна база данных, мы запускаем её экземпляр локально и связываемся с ней с использованием строки соединения, которая выглядит примерно так: 127.0.0.1:3306
. Однако, когда мы развёртываем рабочий экземпляр приложения, может возникнуть потребность, например, подключиться к СУБД, расположенной на удалённом сервере, скажем, доступной по адресу 54.32.1.0:3306
.
Если предположить, что переменными окружения мы не пользуемся, есть два варианта действий.
Первый заключается в том, чтобы обеспечить доступность базы данных на той же машине, на которой работает приложение, что позволит подключиться к ней по адресу 127.0.0.1:3306
. Такой подход означает сильную привязку приложения к инфраструктуре, его потенциально низкую доступность и невозможность его масштабировать, так как развернуть можно лишь один экземпляр приложения, зависимый от единственного экземпляра СУБД.
Второй вариант предусматривает модификацию кода таким образом, чтобы в ходе его выполнения, через условный оператор с несколькими ветвями else
, можно было выбрать подходящую строку подключения к базе данных:
let connectionString;
if (runningLocally()) {
connectionString = 'dev_user:dev_password@127.0.0.1:3306/schema';
} else if (...) {
...
} else if (inProduction()) {
connectionString = 'prd_user:prd_password@54.32.1.0:3306/schema';
}
const connection = new Connection(connectionString);
Подобные конструкции увеличивают число путей выполнения программы, что усложняет тестирование, да и о красоте кода тут говорить не приходится.
Если же для указания строки подключения к базе используются переменные окружения, нашу задачу можно решить так:
const connection = new Connection(process.env.DB_CONNECTION_STRING);
При таком подходе можно как подключаться к локальному экземпляру СУБД при разработке, так и организовать соединение с чем-то вроде защищённого удалённого кластера баз данных, поддерживающего балансировку нагрузки и умеющего масштабироваться независимо от приложения. Это даёт возможность, например, иметь множество экземпляров приложения, которые независимы от конкретного экземпляра СУБД.
В целом, рекомендовано всегда рассматривать зависимости приложения от неких внешних служб как присоединённые ресурсы и настраивать подключение к ним с помощью переменных окружения.
Как использовать переменные окружения
Действия, которые выполняются для того, чтобы подготовить переменные окружения для приложения называют предоставлением ресурсов (provisioning) или подготовкой к работе. При подготовке сервера можно выделить два уровня, на которых можно работать с переменными окружения: уровень инфраструктуры и уровень приложения. Подготовить окружение можно, либо используя специализированные инструменты, либо — некую логику, реализованную на уровне приложения.
Среди средств, работающих на уровне приложения, можно отметить пакет dotenv
, который позволят загружать переменные окружения из файла .env
. Установить этот инструмент можно так:
npm install dotenv --save
Загрузка переменных окружения выполняется с помощью следующей простой команды:
require('dotenv').config();
Такой подход удобен в процессе разработки, но не рекомендуется в продакшне, поэтому, в частности, файл .env
лучше добавить в .gitignore
.
На инфраструктурном уровне для настройки окружения можно использовать средства для управления развёртыванием приложений вроде PM2, Docker Compose и Kubernetes.
PM2 использует файл ecosystem.yaml
, в котором можно задать переменные окружения с помощью свойства env
:
apps:
- script: ./app.js
name: 'my_application'
env:
NODE_ENV: development
env_production:
NODE_ENV: production
...
Docker Compose, аналогичным образом, позволяет задавать свойство environment
в манифест-файле сервиса:
version: "3"
services:
my_application:
image: node:8.9.4-alpine
environment:
NODE_ENV: production
...
...
У Kubernetes есть похожее свойство env
в шаблоне манифеста, которое также позволяет задавать переменные окружения:
kind: Deployment
apiVersion: extensions/v1beta1
metadata:
name: my_application
spec:
...
template:
spec:
env:
- name: NODE_ENV
value: production
...
Сценарии использования переменных окружения
▍Настройки приложения
Настройки приложения не влияют на то, какие именно действия выполняет это приложение, не влияют на его логику. Например, приложению известно, что ему необходимо прослушивать порт для того, чтобы к нему можно было обратиться извне, но ему необязательно знать — какой именно это будет порт. Такие данные вполне подходят для вынесения их в переменные окружения. Поэтому их установку и подготовку сетевой инфраструктуры можно доверить средствам развёртывания приложений.
▍Взаимодействие с внешними службами
Переменные окружения часто используют для указания того, как приложение должно подключаться к службам, от которых оно зависит. Это позволяет сделать код чище и улучшить тестируемость приложения. В частности, такой подход позволяет окружению тестирования передавать приложению некие условные данные, которые, например, имитируют внештатные ситуации, что позволяет проверить приложение на предмет сбоев в подобных ситуациях. Тут мы имеем дело с похожей ситуацией: приложение нуждается в некоей службе, но где именно она расположена, заранее неизвестно. Настройку переменных окружения для подобных случаев можно доверить менеджерам развёртывания.
▍Вспомогательные средства разработки
В ходе локальной разработки обычно полезно иметь некие программные средства, которые позволяют быстро получать информацию из выполняющегося приложения или изолировать ошибки. Пример подобных средств — интерактивная перезагрузка веб-страницы после внесения изменений в относящийся к ней код приложения. Подобное поведение можно реализовать с помощью условных конструкций, решения в которых принимаются либо на основании стандартных переменных окружения, вроде process.env.NODE_ENV
, либо на базе специальных переменных, которые создаёт сам разработчик, наподобие process.env.HOT_RELOADING_ENABLED
.
Анти-паттерны
Вот несколько распространённых вариантов неправильного использования переменных окружения.
- Чрезмерное использование
NODE_ENV
. Во многих учебных руководствах можно встретить рекомендации по использованиюprocess.env.NODE_ENV
, но особых подробностей об этом там можно и не найти. Как результат, наблюдается неоправданное применениеNODE_ENV
в условных операторах, противоречащее предназначению переменных окружения. - Хранение информации, зависящей от времени. Если приложению требуется SSL-сертификат или периодически изменяющийся пароль для взаимодействия с другим приложением, развёрнутым на том же сервере, будет неразумно задавать эти данные в виде переменных окружения. Сведения об окружении, получаемые приложением, представляют собой состояние среды на момент его запуска и остаются неизменными во время его работы.
- Настройка часового пояса. Леон Бамбрик сказал в 2010-м: «В компьютерной науке есть 2 сложные задачи: инвалидация кэша, именование сущностей и ошибки смещения на единицу». Я добавил бы сюда ещё одну: работу с часовыми поясами. При развёртывании приложения в высокодоступных средах его экземпляры могут быть запущены в различных часовых поясах. Один экземпляр может работать в дата-центре, расположенном в Сан-Франциско, другой — в Сингапуре. А пользователи подключаются ко всем этому из Лондона. Рекомендуется, в серверной логике, использовать UTC, а заботы о часовом поясе оставить клиентской части приложения.
Итоги
Правильное использование данных из process.env
приводит к разработке приложений, которые легче и удобнее тестировать, развёртывать и масштабировать. Переменные окружения — это одна из тех мелочей, нередко почти незаметных, правильная работа с которыми позволяет сделать код лучше, а неправильная способна привести к неприятностям, которые имеют свойство проявляться в самый неожиданный момент. Надеемся, наш рассказ о переменных окружения поможет вам улучшить качество ваших программ.
Уважаемые читатели! Пользуетесь ли вы process.env
в своих проектах на Node.js?