Проблеми TypeScript у світі React-додатків вiд Iллi Климова на React fwdays | 27 березня
×Закрыть

Ditsmod — новий TypeScript веб-фреймворк для Node.js

Підписуйтеся на Telegram-канал «DOU #tech», щоб не пропустити нові технічні статті.

Ditsmod є Node.js веб-фреймворком, його назва складається із DI + TS + Mod, щоб підкреслити важливі складові:
він має Dependency Injection, написаний на TypeScript, та спроектований для хорошої Modularity (тобто модульності).

Головні особливості Ditsmod:
  • Зручний механізм указання та вирішення залежностей між різними класами застосунку: ви в конструкторі указуєте інстанси яких класів вам потрібні, а DI бере на себе непросту задачу «де їх взяти».
  • Можливість легко підмінювати by default класи в ядрі Ditsmod своїми власними класами. Наприклад, швидше за все, ви захочете підмінити клас логера на ваш власний клас, оскільки by default логер записує логи лише в консоль.
  • Можливість легко підмінювати класи вашого застосунку тестовими класами (mocks, stubs), не змінюючи при цьому код вашого застосунку. Це дуже суттєво спрощує тестування.
  • Ditsmod спроектований, щоб забезпечувати хорошу модульність всього застосунку, а отже і хорошу масштабованість. Його DI підтримує ієрархію, а це означає, що ви можете оголошувати одинаків: або на рівні усього застосунку, або на рівні конкретного модуля, або на рівні HTTP-запиту.

Ті, хто знайомий з Angular, помітить, що деякі концепції архітектури цього фреймворка дуже схожі на Angular концепції. Це справді так, більше того — сам DI фактично витягнутий з Angular v4.4.7. (з мінімальними допрацюваннями) та інтегрований в Ditsmod.

Мотивація для створення Ditsmod

Рідний Node.js веб-сервер надає мінімальну функціональність для прийому HTTP-запитів та відправки HTTP-відповідей. Він не надає систем роутінгу, парсингу тіла запиту, логування, обробки помилок, тестування. І оскільки в реальних застосунках уся перерахована функціональність є базовою, виникає потреба у додатковому веб-фреймворку. З іншого боку, уся перерахована базова функціональність є майже у будь-якого Node.js веб-фреймворка, але це ще нічого не говорить про зручність його використання.

Огляд ExpressJS та KoaJS

Давайте глянемо на зручність використання, наприклад, ExpressJS та KoaJS. Вони використовують дуже мало коду для «Hello, world!», але в реальних застосунках нам треба вирішувати значно складніші завдання, розподіляючи наш код на десятки чи навіть й на сотні файлів. Архітектура даних фреймворків формувалась задовго до виходу ES2015, тоді вони вирішували проблему «callback hell» за допомогою створення послідовного ланцюжка викликів так званих middleware, і в кожному із них повинен був викликатись наступний middleware, посилання на який, частіше за все, передавалось у змінну із назвою next.

Таким чином, можна сказати, що головна особливість архітектури цих фреймворків була актуальною до виходу ES2015, коли у JavaScript з’явились Promise та класи. Хоча виходили в реліз ES2015, ES2016, ES2017... майже нічого не змінилось в архітектурі ExpressJS, а у KoaJS 2 додались нові фічі із функціями-генераторами, із функціональністю async/await, із додаванням об’єкту контекста. Але все одно стара архітектура із послідовним ланцюжком викликів middleware нікуди не зникла. В плані модульності теж майже нічого не змінилось, окрім хіба що нового способу експорту/імпорту JavaScript модулів, але стара проблема з упорядкуванням структури проекта не зникла.

Як говориться у туторіалі на developer.mozilla.org, ExpressJS дуже мінімалістичний і гнучкий фреймворк, але це часто й ускладнює створення правильної структури проекту:

While Express itself is fairly minimalist, developers have created compatible middleware packages to address almost any web development problem. There are libraries to work with cookies, sessions, user logins, URL parameters, POST data, security headers, and many more. You can find a list of middleware packages maintained by the Express team at Express Middleware (along with a list of some popular 3rd party packages).This flexibility is a double edged sword. There are middleware packages to address almost any problem or requirement, but working out the right packages to use can sometimes be a challenge. There is also no «right way» to structure an application, and many examples you might find on the Internet are not optimal, or only show a small part of what you need to do in order to develop a web application.

Судячи із постів на різних блогах, ExpressJS та KoaJS використовують майже завжди на малих проектах, а також для мікросервісів, де якраз кожен «мікросервіс» сам по собі являється малим застосунком.

