Изоморфные JavaScript-приложения с Catberry.js

в 5:54, , рубрики: catberry.js, framework, isomorphic, javascript, node.js, streaming, web-разработка, Блог компании 2ГИС, Веб-разработка, Программирование

Изоморфные JavaScript приложения с Catberry.js

Catberry.js — это фреймворк для разработки изоморфных JavaScript-приложений на node.js с использованием модульной архитектуры и быстрых механизмов рендеринга. Этот фреймворк позволяет написать модуль приложения один раз и использовать его как на сервере для рендеринга страниц для поисковых роботов, так и в браузере для одностраничного приложения, запрашивая только данные для шаблонов.

Впервые термин изоморфные приложения лично я увидел в блоге компании airbnb, перевод этой статьи можно прочитать на Хабре, хотя этот термин начал звучать немного раньше, например в блоге nodejitsu. Поэтому немного сложно сказать, кто это придумал, но факт в том, что в наше время существует целый класс веб-приложений, которые принято называть изоморфными. На Хабре этот термин в основном упоминался в статьях о фреймворках от gritzko и zag2art.

Что такое изоморфные приложения?

Увидев это странное название, многие разработчики пугаются и стараются избегать знакомство с ним. И очень зря, я считаю, что именно изоморфные приложения — это будущее (а может быть уже и настоящее) веб-приложений.

Как бы страшно оно не звучало, на самом деле всё достаточно просто — это приложения, которые могут переиспользовать серверный код в браузере и вести себя одинаково как на сервере, так и в браузере. Другими словами, вы пишете один раз код вашего приложения и получаете серверный бэк-енд, который рендерит страницы для поисковых роботов и отзывчивое одностраничное приложение в браузере. Оно, в свою очередь, может и вовсе не грузить больше ни байта HTML с сервера, а запрашивать только данные для рендеринга в браузере. Практически мечта, не правда ли?

Возникновение класса изоморфных приложений обосновано желаниями разработчиков, например:

  • иметь современное одностраничное приложение, которое работает без перезагрузки страницы;
  • не жертвовать при этом SEO, чтобы для поисковых роботов это был обычный сайт, который грузится с сервера;
  • не повторять логику рендеринга страниц дважды для сервера и браузера, для этого должен использоваться один код;
  • в случае с JavaScript-приложениями это должна быть единая языковая среда для разработчика — не нужно тратить время на переключения контекста;
  • рендеринг HTML на сервере должен происходить только при первой загрузке, а дальше рендерингом занимается браузер, тем самым мы разгрузим сервер;
  • всё это должно быть просто для разработчика.

Лично я все это хочу столько, сколько занимаюсь веб-разработкой, и, очевидно, не я один, так как в мире радуги, единорогов и изоморфизма уже есть множество фреймворков для разработки изоморфных приложений:

  • Rendr (Airbnb);
  • Kraken (PayPal);
  • Mojito (Yahoo);
  • Meteor;
  • Derby;
  • наверное, ещё с пару десятков менее известных решений.

Ещё есть такой подход как MEAN (MongoDB+Express+Angular+node.js), который делает Angular-приложения изоморфными.

Почему бы просто не взять один из них?

Когда я хотел начать очередной веб-проект, я стал изучать все существующие на тот момент решения и увидел ряд недостатков:

  • некоторые решения использовали фронт-енд фреймворки для рендеринга на бэк-енде. Это означало виртуализацию и построение DOM прямо на сервере, а затем его рендеринг в HTML. Этот подход показался крайне неэффективным, как по памяти, так и по сложности, которая определённо приведёт к низкой производительности. Например, Rendr использует Backbone, Mojito — YUI, MEAN — Angular.
  • ещё один недостаток — зависимость от определённой БД. Ничего не имею против MongoDB и даже сам её использовал несколько раз, но иногда нам нужна надежность и транзакционность, а иногда отсутствие схемы и скорость. Считаю, что разработчика не надо ограничивать в выборе. Речь идёт о Rendr, Meteor, Derby, которые привязаны намертво к MongoDB, а Derby ещё требует Redis.
  • Real-time data binding — это, безусловно, очень полезная фича для приложений, таких как SCADA (АСУ ТП) или приложений для совместной работы над чем угодно. Однако если нужно реализовать обычный сайт, как это делают разработчики на RoR или на PHP-фреймворке — это лишние сложности.

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

