Pull to refresh

Comments 32

Было бы очень удачно, если бы схема, описания с учетом требований swagger/openAPI, могла быть использована и для валидации и для формирования документации.

Для Hapi есть плагин hapi-swagger, который именно это и делает.

Я предполагал что должно быть что то в этом роде. В конце концов есть просто библиотека joi-to-swagber которая делает это. Но остается у меня два вопроса. Насколько перекрываются спецификация swagger и api joi. И второе. Как там обстоит дело с описанием response.

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

Популярность express — это только самовоспроизведение опыта по туторилам, те, кто входят в ноду. Это очень сильно раздудо студентами. Большинство популярных плагинов по express форкнуты менйнтейнерами fastify. Сложность обучения при переходе с express минимальная.
Автор указал, что работал с Nest.js, который под капотом по-умолчанию использует express.js. К счастью, в документации описан способ использовать Fastify.js вместо express.js.

Вцелом nest.js почти независим от фреймворка. Кстати я как раз использую с fastify и даже описал это в сообщении https://habr.com/ru/post/514948/
Когда на это проект принел новый разработчик первое что он попробовал сделать это воспользоваться зоваться возможностью добавить экпрессовское middleware мотивируя тем что вре при мало а так он сэкономит все массу времени и нервов. Но не тут-то было. Fastify.

В принципе подход разработки с middlewares очень усложняет разработку. В них должно быть что-то техническое, например, валидации, авторизация и тд, но когда люди начинают навешивать в этот бедный реквест ВСЁ, начиная от подключения базы данных, до каких-то сервисов, хочется плакать. Вот люблю я nest за то, что там есть IoC-контейнер, всё делится на модули, можно спокойно использовать DDD. Мне кажется, ноде не хватает более строгих и функциональных фреймворков с устоявшимися практиками, как, например, в Java (Spring) и в C# (ASPNET Core), потому что по моему опыту почти в каждой компании кто-то пытается сделать что-то своё поверх express/koa/fastify/итд и далеко не один раз...

Как я выяснил для себя недавно, fastify, все же больше ем еще одна вариация на тему Express.js. Просто мы пирвыкли смотреть на все фреймворки сквозь призму Express.js, а fastify предлагает всеже не порядок более продвинутый наор средств.

А объясните пожалуйста зачем какому-то условному новичку (который примерно понимает как работает веб, как ходят запросы и что такое tcp) использовать все эти express/koa/fastify/nest/etc nodejs-фреймворки? Вот стоит перед новичком задача — со стороны клиента получить какие-то объекты с данными из бд или отправить объект с метаданными (для того чтобы изменить данные в бд). Новичок и так будет знать что вся информация в вебе ходит поверх тсп-протокола (который уже обеспечивает целостность данных при передаче), но чистый тсп в браузерах недоступен и браузеры предлагают два варианта — http или websockets
И какой самый простой способ решить эту задачу? Зачем этому новичку следует выбирать всякие express/fastify/nest-фреймворки со всякими непонятными middleware/сontrollers поверх http/rest подходов со всякими тонкостями/нюансами/дилеммами выбора методов (post vs put), способа передачи информации (path/query/headers/body) и выбора кодов для ошибок, зачем все это если можно просто установить вебсокет-соединение и просто пересылать нужные объекты/json от клиента к серверу и обратно. То есть исходная задача (установка транспорта между клиентом и сервером чтобы пересылать данные) с вебсокетами решается на порядок проще без всяких фреймворков чем c этими http/rest-фреймворками вроде express/fastify/nest и со стороны новичка становится совершенно непонятно зачем выбирать тот же fastify вместо вебсокетов

