Przejdź do treści

Czym jest moduł w Node.js?

Wyobraź sobie rozbudowaną aplikację, która potrafi obsłużyć wszelkiego rodzaju wątki księgowe, spełniając przy tym wszystkie obowiązujące w Polsce przepisy (włącznie z tymi, które weszły w życie w związku z Nowym Ładem 😅)

Wyobraź sobie do tego, że całą implementację, programista zawarł w jednym pliku JavaScript o nazwie accountancy.js.

Nie wiem jak Ty, ale ja czuję się PRZERAŻONY!

Dziel i zwyciężaj

Tylko z pozoru łatwiej jest pisać cały kod aplikacji w jednym pliku. W pewnym momencie nie wiadomo gdzie szukać poszczególnych elementów - czy to w linijce 19273 czy 11982? Każda dodatkowa funkcjonalność to nowe linijki kodu w jednym pliku, który rozrósł się do takich rozmiarów, że ciężko w nim co kolwiek znaleść, a tym bardziej zwizualizować sobie jego podział na poszczególne odpowiedzialności, a co dopiero ze względu na klasy czy funkcje.

Implementację zazwyczaj rozkładamy na mniejsze czynniki - pojawia się kontroler, encja, serwis, czy cała biblioteka do obsługi jednego, konkretnego zadania np. połączenia z bazą danych i wykonywania na niej operacji. Wyznaczamy tak zwane granice kontekstów.

Projektując architekturę kodu aplikacji, kierujemy się separacją ze względu na określony aspekt - techniczny lub funkcjonalny, pamiętając o tym aby:

  • zrobić jedną konkretną rzecz - możliwie jedną np. policzyć wartość brutto podając stawkę VAT i wartość netto;
  • ukryć szczegóły implementacji - nie potrzebujemy wiedzieć, że należny podatek dochodowy zaookrąglamy do pełnych złotówek - zależy nam na konkretnym wyniku;
  • udostępnić przyjazny w wykorzystaniu interfejs - dobrze nazwane funkcje i jej parametry;

A w bardziej prostych słowach - dzielimy aplikację na wiele mniejszych plików.

Co może być modułem?

Moduł w ekosystemie JavaScript jest fundamentalnym elementem za pomocą, którego układamy aplikację w całość, a sam moduł zwykle jest zbiorem kodu o określonej funkcji.

Technicznie rzecz ujmując modułem jest:

  • każdy plik np. auth.js;
  • zbiór plików, zgromadzony w folderze np. /auth/**, wtedy też często stosuje się plik index.js, który jest proxy dla modułu - to on odpowiada za pokazanie na zewnątrz z czego programista może skorzystać;
  • całe rozbudowane struktury plików i folderów o wielopoziomowej hierarchii - i podobnie jak wyżej, często stosuje się plik index.js;

Przykłady modułów

Przykłady oparte o moduły w formacie CommonJS.

1. Tworzymy funkcję, którą eksportujemy

accountancy.js
1
2
3
module.exports = function (netPrice, vatRate) {
  // ... implementation
};

2. Tworzymy funkcje, które eksportujemy w obiekcie

accountancy.js
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
function grossPrice(netPrice, vatRate) {
  // ... implementation
}

function netPrice(grossPrice, vatRate) {
  // ... implementation
}

module.exports = {
  grossPrice,
  netPrice
};

3. Tworzymy klasę, a następnie eksportujemy ją

accountancy.js
1
2
3
4
5
6
7
8
9
module.exports = class Accountancy {
  grossPrice(netPrice, vatRate) {
    // ... implementation
  }

  netPrice(grossPrice, vatRate) {
    // ... implementation
  }
};

4. Tworzymy funkcję, którą eksportujemy jako ekwiwalent klasy

accountancy.js
1
2
3
4
5
6
7
module.exports = function (netPrice) {
  this.DEFAULT_VAT_RATE = 23;

  this.vatFromNetPrice = () => {
    return netPrice / 100 * this.DEFAULT_VAT_RATE;
  }
}
index.js
1
2
3
4
const Accountancy = require('./accountancy.js');

const calc = new Accountancy(100);
console.log(calc.vatFromNetPrice()); // return: 23

5. Używamy innych składowych, które eksportujemy

accountancy.js
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
const DEFAULT_VAT_RATE = 23;

function vatFromNetPrice(netPrice) {
    return netPrice / 100 * DEFAULT_VAT_RATE;
}

module.exports = {
  DEFAULT_CURRENCY: 'PLN',
  DEFAULT_VAT_RATE,
  vatFromNetPrice
};

6. Moduł jako proxy do innych modułów

Komponujemy moduły, aby następnie wyeksponować je w innym module, dodając zachowanie, zmieniając interfejs dostępu lub układając dostęp w odpowiednim drzewie.

accountancy/defaults.js
1
2
3
module.exports = {
    VAT_RATE: 23
};
accountancy/calculations.js
1
2
3
module.exports = {
    vatFromNetPrice: (netPrice) => netPrice / 100 * 23
};
accountancy/index.js
1
2
3
4
5
6
7
const defaults = require('./defaults.js');
const calculations = require('./calculations.js');

module.exports = {
  defaults,
  calculations
};

Korzystając dalej z modułu accountancy w następujący sposób:

app.js
1
2
3
4
const accountancy = require('./accountancy');

accountancy.calculations.vatFromNetPrice(149);
accountancy.defaults.VAT_RATE;

Co daje nam podział na moduły?

Umożliwia nam to między innymi:

  • łatwiejsze przemieszczanie się pomiędzy napisanym kodem;
  • strukturę katalogowo-plikową, która sami definiujemy;
  • uproszczone wprowadzanie zmian;
  • separację odpowiedzialności poszczególnych elementów naszej aplikacji;
  • reużywalność kodu - raz napisany fragment, może być wykorzystany w wielu miejscach aplikacji;