Сatberry.js

Называется этот фреймворк Catberry.js, и сейчас он уже в версии 3.0. Не то чтобы он стар, но для версионирования используется Semantic Versioning 2.0.0, а фреймворк пару раз претерпел изменения без обратной совместимости. Catberry достаточно лёгкий фреймворк, со своей идеологией, которая гласит «Хранение и обработка данных не должна быть частью приложения Catberry, это должен делать отдельный RESTful сервис».

Знакомство с фреймворком стоит начать с подхода SMP (Service-Module-Placeholder), который заменяет привычное многим MVC (Model-View-Controller), но, обещаю, будет выглядеть знакомо. Опять же, стоит оговориться, я не против MVC, он очень даже хорош, но для определенного класса приложений, где как раз необходимо обновление данных в реальном времени. Тот MVC, который используется сейчас в разработке веб-приложений, часто оказывается не тем, что изначально было придумано в качестве MVC, а как некая интерпретация. Так вот свою интерпретацию я решил назвать иначе, чтобы не путать коллег.

Service-Module-Placeholder

Как видно из названия, приложение строится на трёх компонентах.

Service

Service — внешний компонент, представляющий из себя RESTful сервис, к которому постоянно обращается наше Catberry-приложение. Этот сервис может быть реализован на абсолютно любой платформе: Erlang, Go, PHP, .NET, что угодно. Вы ограничены только протоколом HTTP 1.1.

Module

Модуль в понятии Catberry-приложения это набор логики, которая подготавливает данные для шаблонизатора и обрабатывает события от пользователя. Другими словами, если нужно отрендерить часть страницы, Catberry находит ответственный за эту часть страницы модуль и просит его подготовить контекст данных для шаблона, учитывая текущее состояние всего приложения (например, параметры в текущем URL).

Placeholder

С первого взгляда можно сказать, что это просто шаблон. На самом деле он может иметь HTML-элементы, которые ссылаются на другие плейсхолдеры, причём такие ссылки могут появляться динамически во время рендеринга, и это вставит другой плейсхолдер внутрь текущего. Например, в содержимое элемента плейсхолдер «paw» модуля «cat». Почему используется id, спросите вы? А для того, чтобы плейсхолдеры можно было очень быстро найти в DOM, когда рендеринг работает в браузере. Это действительно очень экономит время.

Принадлежность Placeholder к Module

Как было упомянуто, плейсхолдер связан с модулем, а точнее модуль владеет неким набором плейсхолдеров, и никому больше эти плейсхолдеры принадлежать не могут. Отсюда может возникнуть вопрос: «А как же разбить приложение на плейсхолдеры и модули?». Есть два достаточно простых правила:

  1. Если какой-то участок веб-страницы может быть отображен за один запрос к RESTful сервису, то скорее всего это плейсхолдер.
  2. Если несколько плейсхолдеров зависят от одних параметров в URL и при изменении этих параметров необходимо их одновременно обновить, то эти плейсхолдеры стоит сгруппировать в один модуль.

Отличия от MVC

Наверное, кто-то из читателей обязательно подумает: «А в чём же отличие от MVC?». Если постараться, можно сказать, что Service==Model, Controller==Module и View=Placeholder. Но это как раз то, о чём я говорил ранее, термин MVC в наше время очень искажён, и когда люди интерпретируют его по-своему, они называют это MVC. Я же счёл нужным указать другое название, потому что:

  • Service не часть приложения, это внешнее приложение, с которым общается Module через HTTP-запросы, это нельзя назвать Model;
  • как следствие, у нас нет хранения и обработки данных в Catberry-приложении, значит нет Model вовсе;
  • по классическому описанию MVC с активной моделью, она должна оповещать о своих изменениях все заинтересованные View, как это делается, например, в Meteor. В Catberry ничего подобного нет.
  • есть ещё MVC с пассивной моделью, но в таком случае Controller должен отслеживать изменения модели и обновлять View, ничего подобного в Catberry тоже нет;
  • вместо этого обновления происходят только от действия пользователя, когда он меняет URL или отправляет данные формы. Разумеется, никто не мешает вам дополнительно обновлять Placeholder вызовом запроса на обновление в коде Module, например по событиям или используя long-polling. Но контент плейсхолдера будет зависеть от состояния приложения, описанного в URL. В этом смысл подхода — возможность на сервере и в браузере по URL полностью восстановить состояние приложения, чтобы отрендерить идентичные страницы.