Огляд NestJS

TypeScript вніс у світ JavaScript, можна сказати без перебільшення, революційні можливості для зручності написання коду в IDE, можливості для дуже суттєвого зменшення кількості помилок, а отже — проекти написані на TypeScript можна робити значно більшими.

На даний момент є досить популярний веб-фреймворк написаний на TypeScript — це NestJS. Перший стабільний його реліз був в кінці 2017 року, тобто вже після виходу ES2015, ES2016, ES2017. Його автор дуже активно розвиває своє дітище, а тижнева кількість скачувань із npmjs.com складає вже майже 500 тис. і продовжує стабільно рости.

Видно що автор NestJS дуже старанно намагається додати до цього фреймворка усе саме сучасне і
прогресивне. Справді, NestJS:

  • має власний CLI
  • має велику кількість декораторів для різних потреб
  • впроваджено багато фіч, дуже схожих на Angular фічі: Dependency Injection, провайдери, модулі,
    pipes, guards, interceptors, lifecycle events...
  • має підтримку TypeORM
  • має підтримку OpenAPI
  • має підтримку GraphQL
  • має підтримку Websockets
  • має рекомендації для використання мікросервісів
  • ...

Так чому ж, знаючи про NestJS, виникла потреба у створенні Ditsmod? Головною причиною була моя суб’єктивна оцінка NestJS, мені здалось що можна написати веб-фреймворк із простішим API але, разом із тим, зробити його більш потужнішим як в плані продуктивності, так і в плані масштабованості.

Огляд Ditsmod

Хоча Ditsmod у своєму ядрі має код для підтримки хорошої модульності застосунку, бенчмарки з Hello World показують, що він є навіть швидшим за один із найпопулярніших та найлегших фреймворків — за Koa v2:

req-per-sec-frameworks.png

На цій діаграмі бенчмарку з Hello World, самим швидшим є Fastify. Але, як сказано у його документації — Fastify навіть швидший за рідний Node.js вебсервер. Як таке може бути? — Відповідь на це запитання мені не відома, але відомо, що стабільна версія цього фреймворку вийшла в реліз більше 4 років назад, а він чомусь не має великого кута нахилу кривої популярності (судячи зі статистики скачування). Можливо заточеність цього фреймворку саме на швидкості має побічні ефекти у вигляді незручності використання, мені не відомо.

Другим по швидкості йде NestJS на платформі Fastify. Але дивно, що таке поєднання не є супер популярним, адже якщо порівнювати це зі статистикою скачування NestJS на платформі ExpressJS, то виявиться, що їх рахунок 9:1 на користь NestJS + ExpressJS. І це при тому, що NestJS + ExpressJS є самим повільним варіантом (див. останній стовпчик на діаграмі). Звичайно ж, тут має місце вплив популярності самого ExpressJS, але ж NestJS позиціонується як Platform agnosticism (тобто, в теорії, можна писати код у NestJS не залежно від платформи фреймворку, на базі якої він працює). Швидше за все, заявлений «Platform agnosticism» не працює на практиці так, як було задумано в теорії.

Код із даними бенчмарками ви можете знайти у цьому репозиторії.

Більш детально познайомитись з Ditsmod.

👍НравитсяПонравилось2
В избранноеВ избранном1
Подписаться на тему «Node.js»
LinkedIn

Похожие статьи

Допустимые теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Допустимые теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Аха. Більше того — Ditsmod має значно простішу функціональність, тому поріг входження в нього значно нижчий, ніж в Angular. А при бажанні потім освоїти ще й Angular, початківцям буде точно простіше. Якщо ж ви вже знайомі з Angular, то для вас Ditsmod взагалі буде легкотньою.

До речі. Зараз виявилось, що nodemon вміє працювати з TypeScript-файлами. Це взагалі шикарно! Тепер можна приклади запускати, щось змінювати в них і зразу бачити результат. Прям як в Angular CLI.

Мабуть це ваш сарказм про хайпові технології, так? Якщо ні — може ви бачите більше перспектив в deno? Я щось не побачив. До речі, deno форкнув мій форк markdown. Дрібниця, але приємно =).

прикольно що ти розібрався як написати свій нест, я теж в такому напрямку працював коли писав лібу для роботи неста з JSON-RPC
але дивлячись на реальну ситуацію ніхто не візьме цей фреймворк в продакшин

переваг не побачив, хіба що тільки префікси для контролерів та модуля
але це можно у несті зробити за допомогою декораторів для контролерів

не бачу в цій фічі великої необхідності тільки для того щоб написати новий фреймворк