Ответ на это ворос немного философский. Почему браузер стал более попууярной средой выполнения приложений чем десктопные приложения, а протокол http более поппулярным чем CORBA или rmi. По моему давнему убеждлению все дело в существующей инфрастрктре и истории. С однрой сторны мы имеем сложивщуюся ифнратсруктуру которая может связать самых удленных друг от друга клиентов. С другой стороны мы имеем набор готовых средства разработки и специалистов которые меют с этимс средствами работать. Следсвтием этоо является то что продукты которые совсем не преназначались для работы распределенных приложений имеено с ними и работаю (браузеры, http), а средства которые как раз и созлавалис для тако раоты востребованы или меньше как J2EE или о них уже никто не вспоминает.


Кстати о веб-сокетах. Организовать общение по веб-сокетам не такая уж и тривиальная задача если задаваться такими вопросами как масштаируемость и гарантированная оставка сообщений. И там не без своих фреймворков или даже брокеров обходится. Я например пытаюсь максимально для того продвигать MQTT брокеры. Также интерес вызывают такие спецификации как WAMP протокол.

Websocket соединение довольно тяжёлая stateful штука, с кучей нюансов. Совсем не тривиальный инструмент. Всякий раз когда мне приходилось с ним сталкиваться "на боевую" приходилось долго материться. Особенно если интернет соединение нестабильное. Его debugging тоже не самый удобный.


Не рекомендую его использовать без реальной необходимости. У него есть спектр задач, для которых он хорошо подходит. По сути — все ситуации где инициатива исходит со стороны сервера, а не клиента. Т.е. нужно постоянное соединение. Для большей части задач в web-е это слишком дорого и слишком сложно.


Обычные HTTP запросы по сути своей гораздо проще. Клиент стрельнул в сервер запросом. Сервер выстрельнул клиенту в ответ. Задача решена — все довольны. Изолированная операция поверх текстового протокола. Может быть полностью stateless, что удобно для highload.


А все эти REST/RPC PUT/DELETE/OPTIONS/GET/POST и прочее это просто уровни абстракции придуманные самими разработчиками для удобства. Если писать большое приложение на WebSocket-ах вы придумаете свои такие же. Просто чтобы не сойти с ума пытаясь это всё заоркестрировать. Вам потребуется как-то отделять мух от котлет, так что вы придёте к некой схеме. Чем крупнее будет проект, тем больше вещей захочется обобщить, тем более вычурной эта схема будет.

Как вебсокеты могут быть более тяжёлой штукой чем http-запросы? Вы понимаете как работает http? HTTP-запрос это точно такое же тсп-соединение как и вебсокеты и ничем не легче по "тяжести". И если интернет-соединение нестабильное то http-запрос будет точно также обрываться с ошибкой как и вебсокет-соединение (и соотвественно нужно делать повторный запрос/установку соединения), в этом плане я вообще не вижу никакой разницы.


А вот во многих моментах http намного хуже и имеет больший оверхед.
Во-первых если не использовать keep-alive то браузер будет создавать тсп-соединение для каждого http-запроса и здесь http выходит намного тяжелее.
Во-вторых передача статуса и хедеров в текстовый формате для каждого запроса в итоге имеет больший размер и оверхед и это особенно видно при передаче небольших порций данных (с вебсокетами это может быть десяток байт когда же с http запрос будет занимать от сотни байт)
В-третих с http у браузеров есть ограничение на количество одновременных запросов (чего нет с вебсокетами).
В-четвертых проблема идемпотентности с http решается намного сложнее чем с вебсокетами (с http порядок запросов не гарантируются и они на сервер могут прийти в другом порядке чем были отправлены из клиента, когда же с вебсокетами у нас есть строгий порядок сообщений)


Ну и наконец вебсокеты это очень простая спецификация (по сути хедер с размером сообщения поверх тсп-протокола) и написать парсер вебсокетов можно за пару часов (формат фрейма объясняют даже для новичков) в то время как написать парсер http (не говоря уже про http2) это приключение на месяц учитываю кучу разнообразных нюансов самого текстового http формата, так и специфику корректно обработки различных хедеров (в частности keep-alive)


А все эти REST/RPC PUT/DELETE/OPTIONS/GET/POST и прочее это просто уровни абстракции придуманные самими разработчиками для удобства. Если писать большое приложение на WebSocket-ах вы придумаете свои такие же.

