В статье "Contemporary Front-end Architectures" рассмотрены архитектуры фронт-энда с точки зрения потоков данных в исторической ретроспективе.
Материал состоит из трех частей
Часть 1. Теория и история
Логическое обоснование потоков данных в различных компонентах программной системы является центральной идеей архитектуры программного обеспечения.
Мерой качества того или иного архитектурного решения является легкость, с которой вы можете аргументировать это обоснование!
Изучение потоков данных и, в конечном счете, архитектур в современных веб-приложениях — цель данной статьи. Веб-приложения превратились из простых статичных веб-сайтов (двухуровневая архитектура) в сложные многослойные SPA и SSR системы с доступом к информации по API. Системы CMS превратились в системы без визуально-презентационного уровня.
Сообщество фронт-энда изменилось быстро в последнее время. Все началось с методов, воздействующих на DOM, представленных jQuery, которая быстро сменилась Backbone.js на основе MVC. И мгновенно мы оказались в джунглях архитектуры двунаправленных и однонаправленных потоков данных. Где-то мы потеряли след того, как мы сюда попали. Как мир, залитый в MVC, внезапно попал в однонаправленный поток данных React? Какая корреляция? По мере продвижения мы попытаемся разгадать эту головоломку.
Несмотря на то, что статья предназначена для фронт-энд разработчиков, она должна помочь любому веб-девелоперу, которого интересует общее представление о современной архитектуре веб-приложений. Архитектура программного обеспечения лежит в основе большого количества действий — процесса, сбора требований, топологии развертывания, технологического стека и т.д. Однако это выходит за рамки данной статьи.
Необходимая прелюдия — что такое компьютер?
Здесь нужна прелюдия. Компьютер — это машина, которая собирает данные/информацию от пользователя и передает ее пользователю после ее обработки, мгновенно или с задержкой. Как компьютер собирает и показывает эти данные? Для этого используется программное приложение.
Задача архитектуры программного обеспечения заключается в предоставлении разумных средств для создания программного обеспечения без потери здравого смысла.
Главное, что нужно помнить, это то, что данные, которые обрабатывает программное приложение, известны как Модель (Model) или Состояние приложения (Application state). Некоторые называют это «Моделью предметной области» (Domain model) или «Бизнес-логикой» (Business logic) приложения. Приложение может быть настольным или веб-приложением.
На протяжении всей статьи мы будем стремиться понять разумные способы представления этого состояния приложения пользователю (веб-интерфейса) без потери здравомыслия. Мы рассмотрим, как данные передаются от модели к слою представления.
MVC предков — Первоисточник
Отделение данных от представления является основной темой графических пользовательских интерфейсов (как веб-ориентированных, так и настольных). С MVC — Model View Controller, отделение представления (View) от доменной области (Model) было основной мотивацией проектирования. И, без сомнения, MVC была плодотворной работой, которая повлияла на будущие поколения.
Если существует первый принцип разработки программного обеспечения, то это наверняка принцип SoC (Separation of Concern) — разделения ответственностей. И, вероятно, паттерн MVC является его первой манифестацией.
MVC был представлен для Smalltalk-80. В MVC объект View отображает данные, хранящиеся в объекте Model. Прежде чем мы сможем полностью изучить потоки данных в MVC, мы должны понять среды прикладных программ того времени (около 1970-х годов):
- Этот MVC был предназначен только для настольных приложений. Веб еще не родился. Мы говорим о десятилетии до этого.
- Забудьте о Web. Сложных операционных систем с графическим интерфейсом не существует.
- Это означает, что прикладное программное обеспечение было очень близко "железу" систем.
Эти ограничения имели важные последствия для MVC. Обязанностью объекта Controller стало реагирование на пользовательские вводы, такие как клавиатура или мышь, и их преобразование в действия над моделью. Кроме того, отсутствие графических элементов в операционных системах означает, что Представление (View) не соответствует тому, что на экране.
Скорее, Представление и Контроллер существовали как пара. Представление показывало пользовательский вывод, а Контроллер получал входные данные от пользователя. Следует отметить, что пара Представление-Контроллер существовала для каждого элемента управления на экране, что дает нам раннюю концепцию виджета.
Сегодня в React, Vue или Angular эта View-Controller пара концептуально совпадает с компонентом, хотя точная механика отличается в зависимости от состояния обработки.
После всего сказанного о MVC, следующее изображение должно иллюстрировать потоки данных в MVC. В этом примере у нас есть простой счетчик с кнопкой увеличения и уменьшения. Состояние счетчика поддерживается Моделью. Также мы заменили две кнопки на одну, чтобы было проще.
С точки зрения связей:
View и Controller содержат прямую ссылку на Model, но не наоборот. Это означает, что Model не зависит от пользовательского интерфейса и может меняться, не беспокоясь о проблемах пользовательского интерфейса.
Модель реализует шаблон Observer, и на него подписывается один или несколько объектов View. Когда Model изменяется, она вызывает событие, и View обновляется после реакции на событие.
В MVC есть два разных потока данных. Во View-потоке Model не участвует. Это только изменение пользовательского интерфейса. Показ эффекта нажатия кнопки или реакция на событие прокрутки мыши — пример View-потока.
Сегодня мы больше не используем этот MVC и поэтому иногда его называют классическим MVC или MVC предков (father's MVC).
Двигаемся к Модели приложения (Application model)
Вскоре стало понятно, что Application State не может быть полностью изолирован от GUI. Всегда существует какая-то логика представления (Presentation logic) или состояние представления (View state), которые необходимо поддерживать.
Сложные графические интерфейсы нуждаются в дополнительном состоянии, которое существует только для помощи виджетам пользовательского интерфейса и обеспечения лучшего взаимодействия с пользователем.
Но это может вызвать проблемы. Давайте возьмем наш предыдущий пример счетчика. Когда наш счетчик достигнет 10, мы должны изменить цвет метки с черного на красный, чтобы указать предупреждение. Такое поведение изменения цвета на самом деле не является бизнес-логикой или задачей. Это чисто эстетическая часть (UX), которая должна быть учтена. Настоящий вопрос — где? Это должна быть Модель или Представление?
Поскольку эта Логика представления или Состояние представления в основном представляет собой состояние, полученное из Модели предметной области, оно должно поддерживаться в Модели. Но Модель предметной области, поддерживающая визуальный аспект, — то есть красный цвет — по определению не очень хорошо. Если же мы поместим это в объект Представление, то это создаст еще один набор проблем. Наш виджет больше не является шаблонным. Мы не можем использовать его в другом месте. Кроме того, добавление условия с жестко закодированным числом 10 в наш объект View означает, что мы ограничиваем некоторую часть бизнес-логики.
Чтобы решить эту проблему, в исходную MVC была добавлена еще одна сущность — Модель приложения (Application Model, AM). Как видно на рисунке, с Моделью приложения пара View-Controller не имеет прямого доступа к модели. Вместо этого они регистрируются в событиях Модели приложения и используют его для доступа к необходимым данным.
Потоки данных остаются такими же, как и у классического MVC. Конечно, у каждой модели есть свои плюсы и минусы, и AM-MVC не исключение. Наиболее заметная проблема заключается в том, что Application Model не имеет прямой ссылки на объект View и, следовательно, не может манипулировать им напрямую, даже если Application Model предназначена для поддержания состояния View.
В общем, введение Модели приложения отодвигает конкретное состояние Представления от доменной области и помогает упростить объекты Представления за счет уменьшения их сложности. Это очень похоже на Модели представления (Presentation Model), концепцию, придуманную Мартином Фаулером в его оригинальном исследовании.
Суть Модели представления заключается в полностью автономном классе, который представляет все данные и поведение окна пользовательского интерфейса, но без каких-либо элементов управления, используемых для отображения этого пользовательского интерфейса на экране. Затем представление просто проецирует состояние модели представления на стекло.
Эпоха современной архитектуры настольных систем
Продолжаем двигаться дальше по времени и все меняется. Новый тип операционных систем набирает полную силу. Приложения отошли от "железа" компьютеров. Полноценное ядро, драйверы ОС и утилиты появились между ними. Операционные системы на основе графического интерфейса, такие как Windows, предоставляли готовые виджеты сразу после инсталляции.
Контроллерам больше не нужно было слушать устройства ввода. Идея Представления (View) объекта изменилась.
Большая часть функциональности Контроллера была взята на себя операционной системой. Идея View трансформировалась. Раньше это был единственный виджет. Теперь это была композиция виджетов. Представление могло содержать другое представление. Представление стало двунаправленным в том смысле, что оно реагировало на действия пользователя, а также отображало данные модели.
Идея View в мире фронт-энда очень похожа на эту идею View. В контексте Интернета Представление — это целая страница.
Классический MVC становился устаревшим и неудобным в использовании. Чтобы приспособиться к этим изменяющимся средам, команда Dolphin искала новую модель создания пользовательских интерфейсов. Это был 1995 год. История того, как команда Dolphin достигла нового дизайна, очень хорошо задокументирована здесь и здесь. Мы не будем останавливаться на этом.
В итоге, команда проделала поворот модели MVC на 60 градусов, которую они назвали «Скручивание триады» (Twisting the triad). Так мы получили MVP.
С точки зрения связей:
Презентер (Presenter) следит за логикой Представления. Презентер может изменить Представление напрямую. Представление делегирует пользовательские события Презентеру.
В зависимости от реализации, Представление подписывается на Модель и полагается на Презентер для сложной логики, или Представление просто полагается на Презентер для всего.
Как отмечалось в его работах по архитектуре графического интерфейса, Мартин Фаулер разделил реализацию MVP на Supervising Controller MVP и Passive View MVP. Различия и потоки данных поясняются на диаграмме.
MVVM — Model View ViewModel
MVP великолепен, и для него есть много возможных вариантов и сложных деталей, но с точки зрения фронт-энда MVVM действительно выделяется. В некоторых реализациях он также известен как Model-View-Binder. MVVM очень похож на Passive View MVP, но с добавленной особенностью привязки данных (data binding). Это техника программирования, которая связывает данные поставщика и потребителя вместе и синхронизирует их. Она избавляет от большого количества стандартного кода, который нам традиционно необходим для синхронизации View и Model. Это позволяет работать на гораздо более высоком уровне абстракции.
С точки зрения связей:
ViewModel — это объект, который предоставляет связываемые (bind-able) свойства и методы, которые используются View.
MVVM имеет дополнительный элемент Binder, который отвечает за синхронизацию View с ViewModel. Каждый раз, когда свойство ViewModel изменяется, Представление автоматически обновляется, чтобы отразить изменения в пользовательском интерфейсе.
Data binding, присущая MVVM, стала основой многих интерфейсных библиотек, включая Knockout, Angular, Vue.js, React и другие.
Мы еще раз вернемся к привязке данных в разделе веб-приложения.
В царство веб-приложений
По аналогии с оригинальный MVC, появился шаблон для использования с веб-приложениями. Он известен как Web MVC. На самом деле, из-за того, как веб-приложения создаются и разворачиваются, MVC является для них более естественным развитием, чем для настольных приложений.
Основная путаница в сообществе заключается в том, что настольные MVC и web-MVC — это две разные модели. Если бы web-MVC был бы назван как-то иначе, все было бы намного проще.
Веб-приложение является категорией распределенных приложений. Хотя MVC довольно естественен для веб-приложений, в целом создавать распределенные приложения довольно сложно. Некоторая часть кода выполняется на сервере, в то время как другая на клиенте — то есть в браузере.
При построении архитектуры крупных масштабируемых веб-приложений основная задача состоит в определении, где должна выполняться каждая часть кода. На двух концах у нас расположены серверные приложения и насыщенные клиентские (rich client-driven) приложения. Между ними мы можем варьировать бесконечным числом способов.
Говоря о веб-приложении в контексте MVC, существует три отдельных цикла данных и, следовательно, три реализации MVC: MVC на стороне сервера, внутренний MVC браузера и фронт-энд MVC. Браузер является посредником между тремя типами взаимодействия:
- Между кодом на стороне клиента (JS + HTML) и кодом на стороне сервера.
- Между пользовательским и серверным кодом.
- Между пользовательским и клиентским кодом.
Браузер имеет собственную Модель, Вид и Контроллер. Как разработчикам, нам не нужно беспокоиться об MVC браузере.
Серверная MVC a.k.a. Модель 2
Первой известной реализацией серверной MVC является Модель 2 от Sun Microsystems для веб-приложений на Java.
Этот MVC очень похож на классический MVC, но появляются дополнительные сложности, связанные с тем, что время цикла потока данных увеличивается экспоненциально, когда данные пересекают границы клиента и сервера. Некоторые вещи, на которые стоит обратить внимание:
- Десктопный MVC имеет два цикла данных (Data cycles), а веб-MVC — три цикла данных.
- Есть два цикла Представления (View cycles). Первый — это цикл Представления клиента, такой как событие прокрутки, ввод с клавиатуры и т.д. Второй — цикл Представления сервера, такой как обновление страницы, активация гиперссылки и т.д.
- Наконец, у нас есть цикл модели (Model cycle), который имеет дополнительную сложность по времени, поскольку он пересекает границу клиент-сервер.
- Front Controller: компонент, обычно предоставляемый базовым технологическим стеком для обработки отправки HTTP-запросов. Например, контейнер сервлетов в веб-приложениях Java, IHttpHandler в классе ASP.NET или HTTP.Server в Node.js.
Считается, что сегодня SSR (Server Side Rendering) — рендеринг на стороне сервера означает совершенно другую концепцию. Однако это не совсем так. Поскольку весь HTML/контент создается сервером, а клиентский код JavaScript не используется, веб-приложения, полностью созданные с использованием серверной MVC, все еще рассматриваются как SSR.
Выходим за пределы серверной части
Вот где действительно становится интересно. Почти все браузеры начали реализовывать в себе движки JavaScript. На мой взгляд, именно AJAX изменил ход веб-приложений. Google первым освоил его с помощью своего почтового клиента и картографических приложений.
Мир серверной MVC, генерирующей HTML + JavaScript. Код JS разбросан по страницам. JavaScript в основном используется для улучшения UX за счет сокращения циклов просмотра сервера (Server View Cycles). Такие вещи, как отправка формы, проверка ввода и т.д. обрабатываются клиентским кодом.
Это самая распространенная архитектура в истории веб-приложений. Большинство приложений B2C, SEO-дружественных веб-сайтов, особенно созданных с помощью CMS — Content Management Systems, используют его. Количество клиентского кода зависит от потребностей приложения.
Эта архитектура как таковая никогда не была действительно стандартизирована и поэтому не имеет названия. Она развивалась инкрементном стиле и до сих пор считается расширением Web MVC. ASP.NET MVC, Java Struts, Python Django, Ruby ROR, PHP CodeIgniter — некоторые из широко используемых сред, широко использующих серверную MVC или Web MVC.
Конечно, существует много других вариаций этого стандартного шаблона, но они не оказывают реального влияния на современные архитектуры фронт-энда и могут быть опущены.
RIA — архитектура насыщенных интернет-приложений (Rich Internet Application Architecture)
Учитывая всё вышесказанное, мы теперь готовы обсудить современные архитектуры фронт-эндов. Современные фронт-энд архитектуры крутятся вокруг создания RIA — Rich Internet Application. Точное определение RIA невозможно, поскольку оно означает разные вещи. Но в целом RIA или Rich Web Applications — это категория приложений, в которых приложение сильно зависит от кода на стороне клиента, а их UX очень близок к приложению для настольных компьютеров. Они в основном создаются с использованием фреймворков, поддерживающих SPA (Single Page Application) — одностраничное приложение. Поток данных цикла просмотра сервера из Web MVC не существует. Обычно существует только одна исходная HTML-страница, а затем для визуализации последующих страниц используются код на стороне клиента и системы маршрутизации.
Построение RIA — сложная операция, которая возникла в результате изучения предыдущих архитектур графического интерфейса для настольных компьютеров. ViewModel, Observers, Components и т.п. являются примерами понятий, которые заимствованы из этих архитектур. Oliver Steel, в своем посте 15-летней давности (поверьте мне, это превосходная статья) разработал хорошую справочную архитектуру для понимания потоков данных RIA.
Наиболее заметным отличием справочной архитектуры RIA от Web MVC является отсутствие Controller и View в архитектуре верхнего уровня. Однако они не ушли в буквальном смысле. Если мы посмотрим под поверхность, то Controller и View все еще присутствуют, но утончаются, их роли значительно уменьшаются. Бэк-энд реализуется, в основном, как API-сервис. Работа Представления сводится к созданию JSON, а Контроллер отвечает за принятие входящих запросов и их распределение соответствующим бизнес-процессам.
Шаблоны GUI сложны?
Если вы подробно изучили предыдущие шаблоны (Patterns), то поймете, что шаблоны GUI отличаются от других шаблонов разработки программного обеспечения. Возьмем, например, элементы многоразового объектно-ориентированного программного обеспечения (Elements of Reusable Object-Oriented Software). Большинство шаблонов не зависят от технологии или языка программирования. Однако то же самое не относится к шаблонам GUI.
Шаблоны GUI используются на границе HCI (Human Computer Interaction) — взаимодействие человека с компьютером. Пользователь (User) и побочный эффект (Side Effect) являются неотъемлемой частью шаблона.
Таким образом, практически невозможно обсуждать их в теории, не принимая во внимание фреймворки и языки программирования. До сих пор, с достаточно высоким уровнем абстракции, мы могли исследовать эти паттерны. Но по мере приближения к сути статьи мы будем отталкиваться от различных библиотек или структур для описания этих шаблонов.
Большинство шаблонов веб-интерфейса можно разделить на три этапа: эволюционный, революционный и современный.
Эволюционные шаблоны являются просто расширением серверной MVC. Они действительно не пытаются изобрести что-то новое. Они дополняют существующие приложения, улучшая свой UX шаг за шагом.
В то время как революционные шаблоны — это те идеи, которые отделяют разработку интерфейсных приложений от управляемых сервером рабочих процессов. Их прибытие отмечает известность SPA-приложений.
Современные шаблоны похожи на продвинутую версию этих революционных моделей и являются неким общим трендом, в сторону которого движется фронт-энд сообщество.
Конец первой части
LabEG
На стартовом банере есть стрелочки вокруг воздушного шара. Почему то мне кажется что следующая стрелочка будет от State Container к MVC =)