Как это работает

Catberry-приложение работает именно так, как я ранее описывал изоморфные приложения:

  • сначала пользователь делает запрос на сервер по URL;
  • сервер рендерит страницу по заданному URL;
  • вместе со страницей в браузер загружается браузерная версия приложения;
  • и дальше всё работает без перезагрузки страницы как одностраничное приложение.

Для большего понимания картинка

Изоморфные JavaScript приложения с Catberry.js

Потоковый рендеринг на сервере

Как можно понять из описания и схемы, когда происходит рендеринг на сервере, для отрисовки каждого плейсхолдера обычно надо сделать запрос на RESTful-сервис. Это может занять значительное для пользователя время. Чтобы пользователь не разглядывал пустую страницу с крутящимся лоадером, пока мы делаем запросы для всех плейсхолдеров, был разработан потоковый (stream-based) движок отрисовки. Его задача — доставлять в браузер страницу по мере готовности. Другими словами, как только запрос к сервису для отрисовки заголовка страницы выполнен, пользователь тут же увидит этот заголовок у себя в браузере, не дожидаясь готовности всей страницы.

Я сам очень сильно не люблю, когда при открытии страницы несколько секунд вижу белый экран и даже не знаю, дошёл ли мой запрос до бэк-енда, или я сейчас увижу «504 Gateway Timeout». Обычно я закрываю такие сайты через 3—4 секунды белого экрана.

С потоковым рендерингом я сразу же увижу отклик, и то, что сайт для меня работает, старается и пыхтит, собирая данные для отрисовки. Ещё один приятный момент, что стриминг не буферизирует данные в большом объеме, что будет хорошо экономить память нашего сервера с приложением. Ну и самый приятный момент, то, что браузер, получив HEAD-элемент (который приходит с сервера почти сразу) начинает парсить JavaScript и CSS, а также загружать все указанные в странице ресурсы, всё это будет работать параллельно, как на диаграмме ниже.

Изоморфные JavaScript приложения с Catberry.js

Параллельный рендеринг в браузере

Потоковый рендеринг это, конечно, хорошо, но только когда мы ограничены последовательной загрузкой страницы по потоку TCP-соединения. Когда у нас уже есть готовая страница в браузере, и пользователь кликает по ссылке, нам нужно перестроить часть страницы, под новое состояние приложения, здесь мы уже ничем не ограничены. Можем выполнять запросы к RESTful сервису параллельно, а по результатам тут же обновлять плейсхолдеры. А если внутри есть ссылки на другие, то снова параллельно запрашивать для них данные. Таким образом получается невероятно быстрый рендеринг плейсхолдеров в браузере. К тому же, если один из запросов будет получать ответ очень долго, то это не повлияет на рендеринг остальных плейсхолдеров.

Инструменты для изоморфизма

Когда мы разрабатываем веб-приложение, нам часто нужно воспользоваться действиями, которые зависят от реализации в определенной среде, то есть работают по-разному в браузере и на сервере. Для это в Catberry есть изоморфные реализации таких действий. Они внешне работают идентично, имеют одинаковый программный интерфейс, но внутри реализованы, используя средства текущей среды. Вот перечень таких реализаций:

  • получение Location;
  • получение Referrer;
  • получение User Agent;
  • очистка URL fragment (hash);
  • получение или установка Cookie;
  • Redirect;
  • HTTP/HTTPS запросы;
  • кеш данных, которые использовались для последнего рендеринга каждого плейсхолдера.

Именно это API даёт изоморфность приложения на Catberry.js.

Как устроено приложение на Catberry

Service Locator и DI