Нет, такого беспорядка и дилеммы выбора как с http не будет, зачем? Зачем нужны эти имена методов и статусы кодов ошибок если можно просто передать скажем объект {action: "createProject", params: [...]} и вернуть объект {error: "..." | null, data: "..." | null} и вебсокет-обработчик на сервере просто вызовет нужную функцию и уже в самой функции будет происходит вся необходимая обработка — будь то получение (GET) или обновление данных (POST, PUT) или удаление (DELETE) или когда все эти действия происходят одновременно вместе с if-ами и циклами для батч-обработки каких-то данных (что просто не имеет соответствия "семантическим" методам в случае http). То есть исходная задача — вызывать какую-то процедуру на сервере и передать ей нужные параметры чтобы она обработала "запрос" и получила/обновила нужные данные в бд и вернула ответ назад с вебсокетами решается на порядок проще чем с http (где вызов нужной процедуры почему-то додумались делать через ручное придумывание и конфигурацию имен для роутов с маппингом на path/query http-запроса)

Как вебсокеты могут быть более тяжёлой штукой чем http-запросы? Вы понимаете как работает http?

Хотя вопрос не ко мне отвечу. Нагрузка создаваемая веб-сокетами приниципально имеет другой характер чем нагрузка REST запросами. Основная нагрузка идет на поддержание соединений по сокету и прослушивание каналов сокета. Пожтому модет быть прадаоксальная ситуация когда ни один запрос не выполдняется а сервер ложиться под нагрузкой. Особенно это касается такие севрера как nodejs.


В сообщении https://habr.com/ru/post/469315/ я описал как это модно обойти используя брокеры желательно разработанные на Erlang так как он как раз хорошо поддерживает много открытых соединений.

И если интернет-соединение нестабильное то http-запрос будет точно также обрываться с ошибкой как и вебсокет-соединение

С точки зрения программиста оборвётся не HTTP соединение, а просто его запрос. У программиста не болит голова по поводу:


  • когда устанавливать соедиение
  • что делать если оно оборвалось
  • что делать если оно постоянно обрывается
  • когда завершить соединение

Это ведь намного более простая парадигма. Мы что-то хотим от сервера? Стреляем в него запросом, вот наш ответ. Все запросы "типа" изолированы. Все действительно сложные вещи остаются за кадром (всякие сжатия заголовков, HTTP2, TLS и пр.). Мы просто орудуем довольно простыми примитивами.


Ну и наконец вебсокеты это очень простая спецификация

А в реальности как-то это не очень помогает писать надёжные приложения. Видишь приложение на вебсокетах (условный чат со службой поддержки) — готовся к тому что "чат почему-то перестаёт обновляться". Открываешь в соседней вкладке его же — всё работает. А та таба проклята :D


Зачем нужны эти имена методов и статусы кодов ошибок если можно просто передать скажем объект

Ну это всё на ваше усмотрение. Никто ведь не заставляет вас их использовать в случае HTTP. Это ведь просто уровень абстракции. То что вы ниже описали мы как раз и используем с RPC over HTTP, без websocket-ов.


То есть исходная задача — вызывать какую-то процедуру на сервере и передать ей нужные параметры чтобы она обработала "запрос" и получила/обновила нужные данные в бд и вернула ответ назад с вебсокетами решается на порядок проще чем с http

Я всё ещё не вижу никакой разницы и никаких "на порядок". А учитывая что от вас это всё изолировано тем более не понимаю о чём сыр бор? Вы как в случае вебсокетов можете гонять туда сюда json-ы, и если надо хоть бинарные данные, так и в случае HTTP можете в BODY хоть свой собственный формат запихать. Было бы желание. В чём вы тут видите принципиальную разницу?


Как вебсокеты могут быть более тяжёлой штукой чем http-запросы?

В этом я плаваю. Пожалуй стоит почитать как оно низкоуровневое работает. Но вроде суть в том что оно постоянное устойчивое соединение. Если вам оно не нужно (собственно обычно так и есть), то зачем мучить и клиент и сервер?

