Розширення
Що робить розширення Ditsmod
Як правило, розширення виконує свою роботу перед створенням обробників HTTP-запитів. Щоб змінити або розширити роботу застосунку, розширення використовує статичні метадані, що закріплені за певними декораторами. З іншого боку, розширення може ще й динамічно додавати метадані такого самого типу, як і ці статичні метадані. Розширення можуть ініціалізу ватись асинхронно, і можуть залежати один від одного.
Задача більшості розширень полягає в тому, що вони, як на конвеєрі, на вході беруть один багатовимірний масив конфігураційних даних (метаданих), а на виході видають інший (або доповнений) багатовимірний масив, який в кінцевому підсумку інтерпретується цільовим розширенням, наприклад, для створенні роутів та їх обробників. Але не обов'язково щоб розширення працювали над конфігурацією та встановленням обробників HTTP-запитів; вони можуть ще й записувати логи чи збирати метрики для моніторингу, або виконувати будь-яку іншу роботу.
У більшості випадків, багатовимірні масиви конфігураційних даних відображають структуру застосунку:
- вони розбиті по модулям;
- у кожному модулі є контролери або провайдери;
- кожен контролер має один або більше роутів.
Наприклад, в модулі @ditsmod/body-parser працює розширення, що динамічно додає HTTP-інтерсептор для парсингу тіла запиту до кожного роута, що має відповідний метод (POST, PATCH, PUT). Воно це робить один раз перед створенням обробників HTTP-запитів, тому за кожним запитом вже немає необхідності тестувати потребу такого парсингу.
Інший приклад. Модуль @ditsmod/openapi дозволяє створювати OpenAPI-документацію за допомогою власного декоратора @oasRoute
. Без роботи розширення, Ditsmod буде ігнорувати метадані з цього нового декоратора. Розширення з цього модуля отримує згаданий вище конфігураційний масив, знаходить там метадані з декоратора @oasRoute
, й інтерпретує ці метадані додаючи інші метадані, які будуть використовуватись іншим розширенням для встановлення роутів.
Що таке "розширення Ditsmod"
У Ditsmod розширенням називається клас, що впроваджує інтерфейс Extension
:
interface Extension<T> {
stage1(isLastModule: boolean): Promise<T>;
}
Кожне розширення потрібно реєструвати, про це буде згадано пізніше, а зараз припустимо, що така реєстрація відбулася, після чого йде наступний процес:
- збираються метадані з усіх декоратор ів (
@rootModule
,@featureModule
,@controller
,@route
...); - зібрані метадані передаються в DI з токеном
MetadataPerMod2
, отже - будь-яке розширення може отримати ці метадані у себе в конструкторі; - починається по-модульна робота розширень:
- у кожному модулі збираються розширення, що створені в цому модулі, або імпортовані в цей модуль;
- кожне з цих розширень отримує метадані, зібрані теж у цьому модулі, і викликаються методи
stage1()
даних розширень.
- створюються обробники HTTP-запитів;
- застосунок починає працювати у звичному режимі, обробляючи HTTP-запити.
Готовий простий приклад ви можете проглянути у теці 09-one-extension.
Групи розширень
Будь-яке розширення повинно входити в одну або декілька груп. Концепція групи розширень аналогічна до концепції групи інтерсепторів. Зверніть увагу, що група інтерсепторів виконує конкретний вид робіт: доповнення обробки HTTP-запиту для певного роута. Аналогічно, кожна група розширень - це окремий вид робіт над певними метаданими. Як правило, розширення в певній групі повертають метадані, що мають однаковий базовий інтерфейс. По-суті, групи розширень дозволяють абстрагуватись від конкретних розширень; натомість вони роблять важливими лише вид роботи, який виконується у даних групах.
Наприклад, у @ditsmod/rest
існує група ROUTES_EXTENSIONS
в яку по-дефолту входить єдине розширення, що обробляє метадані, зібрані з декоратора @route()
. Якщо в якомусь застосунку потрібна документація OpenAPI, можна підключити модуль @ditsmod/openapi
, де також зареєстровано розширення у групі ROUTES_EXTENSIONS
, але це розширення працює з декоратором @oasRoute()
. В такому разі, у групі ROUTES_EXTENSIONS
вже буде зареєстровано два розширення, кожне з яких готуватиме дані для встановлення маршрутів роутера. Ці розширення зібрані в одну групу, оскільки вони налаштовують роути, а їхні методи stage1()
повертають дані з однаковим базовим інтерфейсом.
Спільний базовий інтерфейс даних, який повертає кожне з розширень у певній групі, - це важлива умова, оскільки інші розширення можуть очікувати дані із цієї групи, і вони будуть опиратись саме на цей базовий інтерфейс. Звичайно ж, базовий інтерфейс при потребі можна розширювати, але не звужувати.
Окрім спільного базового інтерфейсу, важливою є також послідовність запуску груп розширень і залежність між ними. У нашому прикладі, після того, як відпрацюють усі розширення з групи ROUTES_EXTENSIONS
, їхні дані збираються в один масив і передаються до групи PRE_ROUTER_EXTENSIONS
. Навіть якщо ви пізніше зареєструєте більше нових розширень у групі ROUTES_EXTENSIONS
, все-одно група PRE_ROUTER_EXTENSIONS
буде запускатись після того як відпрацюють абсолютно усі розширення з групи ROUTES_EXTENSIONS
, включаючи ваші нові розширення.
Ця фіча є дуже зручною, оскільки вона інколи дозволяє інтегрувати зовнішні модулі Ditsmod (наприклад, з npmjs.com) у ваш застосунок без жодних налаштувань, просто імпортувавши їх у потрібний модуль. Саме завдяки групам розширень, імпортовані розширення будуть запускатись у правильній послідовності, навіть якщо вони імпортовані з різних зовнішніх модулів.
Так працює, наприклад, розширення з @ditsmod/body-parser
. Ви просто імпортуєте BodyParserModule
, і його розширення вже буде запускатись у правильному порядку, який прописаний у цьому модулі. В даному разі, його розширення буде працювати після групи ROUTES_EXTENSIONS
, але перед групою PRE_ROUTER_EXTENSIONS
. Причому зверніть увагу, що BodyParserModule
і гадки не має, які саме розширення будуть працювати у цих групах, для нього важливим є лише:
- інтерфейс даних, який будуть повертати розширення з групи
ROUTES_EXTENSIONS
; - порядок запуску, щоб роути не були встановлені ще до роботи цього модуля (тобто щоб група
PRE_ROUTER_EXTENSIONS
працювала саме після нього, а не перед ним).
Це означає, що BodyParserModule
буде брати до уваги роути, що встановлені за допомогою декораторів @route()
або @oasRoute()
, або будь-яких інших декораторів із цієї групи, оскільки їх обробкою займаються розширення, що працюють перед ним у групі ROUTES_EXTENSIONS
.