Przejdź do treści

Konfiguracja

Po co nam konfiguracja aplikacji?

  • Konfiguracja to element, który jako jedyny może różnić się pomiędzy wdrożeniami aplikacji na różne środowiska - developerskie, testowe, produkcyjne;
  • Zmieniają się dane do połączenia z np. bazą danych, kolejką, wysyłką maili czy serwisami zewnętrznymi.
  • Dane konfiguracyjne powinny być oddzielone od kodu aplikacji, tak aby podczas jej uruchamiania można było dostarczyć nowe, dowolne wartości bez potrzeby przebudowywania aplikacji;
  • Dlaczego bez przebudowywania? Ponieważ w trakcie działania aplikacji, może okazać się, że np. potrzebujemy migrować z danymi z bazy na lepszy, większy serwer - w ten sposób zrobimy to jedynie, podmieniając konfigurację do połączenia z bazą w aplikacji;
  • W tym celu wykorzystuje się zmienne środowiskowe (nazywane również: env variables czy env);
  • Konfiguracja dostarczana za pomocą zmiennych środowiskowych jest również zalecana przez The Twelve-Factor App.

Ustawianie zmiennych środowiskowych

Zmienne środowiskowe odpowiadają za konfigurację naszego systemu. To dzięki nim możemy dowiedzieć się np. jaka jest domyślna powłoka (shell), jakie ścieżki są brane pod uwagę przy dowiązaniach do aplikacji (PATH).

Są także najczęstszym sposobem przekazywania konfiguracji do naszej aplikacji - podczas uruchamiana aplikacji w chmurze czy też środowisku skonteneryzowanym.

Wyróżniamy dwa typy zmiennych, ze względu na ich sposób ustawienia oraz późniejszej dostępności w procesach:

  • zmienne stałe - zdefiniowane w pliku, wczytywane dla każdej uruchomionej powłoki;
  • zmienne tymczasowe - definiowane po uruchomieniu powłoki, dostęp do nich mają wybrane lub wszystkie uruchamiane procesy - w zależności do tego, w jaki sposób zostały zdefiniowane;

Aby wyświetlić zmienne środowiskowe w powłoce, należy użyć polecenia printenv.

[email protected]:~$ printenv
NVM_DIR=/home/adrian/.nvm
HOSTTYPE=x86_64
LANG=C.UTF-8
WSL_DISTRO_NAME=Ubuntu-18.04
USER=adrian
PWD=/home/adrian
HOME=/home/adrian
NAME=EMPHIE
SHELL=/bin/bash
TERM=xterm-256color
DOCKER_HOST=localhost:2375
LOGNAME=adrian
PATH=/home/adrian/bin

Ustawianie zmiennej tylko dla uruchamianego procesu:

$: MODE=1 node env.js

// result:
MODE:  1

Kolejne wywołanie bez podania zmiennej MODE, skutkuje brakiem wartości.

Ustawianie zmiennej dla wszystkich uruchamianych procesów w powłoce:

$: export MODE=1
$: node env.js

// result:
MODE: 1

Każde kolejne wywołanie będzie zwracać dokładnie tę samą wartość (a dokładnie wartość = 1) dla zmiennej MODE.

Więcej na temat ustawiania zmiennych w sposób tymczasowy (na czas życia powłoki) lub stały, można poczytać w artykule pt. “How to Set and List Environment Variables in Linux

Dostęp do zmiennych środowiskowych w Node.js

Bez względu, w jaki sposób zmienna środowiskowa zostanie ustawiona, w procesie aplikacji Node.js, dostępna jest jako właściwość w obiekcie process.env.

1
console.log('MODE: ', process.env.MODE || 'undefined!');

Jeżeli właściwość może posiadać ustawioną wartość domyślną, zróbmy to w taki sposób, aby była to prawidłowa wartość dla środowiska developerskiego - w celu minimalizacji ilości kroków do prawidłowego, lokalnego uruchomienia aplikacji.

Należy pamiętać jednak o niepozostawianiu haseł, innych wrażliwych danych oraz wartości ściśle związanych tylko z naszą konfiguracją komputera. Dla takich zmiennych lepiej wygenerować błąd, aby podczas uruchamiania aplikacji wiedzieć, która z wymaganych zmiennych nie została uzupełniona.