Как-то делал API react-admin на сокетах при помощи socket.io
Изначально было известно что с админкой будет работать мало людей и нарузки не будет.
Описал https://habr.com/ru/post/459978/ как это было. Парадокс в том что для структурирвоания кода пришлось написать мини-роутер который делает примерно то же что Express.js. Не совсем представляю как тредстартер делает работу с сокетами "просто" и с "начинающими разработчиками" без всякого фреймворка.

деление логики на модули. Вот представьте что вам нужно сделать апи. часть публичная часть идет с авторизацией. если у вас есть 20 ендпойнтов с авторизацией то вам вначале каждогой обработки запроса надо будет писать однообразный код для проверки доступа. даже есть это будет вызов единственной функции. а так этот код вы перемещаете в мидлварь и указываете что этот запрос должен использоватб эту самую миддлварь. Получается что выпишете меньше кода и код лучше организован.

  fastify.get('/sync-error', async () => {
    if (true) {
      throw new CustomError('Sync Error');
    }
    return { hello: 'world' };
  });

Несмотря на отсутствие await у вас тут async. Т.е. это никакие не синхронные ошибки. Они асинхронные.


ЕМНИП то fastify просто съедает действительно синхронные ошибки. Я даже как-то кастомный валидатор писал, что проверял что абсолютно все route-handler-ы это async-методы. Но может быть с тех пор что-то изменилось.

async убрал. Не обратил внимание на копипасту (первым реализовал как раз асинхронный роут так как именно по асинхронным были проблемы у Express.js) Результат тот же — работет.

Отлично. Видимо багу исправили. И я могу убирать свои костыли.

Проблема поддержки async/await в Express решается одной строкой


require('express-async-errors');

После чего все middleware будут обернуты в promise.


Проблема, middleware hell скорей заключается в незнание разработчиками каких-либо других архитектурных паттернов, чем в самом Express.


Да, Express очень легковесный фреймворк, как впрочем и Koa, Fastify…
Несколько раз переписывал лапшу из middleware просто написанную с другим синтаксическим сахаром на этих фреймворках. Да, с ними надо на берегу спроектировать архитектуру, а фреймворк использовать лишь как изолированный транспортный уровень.


Но преимущество Express в том, что под него написано куча вспомогательных пакетов на npm для разных задач. А с Koa, Fastify и подобными все гораздо хуже и зачастую надо тратить много времени на написание оберток для интеграции с ними.


Если хочется "жесткий" фреймворк, то стоит смотреть на AdonisJS(аля Laravel), NestJS(аля Spring), LoopBack

Кроме асинхронных ошибок есть еще кое-что асинхронное — инициализация асинхронных сервисов. По куче пакетов из тех что используются в каждом проекте — вроде бы все уже портировано в стандартные то есть разработанные командой fastify плагинами. То факт что в fastify из коробки практически вложена валидация входных/выходных параметров и самодокументирвоание экономит такое количество времени что написание плагина для интеграции вместо кем-то разработанного middleware своего сильно не напряжет никого.
Подчеркну что от веб-фреймворка я хотел бы получить сразу такой набор функциональности без особых услилий со своей сторны


1) Наличие полноценных контроллеров (возвращаемое значение функции возвращается клиенту в теле ответа).
2) Удобная обработка синхронных и асинхронных ошибок.
3) Валидация входных параметров.
4) Самодокументирование на основании определений роутов и схем валидации входных/выходных параметров.
5) Инстанциирование асинхронных сервисов.
6) Расширяемость.

Из всех Express.js не дает ни одного. Разве что условно можно назвать его "расширяемым" если расширяемостью модно назвать добавление еще одного middleware которое делает еще что-то req.


Вот например как пакет на который вы ссылаетесь "расширяет" Express.js


function patchRouterParam() {
  const originalParam = Router.prototype.constructor.param;
  Router.prototype.constructor.param = function param(name, fn) {
    fn = wrap(fn);
    return originalParam.call(this, name, fn);
  };
}