також побачив що ви для контролера передаєте йому в конструктор Request & Response, то ви на кожен Request створюєте інстанс контролера, так ?

> Пріоритетність провайдерів
можно голову зламати, ніхто не хоче думати про це

переваг не побачив, хіба що тільки префікси для контролерів та модуля
але це можно у несті зробити за допомогою декораторів для контролерів

О, видно професійний погляд =). Мені теж це прямо зразу впало в око, це при тому, що сам Nest декларується як масштабований фреймворк. Автора цього фреймворку просили на github не один раз впровадити цю фічу. Їх відсилали куди подалі, поки один із контрибуторів не написав відповідний модуль.

Звичайно ж я розумію, що усім відразу не угодиш, але це прямо таки очевидно необхідна річ у фреймворку, що декларується як «масштабований».

також побачив що ви для контролера передаєте йому в конструктор Request & Response, то ви на кожен Request створюєте інстанс контролера, так ?

Так, передаю Request & Response і створюю інстанс контролера за кожним запитом, як і належить. У Nest це ще одна дивина, що він рекомендує створювати глобальні інстанси контролерів, згадуючи при цьому за додаткове прискорення застосунку. Думаю ви самі розумієте чим грозить глобальний інстанс контролера для застосунку, де кожен запит має асинхронно оброблятись, так же?

Можливо ви відкривали код ядра Koa 2 та бачили що він майже порожній (без будь-яких фіч, навіть без роутера). Так ось, Ditsmod на Hello World навіть трохи обганяє Koa 2, це при тому що у ядрі Ditsmod працює DI, і за кожним запитом створюються контролери і усі необхідні сервіси для нього.

але дивлячись на реальну ситуацію ніхто не візьме цей фреймворк в продакшин

А ви знаєте приклади, що нові фреймворки сходу вкидали в продакшен? Звичайно ж потрібен період його обкатки.

«Пріоритетність провайдерів» — можна голову зламати, ніхто не хоче думати про це

Так отож. Ніхто над цим не париться, до поки не починають проявлятись баги, що важко відстежити.

в прямому сенсі слова, я не хочу розбиратися в пріоритетах
про які баги ви говорите ? в несті ?

А ви знаєте приклади, що нові фреймворки сходу вкидали в продакшен? Звичайно ж потрібен період його обкатки.

тут суть не в цьому що потрібен час, а про те що ще один нест не потрібен

Так, передаю Request & Response і створюю інстанс контролера за кожним запитом, як і належить. У Nest це ще одна дивина, що він рекомендує створювати глобальні інстанси контролерів, згадуючи при цьому за додаткове прискорення застосунку. Думаю ви самі розумієте чим грозить глобальний інстанс контролера для застосунку, де кожен запит має асинхронно оброблятись, так же?

глобальний інстанс це не про глобальні змінні як би мовити
а про те що не має сенсу кожен раз створювати контролер на кожен запит, для того придумали Singleton

можливо я чогось не знаю, бо писав на несті півроку тому

глобальний інстанс це не про глобальні змінні

By default, Nest в DI створює singleton для контролера і він віддається для обробки кожного запиту. Це означає, що якщо розробник надумає створити змінну в межах контролера, то її вміст будуть бачити та перезаписувати усі інші запити по цьому маршруту одночасно.

З іншого боку, Nest контролер проглядає в якому scope створюються сервіси, і якщо він знаходить сервіс зі scope на рівні запиту, то такий контролер буде створюватись за кожним запитом теж. Ну і в метаданих контролера теж можна сказати, що він буде створюватись за кожним запитом.

Але дуже сумнівною є потреба створювати інстанс контролера рідше, ніж за кожним запитом. В такому разі розробнику досить легко попасти в халепу.

By default, Nest в DI створює singleton для контролера і він віддається для обробки кожного запиту. Це означає, що якщо розробник надумає створити змінну в межах контролера, то її вміст будуть бачити та перезаписувати усі інші запити по цьому маршруту одночасно.

тут просто треба розробника з такими бажаннями створювати глобальні не пускати за проект

про які баги ви говорите ? в несті ?

За Nest в цьому плані може ви мені підкажете, я не в курсі.

Уявіть, що у вас є Module1, куди ви імпортували Module2 та Module3. Ви зробили такий імпорт, бо вам потрібні Service2 та Service3 із цих модулів. Ви проглядаєте результат роботи даних сервісів, але по якійсь причині Service3 працює не так як очікується. Ви починаєте дебажити, чому так відбувається, і виявляється, що Service3 експортують обидва модулі: Module2 та Module3. Ви очікували, що Service3 буде мати поведінку таку, як вона задокументована у Module3, але насправді спрацювала та версія, що експортується із Module2.