Архитектура фреймворка построена на реализации паттернов Service Locator и Dependency Injection.

Например,

var cat = catberry.create(config); // создаётся экземпляр приложения
cat.locator.register('uhr', UHR); // можно регистрировать конструкторы по имени
cat.locator.registerInstance('uhr', new UHR()); // или сразу экземпляры
cat.locator.resolve('uhr'); // получить экземпляр
cat.locator.resolveAll('uhr'); // получить все экземпляры под таким именем
cat.locator.resolveInstance(SomeConstructorDependsOnUHR); // создать экземпляр с внедрением зависимостей

//Зависимости внедряются достаточно просто:
function ModuleConstructor ($uhr, someConfigSection) {
  // можно использовать зависимость $uhr
  // и даже секцию конфига someConfigSection
}

Такие внедрения зависимостей не ломаются при минификации, так как она делается самим фреймворком с использованием UglifyJS2.

Как устроен модуль

Каждый модуль — это директория с файлом index.js, который должен экспортировать конструктор модуля (модуль — это конструктор с объявленным прототипом). Также у модуля может быть директория placeholders, в которой располагаются шаблоны-плейсхолдеры модуля.

Методы

Каждый модуль может реализовывать три группы методов: render, handle и submit. Тут используется конвенция именования, если ваш плейсхолдер называется some-awesome-placeholder, то вы должны реализовать метод renderSomeAwesomePlaceholder, если хотите подготовить данные для него. Можете и не реализовывать, ничего от этого не сломается, а шаблон отрендериться с пустым контекстом, что тоже вполне допустимо. Такая конвенция применяется и к handle/submit методам, которые обрабатывают события со страницы.

Пример реализации всех трёх методов:

ModuleConstructor.prototype.renderSome = function () {
  // получение данных
  return {some: data}; // или Promise
};
ModuleConstructor.prototype.handleSome = function (event) {
  // как-то обрабатываем событие
  // event.args
  // event.element
  // event.isEnding
  // event.isHashChanging
  // можно вернуть Promise
};
ModuleConstructor.prototype.submitSome = function (event) {
  // отправляем данные формы
  // event.values
  // event.element
};

Иногда необходимо выполнить привязку к элементам DOM после того, как плейсхолдер будет отрендерин, для этого предусмотрены after methods, например для метода renderSome выше:

ModuleConstructor.prototype.afterRenderSome = function (dataContext) {
  // можно делать что угодно с отрендеренным плейсхолдером
};

Можно добавить такие методы также для handle и submit методов.
Пример реализации модуля можно посмотреть на Гитхабе.

Promises

Как уже упоминалось в примерах, везде, где используются асинхронные вызовы, в Catberry используются Promises (недавно была отличная статья). Причём если таковые уже есть в браузере, будет использоваться нативная реализация, иначе — библиотека-полифил от одного из авторов спецификации. Тип Promise, при этом, доступен глобально, ничего подключать не нужно, как будто вы работаете с нативными примостим.

Где используется

Сейчас на основе фреймворка уже запущен сайт проекта Конфеттин, где можно ощутить производительность и отзывчивость приложения на основе Catberry. К тому же, во всю идет разработка следующей версии Flamp, которая в обозримом будущем уже увидит свет. Чего я лично жду с нетерпением.

С чего начать

Если это, довольно беглое, описание фреймворка вас заинтересовало, то можно начать с этих двух строк в терминале:

npm -g install catberry-cli
catberry init example

Таким образом, вы получите код рабочего примера, который работает с GitHub API и инструкции как его запустить. В этом примере продемонстрированы типовые риализации вещей, которые часто приходится делать в веб-приложении. Этой же CLI-утилитой можно делать ещё много чего интересного. Например, создать новый проект или добавить в проект модуль.

Если устанавливать на свой компьютер ничего не хочется или нет такой возможности, есть этот же готовый проект на Runnable, но там можно превысить лимит запросов к GitHub API.

Подробную документацию и примеры можно найти на GitHub .
Ну и, конечно, сама страница репозитория на GitHub и Twitter-аккаунт catberryjs, в котором всегда самые свежие новости о фреймворке.

Автор: pragmadash

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js