Несколько таких "расширений" и работа фреймворка может стать непредсказуемой.

Спасибо посмотрел по Вашей наводке на AdonisJS. Слышал что появился новый фреймворк на декораторах но не знал какой. NestJS я не воспринимаю как жеский фреймворк. Жесктость как правило связывается с невозможностью расширять фреймфорк, а в этом смысле у Nest.js все хорошо расширяется. В то же время такие фреймворки как sails, loopback я воспринимаю действительно как жесткие, которые самостоятельно не расширишь — нужно знать как строено ядро тогда может быть. AdonisJS кажется принадлежит также к клссу загрытых систем. Впрочем, нашем разраотчику сложно это понять, но в штатах по ппулярности (говорят) Лара сильно популярнее Симфони.

Адонис — это попытка пыховые фреймворки перетянуть в js. Мне кажется это вкорне неверный подход, языки слишком разные. Должна быть другая архитектура, нежели перетасканная из лары.

sails, loopback — сейл давно мертв, но там какой-то дизайер продожает обновлять сайт и копирайты. А версия за 5 лет не тронулась с места.

loopback — псевдобесплано, поддержка за бабло, а она судя по инфе в сети понадобится.

Я все это изучал как-то, мониторил много отзывов — сейчас fastify просто вне конуренции того, что вы перечислили. Ну разве что nestjs но это чуть другое. Я посмотрел лекцию главного мейнтейнера когда он перезентовал fastify, и убедился что он прекрасно знаком с тем, что есть среди конкурентов. А уж как он express перековырял, достойно похвалы, потому он и решился на fastify.
Насчет Adonis не соглашусь с вами, изначально конечно AdonisJS шел по пути перетаскивания идей из лары, но в последнее время особенно с пришествием AdonisJS 5-ой версии, все в корне поменялось. Делаем на нем энтерпрайз проекты и проблем не знаем. Очень красивый, дружелюбный и масштабируемый фрейм. А если собрать его в Docker alpine то image на выходе весит в районе 150мб (лара под гиг)

Еще как плюс очень много разработчиков знающих лару, за считанные дни могут разобраться с адонис. Если AdonisJS 5 сравнивать с тем же NestJS, то по моему мнению NestJS со своей избыточной магией и в подметки ему негодиться.
Хорошо, посмотрю что там с пятеркой. А fastify не ковыряли? Более модульный?
Но преимущество Express в том, что под него написано куча вспомогательных пакетов на npm для разных задач.


Fastify перетянул большинство популярных пакетов из express. Я вообще не понимаю, зачем вспоминать про express. Когда то давно множество студентов понаписало на нем миллиард курсовиков, а теперь этот нафталин из сети не выметешь вместе с их статьями, и кажется, что это некий популярный феймворк! Конечно популярный, но среди студентов и их курсовиков и их жертв исторических. В здравом уме сейчас нельзя его рассматривать как основу. Есть же архитектурно три раза переосмысленные вещи! Fastify именно так и задумывался.
Я думаю самое главное зло, это сама концепция цепочки middleware и то, что мидлвары вообще могут что-то менять в реквесте. Что, на мой взгляд могло бы сделать эту концепцию немного более приемлемой:
1. Концепция контекста. Сам реквест readonly, если надо передать что-то дальше по цепочке — для этого есть объект контекста.
2. Мидлвара может добавлять свою информацию в контекст, и может читать то, что добавлено другими мидлварами, которые установлены для нее в зависимостях, что приводит нас к следующему пункту.
3. Должна быть реализована система зависимостей, т. е. например, авторизация может зависеть от аутентификации, а аутентификация от мидлвары, которая вытаскивает Auth token из реквеста.

Если бы изначально такое было сделано в экспрессе, было бы сейчас значительно меньше проблем.

