Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
Будущее веб архитектуры программного обеспечения уже обретает формы и на этот раз это server-side рендеринг(опять). Но есть кое-что новенькое, а именно передача HTML по WebSocket.
Подход с использованием SPA(Single Page Appllication) и JSON API привел к тому, что многие команды разработки погрязли в бесконечных спорах о структуре данных и ошибках синхронизации между этими двумя слоями. Это увеличивает стоимость разработки, замедляет релизный цикл и сокращает время, которое можно было бы потратить на инновации.
Внимание web разработчиков привлекает новый WebSocket ориентированный подход. Данный подход имеет такие же плюсы, что и классические серверные фреймворки: быстрое прототипирование, хранение состояние на сервере, быстрая отрисовка страницы, быстрое добавление функций и простая поддержка SEO. При этом он обеспечивает многопользовательскую работу и отзывчивый интерфейс без создания двух отдельных приложений. Конечным результатом является единственный репозиторий, но при этом пользователи наслаждаются такой же отзывчивостью будто это полностью JavaScript приложение. При этом у нас более простая шаблонизация, меньше прелодеров с надписью «Загрузка…» и нет ошибок хранения состояния ведь оно живет в одном месте. Все это приводит нас на путь более простой (и быстрой) разработки софта.
Высвобожденное благодаря такому подходу время вы можете потратить на что-то более полезное и прекрасное. Потратьте время разработчиков и деньги вашей компании, с удовольствием самостоятельно создавая функционал, приносящий пользу вашей компании и клиентам.
И по моему мнению, нет лучше фреймворка для уменьшения количества рутины, чем Ruby on Rails. Взгляните еще раз на недооценённый Stimulus JS. Улучшите View в своем MVC с помощью ViewComponents. Добавьте библиотеки CableReady and StimulusReflex для получения реактивного Rails и ваш старый добрый авто снова готов к гонкам. Но мы позже вернемся к Rails…
Все началось с web фреймворков…
Бэкенд фреймворки для web появились примерно в 2005 году посреди моря самописных скриптов, объединяющих набор библиотек, которые запускались под Apache сервер. Представленная новая архитектура обещала разработчикам более целостный подход. Девизом этого подхода был «Соглашение над конфигурацией». Фреймворк решал за разработчика многие спорные моменты и давал ему возможность сосредоточиться на создании читабельного кода и быстрой разработке фич. Все что нужно было разработчику это изучить ядро фреймворка и его соглашения, а затем можно было начать создавать сложные web приложения, пока его коллеги продолжали писать конфигурационные XML файлы для всех других подходов разработки.
Несмотря на критику, которая всегда мешает новым подходам эти серверные фреймворки стали прекрасным инструментом, особенно для быстро развивающихся стартапов, испытывающих нехватку ресурсов, которым еще вчера требовалось привлекательное и с большим количеством фич приложение.
Но потом JavaScript захватил мир….
К 2010 году тенденции стали меняться и server-side фреймворки отошли на второй план из-за появления Single Page Application полностью разрабатываемого на JavaScript и работающего в браузере пользователя. Во многих компаниях, часть сервера была низведена до уровня раздачи данных по API, а большая часть бизнес-логики и весь HTML рендеринг происходил на клиенте. Из-за этого пользователям приходилось грузить большой бандл с JS кодом при первом посещении сайта.
Вот тогда-то все и стало ужасно.
Перенесемся обратно в 2020, где web не стал быстрее как нам обещали с SPA. Засовывание мегабайт JavaScript кода в глотку Iphone 4’s не улучшает пользовательский опыт. И если вы думали, что создание профессионального web приложения требует серьезных ресурсов, то что вы думаете про создание web приложения, api для него и прослойки коммуникации между ними? Мы действительно верим в том, что каждый наш пользователь будет иметь устройство способное переварить 100 Kb JSON и отрендерить сложную HTML таблицу быстрее чем серверное приложение на средненьком сервачке?
Разработка и раздача SPA тоже не стали дешевле. Во многих случаях мы теперь должны делать двойную работу(и возможно платить двум разработчикам) для получения точно такого же результата, что и с server-side фреймворками.
В 2005 году фреймворки взорвали умы всех с помощью видеороликов «Как создать блог-сайт за 15 минут». Пятнадцать лет спустя, делая тоже самое с помощью SPA нам нужно две кодовые базы, слой JSON сериализации и повсюду десятки прелодеров, чтобы мы могли еще заявлять о 50мс загрузке страницы с контентом. В это же время пользователь наблюдает за несколькими серыми прямоугольниками, надеясь что HTML наконец-то отрисуется из JSON, который запрашивает и обрабатывает его браузер.
Как мы сюда попали? Это не то светлое будущее, которое я хотел. Стоило ли от отказаться от сервер сайд "счастья" и удваивать штат сотрудников, а так же время для реализации, чтобы обеспечить нашим пользователям более прикольный UI?
Ну, как бы да.
Мы не создаем web приложения для себя. Мы их создаем его для пользователей. У пользователей нашего софта есть ожидания о том, как оно будет работать. Мы должны оправдать ожидания пользователей. Наши пользователи больше не восторге от полностраничных обновлений и “простыни” из множества форм вложенных друг в друга.
SPA был следующим этапом от кучи разнородных js файлов, живущих на сервере. Проблема в том, что это лучше на 5%, а не 500%.
Стоит ли 5% улучшение двойной работы? Что насчет стоимости разработки?
Впечатляющие web приложения создают более прикольный UX с точки зрения пользователя. Хорошо сделанное приложение может быть более бесшовным и интерактивным, что дает возможность для новых способов взаимодействия. Оформление элементов интерфейса как компоненты было следующим этапом эволюции. Прошли те дни, когда мы думали о том, как мутировать страницу HTML, чтобы создать иллюзию взаимодействия пользователя с виджетом на странице. Теперь мы думаем о нашем UX с точки зрения компонентов и изменяем непосредственно их. Но, увы, расходы почти сразу начинают расти.
Давайте напишем маленький блестящий компонент рейтинга (звездочки). Добавим несколько клевых анимаций, чтобы область наведения мышки и щелчок курсором давали нам интерактивную обратную связь, сопровождающуюся приятной анимацией. Но что теперь? В реальном приложении, нам надо сохранить изменение, верно? База данных должна быть изменена, отразив новое состояния, и приложение перед глазами пользователя тоже должно отразить новую реальность.
Раньше мы отдали бы пользователю несколько GIF’s, каждая из которых была бы ссылкой на один и тот же url c разными параметрами. На сервере мы бы сохранили изменение в БД, а затем отправили новую HTML страницу на перерисовку в браузер. Возможно, мы бы придумали более приятный способ с помощью AJAX, избавившись от перерисовки страницы. Предположим, что x это деньги и время потраченные на разработку (и мы не говорим о потерях, связанных со слишком долгим циклом разработки функционала). В данном случае более интерактивный AJAX подход будет стоить x+n (нам же придется писать дополнительный JS код), но n будет все больше и больше расти по мере того как мы будем добавлять то там, то здесь JS код.
В мире SPA мы пишем JavaScript код на клиентской стороне, используя JSX, Handlebars или другие шаблонизаторы для отрисовки компонентов, а затем пишем код для изменения состояния в нашем приложении (на клиенте) после чего инициализируем http запрос через API для изменения данных на сервере. Кроме того, надо написать API обработчик для этого запроса на сервере, а также обеспечить сериализацию JSON как на сервере, так и в нашем приложении. И наконец, наш код должен обеспечить перерисовку компонента в случае успешного ответа с сервера (и при этом возможна отдельная логика, связанная с отменой изменений и перерисовкой, если backend вернул нам ошибку). Эти затраты гораздо больше, чем x+n, о которых мы говорили выше. И если вы разделили свою команду на фронтэнд и бэкенд, то скорее всего вы удвоите стоимость (деньги и время) для реализации множества нетривиальных компонентов, где необходимо хотя бы по одному участнику от каждой стороны. Разумеется SPA сглаживает проблему растущего спагетти кода, но какой ценой для бизнеса, а именно time to market и себестоимость проекта начинают стремительно расти.
Другой аргумент, который мы слышим в поддержку SPA, это сокращение стоимости содержания инфраструктуры. Как будто перекладывание этого бремени на клиента (без его согласия в большинстве случаев, но это уже другая тема) каким-то образом уменьшит наши счета за облачные услуги. Это же смешно. Для любого нетривиального приложения вы все-равно платите за хостинг API и возможно еще и за базу данных, не о говоря уже о балансировщике, DNS и т.д. И вот что, ни одна из этих затрат не сравнится с тем, сколько софтверные компании платят разработчикам. Серьезно, подумайте об этом. Я еще никогда не работал в бизнесе, где стоимость технической инфраструктуры была больше зарплатного фонда. И при этом хорошие разработчики ожидают ее повышение, а облака напротив в основном дешевеют с течением времени.
Если вы хотите тратить ваши деньги эффективно, особенно если вы стартап (который ограничен в средствах), то не стоит экономить на услугах облачных провайдеров. Ведь вам нужно как можно быстрее получить больше фич от существующей небольшой команды.
Давным-давно до появления web фреймворков, вы бы заплатили разработчику за шесть недель работы, чтобы он только начал прототипировать страницу авторизации. Звучит ужасно! С фреймворками создание страницы авторизации занимало час, а за ночь некоторые запускали стартап. Звучит превосходно! Теперь же с SPA подходом мы опять имеем кучу дополнительной работы. Это стоит больше денег, потому что мы должны писать две кодовые базы одновременно. Звучит не очень круто…
Мы платим очень много денег за улучшение пользовательского опыта на 5%.
Но что если мы могли бы взять лучшие идеи SPA и подходы JavaScript библиотек, которые обеспечивают улучшение использования на 5%, и переосмыслить их с учетом удобства разработки и экономии на заработной плате, используя одну кодовую базу? Что если бы компоненты находились бы в одном монолитном приложении, оптимизированном для сервер сайд рендеринга? Возможно это способ улучшения на 500%?
Это кажется невозможным? Но это не так. Я создал такое приложение в свободное время, когда мои дети бегали вокруг меня, лая как собаки. Приложение использует обновление по WebSocket для зарегистрированных пользователей и мгновенно обновляет DOM на клиентской стороне за миллисекунды. При этом используется 3D анимация на JavaScript для отрисовки окон реал-тайм чата. И все это в одной кодовой базе, на том же самом серверном оборудовании, которое я бы использовал для классического сервер-сайд рендеринга (и возможно я даже могу замасштабироваться на этом оборудовании, так как я чаще отрисовываю кусочки HTML, а не всю страницу целиком). И мы избавились от отдельного фронтэнд приложения. Чистый, компонентный JavaScript и серверный код сочетаются так же хорошо как хлеб с маслом. И это возможно!
Сокета мне! (Поняли? Поняли? Ладно, не важно…)
Наконец в 2011 году, WebSocket получил полную поддержку во всех современных браузерах. С помощью небольшого количества JavaScript кода, мы получаем мультидуплексное соединение между браузером и сервером. Данные могут передаваться в обе стороны и могут быть отправлены с любой стороны в любой момент, не требуя действий со стороны пользователя.
Подобно игровой индустрии, которая развивается в сторону облачного гейминга, будущее web приложений не в том, чтобы взваливать еще больше работы на клиентское устройство, напротив: пусть браузер будет тонким клиентом, который просто отрисовывает то или иное состояние для пользователя. WebSocket обеспечивает транспортный уровень, бесшовный и быстрый, напрямую к пользователю.
Поначалу многим разработчикам было довольно непросто осознать новый подход. Мне тоже. И выгода была не очевидна. Использование другого технологического транспорта, как WebSocket, вызывает недоумение после нескольких лет (даже десятилетий) выстраивания логики работы вокруг отправки и получения HTTP запросов. Как и в случае с большинством новых технологий или протоколов нам была нужна абстракция более высокого уровня, которая обеспечивала бы быстрое создание функционала для пользователей.
Передача HTML по WebSocket
Хотите супер отзывчивый dropdown с autocomplete(<datalist>), который идеально синхронизирован с БД? На каждое нажатие клавиши отправляется запрос через WebSocket, который возвращает необходимый набор данных ни больше, не меньше.
Как насчет валидации? Все просто. На каждое изменение данных в input, извлеките значение поля и отправьте по WebSoсket. Пусть ваш серверный фреймворк провалидирует и отправит ответ прямо в виде HTML, включая любые ошибки, которые необходимо отрисовать. Нет необходимости в JSON, в том числе в сложных объектах ошибки.
Индикаторы присутствия пользователя на сайте? Элементарно. Просто проверьте на сервере у кого активное соединение через socket.
Что насчет многопользовательского чата? Или совместного редактирования документа? В классических фреймворках и SPA мы откладываем эти фичи до последнего из-за их сложности и путаного кода, требующегося для корректной синхронизации состояния у всех клиентов. С HTML по WebSocket, мы просто отправляем небольшие кусочки HTML в зависимости от действий одного пользователя всем другим пользователям, которые сейчас активны. Они увидят те же данные, как если бы они обновляли страницу и получали всю HTML страницу заново с сервера. И вы можете получать эти кусочки данных для каждого пользователя в пределах 30 миллисекунд.
Но при всем при этом мы не отказываемся от компонентов. Подход, основанный на WebSocket можно рассматривать, как «толстый» сервер/«тонкий» клиент, тоже самое можно сказать и про наши компоненты. Это гениально! Заставьте компонент делать восхитительные вещи для пользователя с помощью «умного» JavaScript, а затем просто попросите сервер обновить HTML. Нет необходимости в хранении данных в storage (Redux, Vuex и т.д.) на клиенте, чтобы управлять состоянием компонента, ведь он выглядит в точности так, как ему об этом сообщил сервер. HTML приходит с сервера, следовательно нет необходимости в JSX, Handlebars или любой другой библиотеке для шаблонизации на клиенте. Сервер все держит под контролем, как первоначальную отрисовку компонента, так и его последующее обновление в случае изменений. Все это делается через соединение по сокету.
И никто не говорит, что вы должны использовать сокет только для отправки HTML. Можете отправлять кусочки текста, и пусть клиент сам решит что с этим делать. Например, когда отправляется сообщение от одного пользователя всем остальным участникам чата, и клиентский код каждого может сам решить, как отрисовать сообщение в зависимости от пользовательских настроек/темы. Представьте, какие возможности открываются!
Но это сложно/дорого/требует кучу новой инфраструктуры, не так ли?
Это совсем не так. Наиболее популярные open-source веб-сервера поддерживают WebSocket из коробки без необходимости дополнительной конфигурации и настроек. Многие серверные фреймворки автоматически отправляют JavaScript код на клиент для удобной работы с вебсокетами. Например, в Rails настроить работу с WebSocket так же просто, как настроить встроенный ActionCable, а затем задеплоить на ту же инфраструктуру, которую вы бы использовали для обычного приложения. Надо отметить, что обычное Rails приложение совершенно спокойно справляется почти с 4 000 активных соединений. И вы можете абсолютно спокойно увеличить это число до 10 000+ соединений, используя AnyCable вместо встроенного Ruby WebSocket сервера. Опять же используя для этого стандартное оборудование, которое вы бы использовали для запуска обычного web сервера. Вам не нужно настраивать дополнительное оборудование или увеличивать свою облачную инфраструктуру.
Этот новый подход появляется в виде расширений, библиотек или альтернативных конфигураций в различных языках и web фреймворках: Sockpuppet(Django), Phoenix LiveView(Elixir), Korolev(Scala), ts-liveview(NodeJS), Hotwire(Ruby), Vaadin(Java). Поищите библиотеки на основе WebSocket для вашего любимого фреймворка и попробуйте использовать новый способ проектирования архитектуры вашего приложения. Постройте что-нибудь удивительное и восхищайтесь великолепными кусочками HTML, проносящимися по сокетам подобно реактивному истребителю по ночному небу. Это больше, чем новый технический подход, это новый способ мышления и, возможно, даже источник новых фич, которые принесут вашему стартапу успех.
Но я бы не был собой, если бы не упомянул лучшего претендента на эту роль для читателя. Разумеется, любой фреймворк может реализовать этот подход, но мне кажется, лучше всего на эту роль подходит Ruby on Rails.
Итак, мы возвращаемся к Rails, спустя 15 лет после его появления…
Настройте Rails 6 с последней версией Turbolinks, Stimulus, StimulusReflex, CableReady and gems Github ViewCompinent, и вы сможете работать с реактивным Rails будто создаете классическое Rails приложение и одновременно современное компонентное SPA приложение, в одной кодовой базе, со всеми преимуществами сервер-сайд рендеринга, кэшированием HTML элементов, простым SEO, надежной безопасностью и всем остальным. Вы внезапно обнаружите, что теперь ваш набор инструментов состоит из множества простых вещей, позволяющих решать задачи, которые раньше были пугающе сложными.
И с помощью Turbolinks вы также получаете обертки, позволяющие использовать нативный гибридный HTML UI в единой кодовой базе. Используйте быстрое решение для деплоя, как Heroky или Hatchbox, и один разработчик сможет создавать отзывчивое и мультиплатформенное приложение в свое свободное время. Взгляните на этот клон Twitter, если вы мне не верите.
Ладно, звучит интересно, но почему именно Rails? Разве он не устарел? Ты же сказал, что любой фреймворк даст аналогичные преимущества, если использовать его в связке с WebSocket и DOM патчинг, не так ли?
Конечно. Но Rails всегда славился тем, что можно делать быстрые прототипы, и системой хорошо написанных gem пакетов. Он не остановился в развитии, Rails 6.1.3 имеет множество интересных фич.
Если у вас маленькая и ограниченная ресурсами команда, Ruby on Rails служит мощным инструментом, позволяющий вам прыгнуть выше своей головы. С новым подходом ваша команда может создать на порядок больше функционала. Пока ваши конкуренты возятся с сериализацией JSON в своем API и борются за уменьшение количества индикаторов загрузки в своем UI, вы запускаете новую многопользовательскую функцию для совместной работы каждую неделю или даже день.
И все в выигрыше: разработчики, бизнес и, что самое главное, ваши пользователи.
Это то, что Rails обещал с момента своего релиза. Именно поэтому Rails породил такое количество подражателей в других языках программирования и имел такую бешенную популярность среди стартапов. И этот старый дух быстрого прототипирования, совмещенный с HTML по сокетам создает предпосылки для мощного возрождения Rails.
Евангелист Ruby и автор “The Ruby Way” , Obie Fernandez кажется так же думает.
Черт, даже Russ Hanneman считает, что новый подход с StimulusReflex перспективен.
И классные парни из Basecamp (первоначальные создатели Rails) отказались от своего концепта в пользу Hotwire, чтобы добавило еще один способ для реализации ранее описанного подхода.
Не надо считать это возвращением Rails потому что он был с нами все эти годы. С этим новым архитектурным подходом с HTML по сокетам и полнодуплексным взаимодействием с помощью JavaScript, Rails становится чем-то новым, чем-то прекрасным, чем-то, что требует внимания (опять).
Реактивный Rails c StimulusReflex и "друзьями" это обязательная вещь для ознакомления для тех, кто устал от работы с JSON API или JSX, и я буду очень рад видеть новое поколение приложений, которые он позволяет создать.
P.S. Использовали ли вы такой подход на перечисленных выше языках/фреймворках и что о нем думаете, напишите свое мнение в комментариях.