Мы не замечаем, но услуги и продукты, которыми мы пользуемся, постоянно усложняются.
- Войти в метро теперь – не просто кинуть пятачок, а приложить карту Тройка, записанную на телефон и учитывающую пересадку.
- Позвонить по телефону и посмотреть телевизор – давно уже не провести два провода в квартиру и вносить фиксированную абонентскую плату, а triple play с кучей опций и возможностей.
- Посмотреть дневник сына – на святое же покусились! – теперь можно с планшета, заодно ответив на комментарий классного руководителя о его неудовлетворительном поведении.
Ну и я уже молчу про всякие Tinkoff, Apple Pay, Google Now, умные дома и многое другое.
Как следствие, в любой компании растут IT-отделы. То, чем раньше занимались несколько десятков сотрудников, сейчас делают команды из тысяч и десятков тысяч человек (кстати, поделитесь в комментариях, как выросли ваши IT-отделы).
Такие большие команды вынуждены более ответственно подходить к выбору технологий, в том числе и UI-фреймворков. И вот вам вброс: неважно, какой UI-фреймворк выбран. И даже вредно ограничивать себя выбором одного фреймворка. Но абсолютно не вредно и даже необходимо следовать правилам использования этих фреймворков.
Технологическое развитие – увеличение скорости, объема и удобства обмена информацией – позволило большим IT-коллективам не собираться в одном здании, а работать распределенной командой. И вот уже у нас огромные open-source проекты, где много команд из различных компаний, стран и континентов работают над одним продуктом.
Совместная разработка большой распределенной командой принесла следующие особенности, которые, в свою очередь, выразились в развитии соответствующих инструментов, фреймворков и передовых практик:
- Декаплинг. Строгое разделение на back-end и front-end. REST-сервис под каждый отдельный UI.
- Микросервисы. Каждая команда независимо разрабатывает свою часть, но все эти части должны сложиться в одно работающее приложение.
Эти особенности фактически развязали руки front-end разработчикам и позволили разным командам выбирать front-end технологии в зависимости от бизнес-задач, экспертизы команды, предпочтений и «религии».
Во многом поэтому мы сейчас наблюдаем активный рост и отсутствие явного и безоговорочного лидера в UI-фреймворках: AngularJS, ReactJS, EmberJS, ExtJS и др. При этом, вполне вероятно, надо быть готовым к тому, что завтра появится новая front-end технология, которая будет более эффективной и будет более активно поддерживаться/развиваться сообществом. Именно поэтому считаю неправильным сейчас делать долгосрочный выбор и останавливаться на одной front-end технологии. Более того, вести разработку надо так, чтобы front-end технология была относительно дешево заменяемой, всегда держать это в голове при разработке архитектуры конкретного приложения. Это задача номер один.
Кроме того, при дальнейшем увеличении команды мы будем иметь уже несколько front-end команд, которые разрабатывают одно UI-приложение. Каждая команда независимо разрабатывает свою часть, но все эти части должны сложиться в одно работающее приложение так, чтобы UI одной команды не конфликтовал с UI другой. Это задача номер два.
Для решения этих задач мы, front-end практика в компании Netcracker, разрабатываем архитектурные требования, которые обязательны для всех продуктов.
Немного теории модульного подхода
В основе архитектурных требований лежит подход, который подразумевает независимые JS-блоки и их встраивание друг в друга. У этого подхода есть несколько названий: Scalable JS Apps, Modular JavaScript, JS Portals. Такие фреймворки сравнительно молоды и, можно сказать, сделаны по принципам Николаса Закаса: если можете писать, просто пишите монолит, если нужна интеграция с различными блоками, то придется использовать модульный подход.
Терминология
- Application (конечное приложение) – набор html/js/css, который отображается пользователю. Конечное приложение может в общем случае не использовать принципы Modular JavaScript и быть написано на любом языке, хоть на flash.
- Application Controller – JS-объект, реализующий принципы Modular JavaScript, позволяющий модулям общаться и встраиваться рядом и друг с другом.
- Module – JS-приложение на любом языке, подобном JS.
- Sandbox – объект, через который Module может общаться с внешним миром.
- Service – утилитарные объекты, имеющие логику, но не имеющие UI.
- BroadcastService – сервис, позволяющий модулям общаться.
- Plugin – встраиваемый в Application Controller модуль. Application Controller может использовать его, чтобы получить новый функционал.
Modular JavaScript. Принципы
Принципы основаны на том, что мы считаем каждый модуль ребенком. Поэтому к ним применимы следующие правила:
- Модуль может вызывать только свои методы или методы Sandbox.
- Нельзя смотреть на DOM вне своего Sandbox.
- Нельзя трогать ненативные глобальные переменные.
- Если модулю что-нибудь нужно, это нужно спросить у Sandbox.
- Не разбрасывать игрушки (модуль не должен создавать глобальные переменные).
- Не разговаривать с незнакомцами (модуль не должен напрямую вызывать другой модуль).
Оригинал можно прочитать здесь.
Эти принципы реализованы Николасом Закасом в ряде проектов, например здесь: http://t3js.org. Однако есть альтернативные реализации: http://openf2.org/ (хотя принципы там те же, о них рассказывается в видео).
Взяв за основу эти принципы, мы начали уточнять и развивать их в наших проектах.
Наша специфика и архитектура
Уточнения и дополнительные принципы, которые диктуются спецификой наших проектов:
- Сервис не имеет UI;
- Сервис обычно singleton;
- Модули могут общаться через сервис общения (это может быть EventBus или publish/subscriber);
- Есть только один сервис общения, общий для всех модулей;
- Поскольку мы имеем дело с JS, нужен статический анализатор кода (например, ESLint), который запретит внесение изменений в код в случае нарушения принципов;
- Реализация Modular JavaScript должна быть JS Agnostic, т. е. модуль может быть написан на любом языке, подобном JS;
- Необходима поддержка модуля в модуле, так как часто хочется переиспользовать код (например, модуль table может быть отрисован внутри модуля dashboard, который, в свою очередь, рисуется на модуле tab navigation – наподобие панели c вкладками для переключения);
- Из-за того, что модуль не может выйти за рамки своего элемента, необходим DialogManagerService, который управляет body для показа диалогового окна; модуль, который хочет показать диалоговое окно, использует модуль dialog и передает его в сервис;
- Поскольку модулей в проекте может быть много, компилирование всех в один пакет или предварительное соединение ссылками может привести к проблемам с производительностью, поэтому модули должны уметь подгружаться асинхронно по требованию и, естественно, запускаться только после того, как подгружены все другие модули и сервисы, от которых они зависят; отсюда следует, что нам понадобится устранять конфликты между зависимыми модулями.
И вот какая получается архитектура
Блок T3-NC-powered представляет собой Application Controller. У Application Controller есть набор базовых плагинов (треугольники), которые дополняют его функциональность. Плагины не имеют доступ к Application Controller. Наиболее важным является плагин, позволяющий общаться с сервером для «ленивой» загрузки модулей.
Сервисы могут обмениваться данными с Application Controller. Наиболее важным является сервис для обмена сообщениями между модулями. Также сервисы могут использовать плагины. Сервисы не имеют UI, так как предоставляют JS-функции.
При старте модуля по его имени создается Sandbox, который ограничивает модуль. Основной API – дает возможность получить сервис и DOM element, в который модулю нужно встроиться. Если необходимо запустить два модуля с одинаковым именем, то Application Controller создаст два экземпляра модулей, внутренние id которых будут разными.
Заключение
Совместная разработка большой распределенной командой принесла декаплинг и микросервисы, что развязало руки front-end разработчикам и позволило разным командам выбирать различные front-end технологии. Это могло бы породить хаос и превратиться в бесконечный спор.
Но мы хитрые ) Модульная разработка и архитектурные требования позволяют собирать в одно UI-приложение части, разрабатываемые различными front-end командами, и гарантируют относительно дешевую замену front-end фреймворка.
Если вы интересуетесь модульным подходом или уже вовсю используете его, пишите в комментариях – давайте делиться опытом. Мы в свою очередь готовы дать больше технических деталей, если у сообщества будет интерес.