Ось про які я баги кажу. Nest із цим якось справляється?

Щойно перевірив роботу Nest у цій ситуації. Підставляється той провайдер, чий модуль був імпортований останнім. І це вносить непередбачуваність, яку не просто дебажити, особливо коли модуль приходить із зовнішньої бібліотеки.

Будь-ласка, не перекладайте технічні терміни, такі як singleton, іншою мовою. Дякую.

Якщо писати сінглтон — не комільфо, можна написати англійською. Вставляти посилання — краще, ніж не вставляти в данному випадку, адже тоді було б взагалі не зрозуміло про що йде мова.

IMHO.

Юрій, для singleton, в даному випадку, є хороший український відповідник — одинак. Не звично і вухо ріже — згоден, мені теж ріже, але це не привід панікувати. З часом люди звикають. А якщо таки немає сил терпіти, то дочекаєтесь англійської документації, вона обов’язково теж буде, але вже із першого релізу (зараз ще бета).

Не зрозумів про які посилання йде мова (чи може посилання лінками краще називати ;)?

Посилання під словом «одинаків» на початку статті.

А, це ви типу кажете, що «якщо пишете слово одинак, то хоч завжди ставте лінки на singleton»?

Так, коли є посилання / лінка, зрозуміло про що йде мова.

Кому нужны эти бенчмарки, покажите примеры использование хотя бы типовых задач.

Це я запланував зробити найближчим часом — каталог examples буде мати більше прикладів, типу як це зроблено в Express.

Хоча думаю цінність цих прикладів буде значно нижчою, ніж вона є у Express, оскільки Ditsmod застосунки складатимуться з модулів, писати окрему якусь фічу простіше. Зв’язок між модулями дуже ефективно забезпечує DI, тому розробникам інтуїтивно зрозуміліше що треба зробити, щоб реалізувати задумане.

Є ще Ditsmod seed, зі стартовим темплейтом. До речі, хто не знав, github замість форку цього репозиторію робить копію без історії комітів.

Можу тут дати коротенькі відповіді, якщо вас щось конкретне цікавить.

Додав приклади із ControllerErrorHandler, із гардами, із різними логерами в різних модулях (лише для наглядності, щоб показати, що можна підключати різні логери).

о, новый фреймворк, наконец-то. Дякую!

Согласен, что-то давно новых Фреймворков не появлялось....

=) Так, ваша іронія мені зрозуміла, але таки серед TypeScript бекенд веб-фреймворків їх не так і багато насправді. TypeScript заслуговує на більше.

Є loopback.io, якого вистачає для 99% задач.

Це стара школа, чи не старіша за Express. Чи може він може похвалитись відповідними змінами після виходу ES2015... TypeScript. У нього є хоча б щось типу @types/loopback?

Колись думав на нього перейти. На той момент відлякало, що через CLI все пишеться (чи то я так думав...). А зараз схоже що вони навіть на TypeScript теж пишуть.

Так що, вони вже відійшли від middleware, є у них щось типу контролерів на класах ES2015+?

На той момент відлякало, що через CLI все пишеться

Це як доповлення. Все можна робити ручками.

Так що, вони вже відійшли від middleware, є у них щось типу контролерів на класах ES2015+?

Так, там є все що потрібно. Ось тут можна почитати детальніше: loopback.io/...​doc/en/lb4/Component.html

За ради інтересу зробив усе як loopback «Getting started» пише, із прикладом Hello World. Запустив бенч на швидкість, як завжди це роблю:

ab -n 50000 localhost:3000/hello

І воно видало результат — більше, ніж удвічі повільніше за NestJS на платформі Express (а це найгірший результат на картинці вгорі).

Трохи сумніваюсь, що воно таке повільне. Може якийсь логер працює, чи ... може воно біткойни добуває — не знаю, але тупить страшно.

Мабуть по кількості скачаних @loopback/cli можна міряти їхню популярність, так же? Тобто цей пакет скачують 3804 за тиждень. Прямо дуже скромні результати як для таких старожилів.

А, ні, @loopback/core скачують 33145 разів за тиждень. Все одно дуже скромні цифри. Якась причина має бути чому за стільки років вони не набрали собі хоча б таку популярність як у NestJS (500 тис. за тиждень).

ну це ж пакет cli, не кожен день треба cli ставити щоб розпочати новий проект

Подписаться на комментарии