Три подхода к методологии построения сложного клиентского приложения
Наверно, не существует единого рецепта, который бы всех устроил. Это касается любой проблемы. Для разработчиков этот тезис самоочевиден, и вовлеченность в использование и проектирование отдельных инструментов определяется, главным образом, лишь профессионализмом. Изобретение велосипедов романтично и неизбежно.
Особо вероятно изобретение велосипеда, когда рост сложности приложения происходит постепенно и в некотором смысле незаметно. Сложное приложение обычно является богатым приложением (rich), его элементы и особенности специфицированы W3C www.w3.org/TR/backplane/. Известный JavaScript-евангелист Addy Osmani так дополнительно определяет сложное приложение: “По-моему, крупное JavaScript приложение есть нетривиальное приложение требующее значительных усилий разработчика для поддержки, причем наиболее сложное оперирование обработки и отображения данных ложится на браузер” (http://addyosmani.com/largescalejavascript/).
Для десктопных приложений процесс разработки вроде как установился, описан. Для сетевых приложений общая схема тоже ясна. Но техническая особенность веба, выраженная в языке разметки и неформализованного понятия юзабилити, усложняет понимание и описание богатых приложений. Особенность сетевого характера приложения, в котором возможности работы в реальном времени ограничены техническими факторами и нет моментального доступа к данным; особенность объектного языка JavaScript, у которого нет родных конструкций для классов; особенность развертывания и исполнения в разных средах (т.е. браузерах), что влияет на производительность и возможности, — все эти факторы, видимо, мешали на ранних порах определить мощь клиентских приложений и разработать общую конструкцию для их проектирования (сори за пафос).
Я просто не вижу такой общей, пусть даже она и есть. Вместо этого я вижу и осмысливаю несколько разных концепций. Первая — знаменитая схема проектирования MVC (Model View Controller). Вторая — концепция логического разделения кода от Николаса Закаса (выступление на конференции Yahoo www.youtube.com/watch?v=vXjVFPosQHw ).
Третья — наивный подход создания кода, где перемешаны данные, оперирование элементами интерфейса и различные функции (перемешивание трагически может вылиться в “макароны”).
При этом достаточно тонкой гранью является вопрос, что же в этих подходах является организацией кода, а что — организацией функционала.
Схема MVC (Модель Представление Контроллер) нашла несколько пользующихся успехом реализаций в JavaScript: backbone.js, JavaScriptMVC. Вкратце о схеме. Модель содержит в себе данные в их структуре и иерархии, Представление описывает тот вариант этих данных, который передается пользователю. Прямой связи Модель и Представление не имеют, для этого есть посредник в виде Контроллера — он управляет событиями и может управлять несколькими представлениями или передавать управление на другой модуль MVC (в схеме иерархических MVC).
Особенности управления Контроллером привели к несколько иной модели MVP (Model View Presenter), а затем к MVVM (Model View ViewModel). MVVM отличается более “тесными” связями между Моделью и Представлением в виде Представления-Модели (или Видимой Модели, если хотите), которая синхронизирует данные как при событии на стороне Модели, так и на стороне Представления.
MVVM была описана для Silverlight и имеет преимущества для сложных интерфейсов с определенной логикой, которая отличается от логики приложения (например, при добавлении объекта в модель, нетривиально изменяется интерфейс приложения). Для HTML схема MVVM особо удачна благодаря DOM, который, как известно, вполне может вмещать данные. Поэтому MVVM была успешна реализована во фреймворке knockout.js. Изначально все просто. Есть модель данных, предоставленная сервером. Есть представление в виде DOM, в виде разметки. А есть Представление-Модель, которая описывает изменение представления, связывает модель и представление, причем синхронизует эту связь.
Стоит отметить, что MVC могут трактовать просто как разделение трех уровней приложения, и никак не регламентировать связи между ними. Поэтому довольно часто встречаются диаграммы, на которых Модель и Представление связаны стрелками, хотя очевидно, что таким образом теряются полезные свойства масштабируемости при использовании разных Представлений и иерархичность Контроллеров. Подробнее об этом очень четко изложено в первой лекции Paul Hegarty о разработке приложений под iOS www.stanford.edu/class/cs193p/cgi-bin/drupal/node/259.
Николас Закас представил свои предложения по разработке (их формализация на язык паттернов есть в статье Addy Osmani addyosmani.com/largescalejavascript/ ). Он решил, собственно, уйти от общей модели и логики, и воспользоваться алгоритмом “разделяй и властвуй”: элементы приложения оформлены блоками интерфейса, а реализованы каждый в своем модуле. Эти модули друг с другом не обмениваются. Они знают только о песочнице, в которой происходит их выполнение. Песочница, в свою очередь, имеет дело с ядром приложения, которое запускает процесс и следит за последовательностью действий. Само ядро при этом не последнее в цепочке (вызывов): ниже есть стандартные библиотеки по технической работе с окружением, вроде jQuery, вместе с плагинами. Для модульных приложений создана спецификация описания модулей AMD (и альтернативная CJS).
Позволю себе предложить и третий вариант, идущий из того же подхода “разделения”. Разграничим объекты (в широком смысле) на акторов, функционал и сценарии. Акторы — это части приложения в их графической интерпретации. Можно рискнуть назвать их виджетами. Акторы обладают информацией о самих себе, в них, таким образом, помещены данные. Функционалы — это списки возможных преобразований сходных виджетов, каждый функционал может быть привязан к актору. Вызов же этих функционалов происходит в сценарии. Сценарий может быть актор-зависимым, может быть инициализирующим. В нем просто перечисляются последовательные вызовы функционалов. Понятно, что это не самый оптимальный путь с точки зрения масштабирования при возможном усложнении задачи. (Но зато, по-моему, это самый очевидный путь создания велосипеда.)
Где достигается масштабируемость
Как описывал сам Закас, большое преимущество его архитектуры больших приложений, это то, что можно основываться на любой из стандартных библиотек типа jQuery и менять их без проблем, чуть исправляя уровень Application Core. Легко добавлять модули и “выключать” их.
В модели MVVM мне видется очень жесткая “вертикаль”: изменения в интерфейсе заставляют описывать все три компоненты; хотя уже описанные элементы могут быть изменены без особых заморочек. При этом самая масштабируемая, самая гибкая часть — уровень представления, когда полная перерисовка графики интерфейса не затрагивает ее логики.
В наивном подходе тоже жесткая связка, определяющая обязательное описание всех трех объектов реализации. Но самая гибкая часть, по всей видимости — уровень сценариев, которые могут меняться очень просто.
Где прописывается основная логика
В модели Закаса бизнес логика и UI реализуется в песочнице и отчасти в ядре.
В MVC логика зашита в Контроллере.
В наивном подходе логика разбита по сценариям, а UI в примитивных функционалах.
Много ли служебного кода, оценка теоретического объема для framework
В модели Закаса фреймворк описывает служебные связи модулей с песочницей (“запуск в окружении”), и ядро, описывающее основные типы действий.
MVC предполагает описание основных классов для модели и представления. Часть функций контроллера тоже должна быть реализована во фреймворке (например, синхронизация для модели MVVM).
Наивный подход должен содержать реализацию “сборки” акторов и привязнных к ним функционалов.
Простота тестирования частей
Подход модульности отлично воспринимает тестовые сценарии — каждая часть схемы может быть протестирована отдельно и каждый модуль также.
MVC тоже может быть протестирован “частями” по уровням взаимодействия с Контроллером, но это более “тяжелое” по объему тестирование.
Наивный подход приспособлен к тестированию функционирования отдельных виджетов. Тестирование сценариев может оказаться более сложным из-за их ветвления и нечетких начальных состояний для тестирования.
Действительно ли код re-useable
Подход модульности создает прекрасно масштабируемые модульные решения (причем при применении AMD в рамках всего интернета, а не только приложения). Код может быть применен потом частями в рамках другого проекта на другой основной библиотеке (YUI etc.).
MVC может быть промасштабирован только внутри своей задачи, в случае модификации и замены уровня Представления.
Наивный подход создает отдельные функционалы, которые могут быть использованы повторно на различных акторах. Можно выстраивать иерархию акторов. Сценарии “одноразовые”.
Хотелось бы этот поверхностный анализ углубить. Какие моменты я отразил неверно? Какие методы имееют более широкое описание? Прошу всех комментаторов поделиться опытом использования схем MVC, MVVM и подхода модульности!
Источники
blog.rebeccamurphey.com/code-org-take-2-structuring-javascript-applic
addyosmani.com/largescalejavascript/
nirajrules.wordpress.com/2009/07/18/mvc-vs-mvp-vs-mvvm/
Alex MacCaw, JavaScript Web Applications. jQuery Developers’ Guide to Moving State to the Client, 2011
www.youtube.com/watch?v=vXjVFPosQHw (видео)
www.intuit.ru/department/internet/aspnetmvcframe/1/ (видео)
www.stanford.edu/class/cs193p/cgi-bin/drupal/node/259 (iTunes, видео)
blog.astrumfutura.com/2008/12/the-m-in-mvc-why-models-are-misunderstood-and-unappreciated/ (php, критика толстых контроллеров)
martinfowler.com/eaaDev/uiArchs.html