Три подхода к методологии построения сложного клиентского приложения

    Наверно, не существует единого рецепта, который бы всех устроил. Это касается любой проблемы. Для разработчиков этот тезис самоочевиден, и вовлеченность в использование и проектирование отдельных инструментов определяется, главным образом, лишь профессионализмом. Изобретение велосипедов романтично и неизбежно.

    Особо вероятно изобретение велосипеда, когда рост сложности приложения происходит постепенно и в некотором смысле незаметно. Сложное приложение обычно является богатым приложением (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).

    image

    Особенности управления Контроллером привели к несколько иной модели MVP (Model View Presenter), а затем к MVVM (Model View ViewModel). MVVM отличается более “тесными” связями между Моделью и Представлением в виде Представления-Модели (или Видимой Модели, если хотите), которая синхронизирует данные как при событии на стороне Модели, так и на стороне Представления.

    image

    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).

    image

    Позволю себе предложить и третий вариант, идущий из того же подхода “разделения”. Разграничим объекты (в широком смысле) на акторов, функционал и сценарии. Акторы — это части приложения в их графической интерпретации. Можно рискнуть назвать их виджетами. Акторы обладают информацией о самих себе, в них, таким образом, помещены данные. Функционалы — это списки возможных преобразований сходных виджетов, каждый функционал может быть привязан к актору. Вызов же этих функционалов происходит в сценарии. Сценарий может быть актор-зависимым, может быть инициализирующим. В нем просто перечисляются последовательные вызовы функционалов. Понятно, что это не самый оптимальный путь с точки зрения масштабирования при возможном усложнении задачи. (Но зато, по-моему, это самый очевидный путь создания велосипеда.)

    image

    Где достигается масштабируемость
    Как описывал сам Закас, большое преимущество его архитектуры больших приложений, это то, что можно основываться на любой из стандартных библиотек типа jQuery и менять их без проблем, чуть исправляя уровень Application Core. Легко добавлять модули и “выключать” их.
    В модели MVVM мне видется очень жесткая “вертикаль”: изменения в интерфейсе заставляют описывать все три компоненты; хотя уже описанные элементы могут быть изменены без особых заморочек. При этом самая масштабируемая, самая гибкая часть — уровень представления, когда полная перерисовка графики интерфейса не затрагивает ее логики.
    В наивном подходе тоже жесткая связка, определяющая обязательное описание всех трех объектов реализации. Но самая гибкая часть, по всей видимости — уровень сценариев, которые могут меняться очень просто.

    Где прописывается основная логика
    [благодаря комментаторам большая часть этого блока исправлена!]
    В модели Закаса бизнес логика и UI реализуется в модулях.
    В MVC логика зашита в Модели, ее можно также помещать в Контроллер, но это справедливо подвергается критике (см. источники). В MVVM, напротив, логика помещается в «промежуточный» слой ViewModel.
    В наивном подходе логика разбита по сценариям, а UI в примитивных функционалах.

    Много ли служебного кода, оценка теоретического объема для framework
    В модели Закаса фреймворк описывает служебные связи модулей с песочницей (“запуск в окружении”), и ядро, описывающее основные типы действий.
    MVC предполагает описание основных классов для модели и представления. Часть функций контроллера тоже должна быть реализована во фреймворке (например, синхронизация для модели MVVM).
    Наивный подход должен содержать реализацию “сборки” акторов и привязнных к ним функционалов.

    Простота тестирования частей
    Подход модульности отлично воспринимает тестовые сценарии — каждая часть схемы может быть протестирована отдельно и каждый модуль также.
    MVC тоже может быть протестирован “частями” по уровням взаимодействия с Контроллером, но это более “тяжелое” по объему тестирование.
    Наивный подход приспособлен к тестированию функционирования отдельных виджетов. Тестирование сценариев может оказаться более сложным из-за их ветвления и нечетких начальных состояний для тестирования.

    Действительно ли код re-useable
    Подход модульности создает прекрасно масштабируемые модульные решения (причем при применении AMD в рамках всего интернета, а не только приложения). Код может быть применен потом частями в рамках другого проекта на другой основной библиотеке (YUI etc.).
    MVC может быть промасштабирован только внутри своей задачи, в случае модификации и замены уровня Представления.
    Наивный подход создает отдельные функционалы, которые могут быть использованы повторно на различных акторах. Можно выстраивать иерархию акторов. Сценарии “одноразовые”.

    Хотелось бы этот поверхностный анализ углубить. Какие моменты я отразил неверно? Какие методы имееют более широкое описание? Прошу всех комментаторов поделиться опытом использования схем MVC, MVVM и подхода модульности!

    UPD
    mark_ablov предложил неочевидную для меня связь между подходом MVC и подходом модульности: модули могут быть реализованы на схемах MVC, в частности. Итого, можем получить такую диаграмму:
    image

    Источники

    habrahabr.ru/blogs/webdev/117791
    blog.rebeccamurphey.com/code-org-take-2-structuring-javascript-applic
    addyosmani.com/largescalejavascript
    nirajrules.wordpress.com/2009/07/18/mvc-vs-mvp-vs-mvvm
    www.w3.org/TR/backplane
    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
    Share post
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 27

      0
      > В модели Закаса бизнес логика и UI реализуется в песочнице и отчасти в ядре.
      Разве?
      Насколько я знаю, всё (UI и бизнес-логика) содержится в модулях, ядро и песочница служат только для оперирования модулями (создание/отключение, event managment, работа с правами доступа).
      Песочница служит только одной глобальной цели — идентификация модуля, а это в свою очередь нужно для разграничения прав доступа.
        0
        Да, вопрос хороший. Если помещать всё в модули, то модель Закаса почти сведется к MVC, где песочница — другое название Контроллера. Мне кажется, есть некоторая логика, которая неизбежно будет в ядре, а именно, когда перерисовка интерфейса фиксирует несколько модулей. Хотя я могу ошибаться.
        Текст поправлю позже, когда наступит более четкое понимание.
          +1
          Не согласен.
          Модуль в данном случае содержит и Model, и View, и Controller.
          Ядро в данном случае выступает фреймворком, который всё это поддерживает, а песочницы реализуют какое-то подобие FrontController'a, через который проходит общение с другими модулями и с ядром.
          В данном случае это не является плохой практикой смешивать эти три сущности, ибо всё же client-side разработка вносит свои коррективы, и собственно вьюшки для каждого отдельного модуля либо очень просты, либо их вообще нет.
          Например, почтовый клиент — модуль «список писем», 1 вьюшка, 10 действий (action); модуль «список папок», 2 вьюшки, 10 действий; модуль «новое письмо», 1 вьюшка, 5 действий.
          Собственно поэтому контроллер цепляем к модели и не паримся.
          В ядре только логика работы с модулями + generic-event'ы, такие как resize/ready. Но всё эти event'ы просто транслируются подписанным модулям, буквально одна-две строчки.
          PS: Говорю только из собственного опыта, большое-небольшое, но 10к строк написано :)
            0
            Осознание, кажется, пришло. Обновил топик.
        0
        > В MVC логика зашита в Контроллере.
        Имхо логика должна быть в модели
          0
          Да, обычно критикуют размещение логики в контроллере, называя их «жирными и тупыми». Я когда писал эту строчку, держал в уме модель MVVM, где логика прописывается в VM. Надо будет поправить в тексте. Спасибо!
          +2
          Есть ли где-то более подробное описание реализации модели Закаса, желательно словами, а не видео? А то приходится догадываться о том, как он представляет перенос данных и команд между уровнями и сколько дополнительных прикладных языков существует в его системе, чтобы в конце осуществились слова "основываться на любой из стандартных библиотек типа jQuery и менять их без проблем". Иначе звучит очень рекламно, потому что библиотека — это язык выражения действий. Смена библиотеки — смена языка. А, простите, на чём тогда выражена модель? Очевидно, на своём языке, со своими понятиями. И «беспроблемная» смена библиотеки означает «всего лишь» замену методов одной библиотеки методами другой. Или дописывание своего «контроллера» библиотеки — транслятора языка данной библиотеки в свой. Хочется увидеть, где об этом говорится.
            0
            Согласен, мне тоже это показалось какой-то ересью. Вот использовал я MooTools, расширенные прототипы, его работу с DOM и всё остальное, а потом решил перейти на jQuery. Как это можно сделать легко? О, о
              0
              Легко не получиться. Смысл в том, что в ядре создается дополнительный уровень абстракции для управления методами библиотеки и плагинов. Поэтому при смене библиотеки или плагина достаточно переписать только ядро и не менять модули. Например, как-нибудь так
              var Core = {};
              Core.getElements = function( selector, context ){ return jQuery(selector, context||document);  }//jQuery
              Core.getElements = function( selector, context ){ return $(context||document).getElements(selector);  }//Mootools
              
                0
                но ведь интерфейс у вернувшихся объектов разный
                  0
                  В этом и состоит задача ядра создать новый интерфейс для подключаемых библиотек. Насколько будет поменять библиотеку я себе слабо представляю. Это, как говориться, всего лишь идея. Знаю, точно, что в ядре очень хорошо оперировать со сторонними плагинами и менять их при необходимости.
                    0
                    а если декорировать возвращаемые объекты на уровне ядра?
                0
                И довольно трудно представить (точнее, не представляю), как транслируется представление (View) у него в Песочницу, а потом обратно, в требуемое представление. Вот, для примера. Есть у нас страница. Там разные блоки, стили, места для информации и для работы виджетов, и эти места пересекаются. Скажем, один виджет — показ кода, другой — подсветка кода в одном и том же блоке. Предлагается создать Песочницу, где тот же блок предстанет для первого скрипта как контейнер для текста, а для второго — как место для раскраски? Но ведь ясно же, что их действия пресекаются, какая здесь независимость? Ладно, по правилам Песочницы блок заполнили, раскрасили и ядро приложения переносит его обратно, в HTML. Можно представить себе, насколько тщательно ядро должно учитывать сособенности браузеров, чтобы отразить представление в Песочнице, а потом перенести обратно. (Я правильно понимаю модель поведения ядра?) И затем смотрим на результат и думаем, какие и на каком уровне допущены ошибки, почему и где у нас не получилось, как задумано. Вместо 2 предметов — виджет и объект — имеем Вид, Ядро, Песочница, Виджет (Модуль в его терминах). И ещё библиотека, с помощью которой написан Модуль — легко меняется. Значит, ещё имеем где-то в схеме Транслятор библиотеки в некий внутренний язык. 5 сущностей вместо 2. Так писать действительно легче?
                  0
                  Представление (VIEW) не содержится в схеме Закаса. Транслятор в ядре (в каком-то смысле ядро и есть транслятор, хотя предполагаются еще служебные функции).
                  Действительно ли писать легче — я на это не могу ответить. Кстати, Вы использовали MVC или MVVM в js?
                    0
                    Я писал на knockout в стиле MVVM.
                    А отсутствие слоя View я у него уже «исправил» (т.е. поставил туда, где оно могло бы стоять, правда, линия должна идти из ядра) и хочу предложить картинку немного по теме того, что обсуждается. И чуть ниже — почему я к ней пришёл.

                    Итак, есть ещё один путь построения архитектуры, я с ним познакомился не так давно, благодаря публикациям такой библиотеки: habrahabr.ru/blogs/javascript/133328/. Чтобы долго не искали суть, расскажу в 2 словах. Библиотека DOM-shim подходит к работе как с DOM, так и с моделью языка так, что создаёт мета-модель языка, близкую к новейшим стандартам, а остальные браузеры (практически все) подтягивает до понимания этой модели. И даёт работать в этой модели. Для самых далёких от модели браузеров ситуация будет выглядеть так, как на рисунке: они будут понимать задачу в песочнице, но транслятор не сможет её отобразить. И, как следствие, просьба заменить себя :).
                    В плане построения архитектуры — почему бы не взять за основу метаязык, близкий к стандартам?
                      0
                      Мне кажется, Вы немного ушли в частности. DOM-shim можно мыслить как плагин или как часть основной библиотеки. Аналогично Sizzle есть часть jQuery, и незачем выделять это в отдельный View. В основной библиотеке должна быть работа с сетью (XHR), работа с браузерными событиями и проч. техника — не будем же мы для каждого такого блока выделять отдельную логическую базу на схеме?
                        –1
                        Да, часть основной библиотеки, тот самый необходимый метаязык. Точнее, заплатки, позволяющие реализовывать его во всех браузерах. (Можно считать язык абстрактным, но тогда смена языка изложения потребует переписывания всех программ.) У меня и был об этом вопрос — каков язык описания на клиентской стороне? Взятие его за основу позволит написать ядро и отвязаться от других библиотек.
                  0
                  Есть скринкаст-курс marketplace.tutsplus.com/item/writing-modular-javascript/128103/, платный.
                  Но я его пока не смотрел. Пока что я тоже понял Закаса в том смысле, что есть некоторый транслятор для стандартной библиотеки. Он может быть простейшим для начала: изменение переменной $ на 'lib', к примеру.
                    0
                    Вроде даже на Хабре было описание от azproduction (верно ник написал? :) ).
                    С примерами, со структурой.
                • UFO just landed and posted this here
                    +2
                    ОМГ. И этот человек ратует за чистоту великого и могучего.
                      0
                      Вы, очевидно, тролль. Но тем, кто действительно не сталкивался раньше с этим термином («актор»), нужно понимать, что UML-нотация — она устоявшаяся, все определения уже употребляются в проф. среде.
                      Если же переводить actor на русский в данном контексте, то это будет что-то типа «движитель», «субъект», но никак не «актер».
                      • UFO just landed and posted this here
                          0
                          Вам нравится слова тренд, бюстгалтер, аккумулятор, компиллятор… компьютер, в конце концов? Или предпочитаете «ЭВМ»?
                            0
                            *нравятся
                            • UFO just landed and posted this here

                      Only users with full accounts can post comments. Log in, please.