1
console.log('MODE: ', process.env.MODE || new 'MODE env variable does not exist!');

Informacja

Wartości, które otrzymujemy we właściwościach process.env zawsze są ciągami znaków!

Jeśli chcemy uzyskać inny typ danych (np. bool, number), musimy sami dokonać konwersji.

1
2
console.log('TO NUMBER: ', +process.env.DB_PORT || 3306);
console.log('TO BOOL: ', process.env.DEBUG === 'true' || process.env.DEBUG === '1' || false);

Centralizacja konfiguracji w jednym miejscu

Praktyką, którą stosuje się, jest gromadzenie konfiguracji w jednym miejscu. Dokładniej w jednym pliku, tak aby zaczytywanie zmiennych konfiguracyjnych nie było rozproszone po całym kodzie naszej aplikacji.

W ten sposób w łatwy sposób możemy ewentualnie napisać logikę ustawiania danej zmiennej, ale także wiemy, gdzie można spodziewać się wszystkich opcji konfiguracyjnych.

Wygląda to w mniej więcej następujący sposób. Oto zawartość pliku config.js:

1
2
3
4
5
export const DB_HOST = process.env.DB_HOST || 'localhost';
export const DB_PORT = +process.env.DB_PORT || 3306;
export const DB_NAME = process.env.DB_NAME || 'exchange_rate';
export const DB_USER = process.env.DB_USER || 'root';
export const DB_PASSWORD = process.env.DB_PASSWORD || '';

Następnie w miejscach, gdzie potrzebujemy uzyskać dostęp do konfiguracji:

1
2
3
import * as config from './config.js';

console.log(`Database host: ${config.DB_HOST}`);

Warto wiedzieć:

  • Pamiętaj o prefixie dla nazw opcji konfiguracyjnych, tak aby segregować je w poszczególne grupy np. DB_HOST, MQ_HOST, REDIS_HOST;
  • Zamiast eksportowania każdej stałej osobno, można utworzyć jeden obiekt lub kilka obiektów (każdy dla danej grupy) - patrz przykład poniżej;
  • Można także tworzyć bardziej rozdrobnioną konfigurację w osobnych plikach np. osobna dla wybranego modułu. Takie podejście polecałbym w momencie kiedy moduł zaczyna być wyodrębniany do następnych projektów i może stać się niezależnym modułem w npm.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
export const DB = {
  HOST: process.env.DB_HOST || 'localhost'
  // ...
}

export const MQ = {
  // ...
}

console.log(`Database host: ${config.DB.HOST}`);
console.log(`Message queue host: ${config.MQ.HOST}`);

dotenv

Biblioteka dotenv pozwala na definiowanie zmiennych środowiskowych w pliku .env. W ten sposób nie musimy na środowisku lokalnym ustawiać zmiennych w powłoce systemu.

Uwaga!

Plik .env zawsze musi być wykluczony z wersjonowania! Oznacza to absolutny zakaz komitowania go do repozytorium.

Korzystając z dotenv, plik .env będzie definiował zmienne i ich wartości w następujący sposób:

DB_HOST=191.168.0.15
DB_PORT=3306
DB_NAME=exchange_rate
DB_USER=admin
[email protected]

Szablon konfiguracji - .env.template

Podczas pracy z dotenv, stosowałem zazwyczaj następującą praktykę:

  1. W repozytorium tworzymy template dla pliku .env (zwykle plik o nazwie .env.template), w którym znajdowały się wszystkie zmienne środowiskowe aplikacji, ale podane bez wartości, które są poufne - hasła, klucze do zewnętrznego API lub usługi;
  2. Podczas pierwszego uruchamiania aplikacji na środowisku (lub po zmianie pliku .env.template), kopiujemy zawartość pliku z template do pliku .env;
  3. Następnie przypisujemy zmiennym odpowiednie wartości - podajemy hasła, klucze;
  4. .env.template jest plikiem przechowywanym w repozytorium, ponieważ zawiera tylko nazwy wymaganych zmiennych bez żadnych poufnych danych;
  5. .env zostaje ignorowany przy wersjonowaniu;

Wczytywanie konfiguracji

TODO

Debugowanie w razie problemów

TODO