Один из путей преодолеть проблемы это фреймворк на основе Express.js. из популярных есть два решения sails и nestjs… Также мне приходилось встречать решения когда разработчик пытается придерживаться строгих правил. То есть пишет контроллеры, data transfer object, data persistent object, сервисы, валидаторы. Чтобы придать строгость все это располагается в отдельных.каталогах. Это вариант наверное чуть лучше чем простой поток кода. Но все равно объект req/req прикидывался из роутов дальше. И еще наблюдение обычно так делают программисты кт орые пилят проект водиночку. Добавление второго разработчика быстро ломает систему так как то что понятно автору такой системы может быть не понятно или даже противоестественно для другого разработчика. Сам один раз пытался сделать небольшую обвязку вокруг Express в плане обработки ошибок. Чтобы они генерировали ответы с нужным статусом и локализованные на языке клиента. Посде подключения второго разработчика вся система быстро начала рушится. Так как у него был свой взгляд на вещи и дедлайны и п т.
Собственно nestjs.js дал такие средства. И я уверен что в ближайшее время мы будем наблюдать его рост популярности. Я для себя сделал выбор в пользу nestjs.js. Конечно вот был недавно проект который казался маленьким и был соблазн заполнить на Express. Потратив лишние полтора часа на nestjs я получил Профит когда внезапно на это проект начали приходить новые и новые фичи. При этом то как я люблю с интеграцией с разнообразными сторонними службами. И когда код не выходит из под контроля возвращаться на Express.js уже не хочется никогда.Конечно я долго к не в пр сматривался. Сомнений было два: nestjs мог оказаться недостаточно гибким и расширяемым например как sails от которого не очень приятный опыт и что команда nestjs может прекратить разработку. Ну и в прошлом году понял что это не так и можно переходить.

Какой sails! Господь с вами! nestjs хоть православен для тех кто перешел с c#, java — он им приятен и понятен, а sails умер уж лет 5 назад! Они большие молодцы, у них красивый сайт, сбивающий с толку. Это полностью мертвый фреймворк. Вот сейчас зашел глянуть версии — все так же без изменений.
Непонятно что с декораторами в JS не так? Babel 7 лет как умеет с ними обращаться. Или дело в нативной поддержке браузерами? Так TS вообще ничем не поддерживается без трансплайта, но почему то об этом никто не ноет…
Ну и лаконичнее и гибче Koa ничего нет, простой как велосипед. Разница только в том, что тут обвес сам выбираешь, а в других схожих фреймах за тебя уже решили, вот и все.

Проблема с декораторами такая что уже давно тянется разделение на legacy и poropsal декораторы. Первые это как все привыкли и как декораторы работают в typescript. Вторые это как предлагается сделать в стандарте. Пока не будет полного консенсуса как двигать этот стандарт дальше он не будет принят и соответсвенно этот функционал не попадет в nodejs. АНалогичная долгоиграющая история была с импортом которы неаонец то включен в стандарт. Пока что перспективы как могу предположить такие что еще несколкьо лет декораторов в нативном nodejs не появится.


Что касается Коа то идеологичпески этот тот же Expresss только минус некоротые его части такие как шаблонизаторы и плюс асинхронные контроллеры. Думаю что разраотка на Коа сталкивается с теми же проблемами что и разработка на Express.js.

Ну нативный-не нативный нам же без разницы, если собираться с бабелем, одним пайпом больше при сборке CI или одним меньшем не особо волнует как бы.
К первому стандарту успели привыкнуть только TSники, а так неудачная по гибкости спецификация вышла, так что она и не задержалась в бабеле/JS. Да и если очень хочется, то можно одновременно поддерживать обе спецификации декораторов во фреймворке, а уже в рантайме подстраиваться под нужную спецификацию декоратора исходя из вызываемого контекста. У себя в пакете в качестве эксперимента такое запилил и в принципе это работает, как минимум, для декораторов класса и методов ибо очень не хотелось 2 версии библиотеки поддерживать для окружений, с легаси и драфтом декораторами.

Про производительность написано только в заголовке. Почему?

Sign up to leave a comment.

Articles