O Backbone.js очень просто и кратко для любителей MVC-фреймворков

  • Tutorial
Об использовании JavaScript-фреймворка Backbone.js написано много, но просто и кратко — мало. Я постараюсь исправить этот недостаток и рассказать разработчикам web-приложений максимально просто, доступно и кратко о том, зачем им может пригодится этот фреймворк и как, в общих чертах, он работает. Профессионалы и специалисты по Backbone.js: можете не тратить время, этот рассказ для новичков. Если быть честным, то для чтения этой заметки не обязательно быть Rails-developer'ом, надеюсь, статья пригодится всем, кто работает с любым из МVC-фреймворком.



Итак, представим себе среднестатистический проект, использующий MVC подход. Это, вероятно, будет интернет-магазин с несколькими моделями (около 10-15 обычно), связанными друг с другом различными видами отношений. Проект будет иметь соответственное количество контроллеров, 2-3 layout'а для разных устройств вывода, несколько контроллеров в namespace'е /api/v1, масса view'шек и partial'ов. Всё это работает стандартно, браузер шлёт запросы, контроллеры делают выборки данных, передают их во view'шки, те, в свою очередь, отсылаются пользователю в браузер. Есть функция «Поиск», которая ищет определённые экземпляры какой-то модели, есть view для результатов, содержащая цикл, который создаёт на странице отображение этих найденных экземпляров, есть даже мобильное приложение для Android, которое общается с Вашим серверов через API (запрашивает контроллеры, которые ждут в namespace'e /api/v1, кстати, часто дублирующие аналогичные 'обычные' контроллеры, только отдающие информацию в другом виде).

Теперь включаем режим Ванги. Я знаю, что в проекте обязательно есть некоторое количество кода на JavaScript. В различных веб-приложениях JavaScript'ом решаются разнообразные задачи — почти везде используется jQuery с плагинами или без, jQuery UI и прочие другие модули и плагины. Мы не будем рассматривать здесь использование JavaScript для создания всякого рода эффектов, выпадающих меню, drap&drop'ающихся элементов и прочее, что относится к украшательствам, мы рассмотрим работу с данными.

Итак, режим Ванги позволяет мне предположить, что вас есть модель Product (у нас ведь интернет-магазин, помните?) и безусловно есть интерфейс менеджера, который может вывести себе список товаров по некоторому критерию. У менеджера, есть стилизованный индикатор количества новых заказов, чтобы сразу же, как можно быстрее увидеть, что появился новый заказ. Я думаю, что этот индикатор работает так: в document.ready() вы сделали таймер и раз в 60 секунд запрашиваете адрес вроде /api/v1/kolichestvo_zakazov. Ну или /api/v1/orders/new/count, смотря по вашему отношению к лингвистике. В функции, которую запускает таймер у вас есть написанный на jQuery ajax запрос, который получает по ранее указанному URL'у какие-то данные. Почему какие-то? Я не знаю, Ванга не поможет. Один программист может в ответе слать корректный json вроде {orders: {new: 3}}, другой может слать просто цифру 3. Третий может на стороне сервера отрендерить и прислать в ответе целый кусок html-кода, пригодный сразу к вставке в нужное место документа. В общем, Ванга тут не поможет, тут нет никакого порядка, закона и максимальный уровень неожиданности. Но самое главное — данные, после получения, просто говоря, теряются. Клиентская сторона получила что-то, сделала с этими данными что-то и они больше не принесут вам никакой пользы.

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

Что же нам может предложить Backbone.js? Первое, на мой взгляд, самое главное, это именно упорядочивание данных, которое в Backbone.js реализуется через знакомый нам MVC. Кратко о том, как это будет:

1. У вас в приложении есть модели. User, Product, Order… Отлично, с Backbone.js Вы сможете описать эти модели на стороне клиента! И эти модели будут настоящими, вы сможете создавать новые экземпляры, редактировать поля уже имеющихся, удалять ненужные. Вы можете не писать какие-то функции, для получения какой-то информации, которая потом будет выброшена, получайте с сервера экземпляры класса order. Быть может, order будет содержать много лишней информации, подумает кто-то, нужно было же только количество! Может быть. Но никто не запрещает передавать в JSON'е только нужные на стороне клиента поля order'ов. А ваш гипотетический менеджер вдруг может захотеть кроме количества заказов видеть ещё и суммы каждого — а у нас уже всё готово, информация уже там, только покажите её, обьекты уже у клиента и синхронизированы с сервером. А если менеджер захочет прямо на месте изменить эту сумму? Не проблема. Намного приятнее написать в коде order.save( {:cost => this.input.val(} ), чем писать новую самодельную функцию. Модели будут знать URL, куда слать GET, POST, PUT и DELETE запросы и будут делать это сами, когда нужно. Например, product.delete() самостоятельно отправит DELETE на /api/v1/products/75 (75 это id этой модели).

2. У вас в приложении есть страницы, где обязательно есть множество однотипных блоков. Результаты поиска товаров тому пример. Каждый товар может быть расположен внутри какого-нибудь div class='product'. После создания этой страницы на сервере, связь этой области с товаром, в ней описанным теряется. Если Вы захотите что-нибудь делать на этой странице, вам придётся изобрести способ снова дать знать этой области с каким она товаром связана, например дать data-атрибут с id продукта и использовать его в формах и запросах… Backbone.js реализует такую привязку сам. Это будет называться View и будет делать даже больше: область продукта может реагировать на изменения продукта и перерисовываться сама, к примеру. Внутри области вам будет доступен экземпляр класса Product, именно тот самый, и вы сможете делать с ним всё, что угодно на стороне клиента. Не забудьте после сделать ему save(), чтобы Backbone.js сохранил ваши изменения на сервере.

Кстати. Хотите фильтр по некоторому полю таблицы товаров? Уже догадались, что это вообще не требует никаких дополнительных действий? Не надо ничего отправлять на сервер, получая ту же таблицу, только отфильтрованную, заново. Обьекты-то у вас уже есть, просто отфильтруйте коллекцию product'ов одной строкой кода на javascript. И да, они ещё и перерисуются сами, вам об этом нужно позаботиться всего лишь один раз, сообщив Backbone'у, что вы хотите обновлять кусок страницы, где находится информация об этом product'e при изменении product'a.

3. И ещё в тему упорядочивания данных. Используя MVC-фреймворк на стороне клиента вы, в некоторых случаях, можете свести трафик между клиентами и сервером к обмену небольшими кусками json'а, что позволит экономить огромное количество трафика. Вы будете передавать клиентам и получать от клиентов только данные, вы не будете постоянно пересылать клиентам полные страницы. Весь javascript и css может быть загружен единожды и закеширован, дальше будет происходить только обмен упорядоченными как вам удобно данными.

Вместо заключения: конечно, в этой заметке не затронуто и 5 процентов возможностей Backbone.js, но я хотел рассказать не о нём, а о самой идее поддерживать информацию в порядке как на сервере, так и в браузере. Разработчики ПО занимаются упорядочиванием окружающего мира, мы делим мир на модели, выявляем их свойства, их поведение, состояние и взаимодействие. Быстро меняющийся Интернет практически не следует стандартам (а в отношении клиентского контента вообще никаких стандартов нет, кроме 4х или 5ти спецификаций CSS, которые далеко не лучший пример порядка). Так давайте же увеличивать энтропию этого хаоса хотя бы таким удобным способом — будем держать данные в структурном порядке и в синхронизированном состоянии!

Спасибо за внимание, с уважением разработчик-перфекционист.

P.S. Если статья окажется интересной сообществу, напишу продолжение, с примерами, что изменится к лучшему при использовании Backbone.js на примере магазина, который был описан в начале статьи.
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 38

    +1
    Интересно. Только мало. И про контроллеры ничего не сказано.
      0
      Да. Рассказ про контроллеры прямо таки интригует, учитывая, что:
      Different implementations of the Model-View-Controller pattern tend to disagree about the definition of a controller. If it helps any, in Backbone, the View class can also be thought of as a kind of controller, dispatching events that originate from the UI, with the HTML template serving as the true view. We call it a View because it represents a logical chunk of UI, responsible for the contents of a single DOM element.

      documentcloud.github.io/backbone/#FAQ-tim-toady
      Что лично я перевожу на русский как «Нет у нас никаких контроллеров».
        0
        Новая разновидность MVT.
      +3
      Если статья окажется интересной сообществу, напишу продолжение, с примерами

      Сейчас бы с примерами сделали, а так читать довольно сложно сплошной текст.

      Модели будут знать URL, куда слать GET, POST, PUT и DELETE

      Также про PATCH могли бы рассказать, который сейчас поддерживается из коробки в Rails 4(думаю многие, кто работает с backbone&rails сталкивались с проблемой, когда backbone отправляет всю модель и приходилось городить свои огороды, чтобы отправить лишь часть атрибутов).
        0
        А можно поинтересоваться, зачем отправлять часть отрибутов модели?
          +3
          Попробую ответить. Все атрибуты модели нужно отправлять на сервер только в случае её создания на стороне клиента. Все атрибуты отправлять от сервера клиенту может быть нужно только если логика приложения того требует. Во всех остальных случаях нужно передавать только названия и значения изменённых полей или полей, которые действительно нужны на клиентской стороне в этот момент.
            0
            Пробовали такой подход, но отказались, т.к. оптимизация сомнительная, а вот сложность кода возрастает (проверка на клиенте и формирование, потом проверка еще и на сервере). Говорю про связку backbone & asp mvc.
              0
              Что значит «нужно»? Какая выгода от этого?
                +4
                «Нужно» не в смысле «все обязаны так делать». Выгода здесь двойная. Измеримая — в уменьшении трафика, в увеличении скорости обмена с сервисом, уменьшении времени на обработку запросов, уменьшение нагрузки на серверную часть. Выгода другого рода — мы не гоняем туда-сюда информацию, которая не используется в пункте назначения. Если я изменю поле «price» объекта «product», зачем мне слать серверу весь product, там может быть полей на килобайты, описания всякие, массив отзызов, да мало ли что ещё. Хватит отправить price и id и получить в ответ подтверждение, что запрос выполнен.
                  0
                  На вашем примере приведу аргументы, почему отказались от такого решения:
                  • Api ресурс объекта «product» не должен принимать связанные с ним сущности, такие как отзывы, комментарии, описания. Для этих элементов нужны свои ресурсы.
                  • Спорный момент, что обновление одного свойства на сервере быстрее (а в случае использования Entity Framework ORM для .net так разницы вообще не будет из за его особенностей работы. Для RoR принцип работы ORM'ок будет скорее всего такой же, но не возьмусь это точно утверждать). В итоге получим только усложнение кода, что не есть хорошо.
                  • Часто запросы на обновление таких объектов нивелируются по сравнению с количеством запросов на чтение. Т.е. лучше потратить время на оптимизацию этой операции.
                  • Для сущностей с действительно большими полями (сотни кб текста) лучше будет отправлять дельту изменений, хотя это то еще специфичное и на любителя решение.

                  В итоге получим усложнение логики и экономию пары килобайт на запрос обновления. Довольно таки спорная оптимизация.
                    0
                    ActiveRecord в RoR умеет отслеживать «dirty» поля, и сохраняет в базу только те поля, которые изменились. В итоге рельсе, в принципе, пофиг, всю модель мы ей отправляем, или только часть — код в контроллере одинаков, а ORM сама решает, какие поля ей сохранять.
                    Так что в частичной отправке полей всё же есть смысл.
                    Ну и, в конце концов, если вы редактируете статью на несколько кб, поменяв в ней только опечатку в title — действительно нет абсолютно никакого смысла, ни, тем более, профита!, в отправке ВСЕХ полей записи на сервер.

                    PS.:
                      # PATCH/PUT /posts/1
                      def update
                        if @post.update(post_params)
                          redirect_to @post, notice: 'Post was successfully updated.'
                        else
                          render action: 'edit'
                        end
                      end
                    


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

                      А это, значит, не костыли, да? :)
                      +1
                      Ну да, еще электричество экономим и снижаем выбросы парниковых газов.
                      Но я скорее соглашусь с предыдущим комментарием: оптимизация сомнительная, а сложность кода возрастает.
                      Хотя, всяко бывает. Иногда это может быть важно.

                      И странно, что массив отзывов лежит в той же модели, что и продукт. Думаю, это ошибка.
                        0
                        Mongoid, class Product embeds_many :reviews
                        Ничего странного не вижу. Решение, может быть, спорное, но не странное.
              0
              Я думаю, что этот индикатор работает так: в document.ready() вы сделали таймер и раз в 60 секунд запрашиваете адрес вроде /api/v1/kolichestvo_zakazov.

              Кстати, а сам обмен Backbone.js как-то реализует? Или так же ручками таймер вешать, только модели заставлять синкаться?
                0
                Можно ручками таймер и синкать модель, можно воспользоваться плагинами и сделать обмен в real-time на основе websockets. Вариант с таймером на фоне остальных прелестей бекбона выглядит, конечно, деревянно, но тем не менее внутри обработчика таймера будет всего лишь одна строка: @model_name.fetch() — всё же лучше самописного кода, как мне кажется.
                  0
                  Ну, вообще говоря, там может быть простыня @model1.fetch(), @model2.fetch() и т. д.
                    0
                    @models.fetch()?
                      0
                      Вряд ли у Вас будет на одной странице такое множество РАЗНЫХ моделей, чтобы из их инициализации получилась простыня. Если Вы имеете ввиду, что у вас на странице множество экземпляров одного класса (например, 150 product'ов), то на этот случай Backbone умеет т.н. коллекции. Вы можете обновить сразу всю коллекцию: @products.fetch(). Вот так.
                        0
                        Ну почему. Скажем игра какая-нибудь — там вполне могут быть десятки коллекций разных типов.
                  –7
                  1. ЭТО очень кратко?
                  2. Если быть честным, кому вообще сдались ваши Рельсы? Это нынче тренд такой? Пэхапэшники — лошары?

                  *TROLL MODE OFF*
                    0
                    1. Это слишком кратко. Многое не написал, т.к. знаю, что тексты хорошо воспринимаются только до определённого количества слов :) Лучше написать продолжение.
                    2. Я буду с Вами честным: наши рельсы сдались очень многим. А кроме того, на месте Rails в этих примерах мог быть любой фреймворк (хоть и на PHP), приложение на котором реализует CRUD-интерфейс.
                      +1
                      1. Переосторожничали имхо :)
                        –1
                        1. Поскольку вы же сами упомянули про то что о Backbone написаны горы туториалов (и это действительно так), я ожидал некого тезисного изложения, в максимально краткой форме: особенности, что хорошо, что плохо. Но это исключительно мои не оправдавшиеся ожидания.

                        2. Вы написали статью о фреймворке на языке JavaScript. Языке, по большей части, используемом для фронт-енд разработки. Более того, о фреймворке используемом не «аниматорами кнопок на jQuery», а людьми, большую часть времени занимающимися фронтендом. К чему вообще ваши пассажи о том кем им быть и знать или не знать рельсы (или любую другую backend технологию). Вариант «в глаза не видеть этот ваш серверсайд» — тоже возможен, учитывайте и его.

                        Ну и сказать по правде, если уж проводить параллели и точки соприкосновения с бэкендом — имхо делать это надо с максимальным покрытием аудитории. Ну или выдерживать полностью абстрактный уровень повествования. Вынесли бы в заголовок то что статья про Backbone+Rails, я бы и изначально не прицепился.
                          +1
                          Тут пара рубишных строк кода, понятных всем. Хотите сделать статью с этим вашим php — пишите.
                        +3
                        Про рельсы тут чуть менее чем ничего. Собствеенно говоря даже про js практически ничего.
                        +1
                        Интересно. Хочется больше.
                          0
                          Если мы говорим о серверном MVC, то там чаще всего уже есть модели.
                          И на клиентской стороне модели писать.
                          Как-то оно… не так.
                          Есть хорошее решение?
                            0
                            А как по мне, то хорошо. Ещё лучше, конечно, их один раз написать и чтобы и на сервере, и на клиенте были, но не уверен, что такое даже с node.js можно малой кровью…
                              0
                              Ну почему же, описание модели — это, по сути, js код, загружаемый с сервера. Ничто не мешает нам генерировать его динамически. Единственная проблема — js-specific методы модели.
                                0
                                Я бы сказал — браузер-специфичные, или backbone,js специфичные, если угодно. Именно поэтому уточнил что не уверен что даже с node.js получится малой кровью связать.

                                А идея динамической генерации мне не нравится, даже если использовать какой-то вид кэширования, то есть генерировать не на каждый раз, а, скажем, при деплое. Мне миграций и копирования конфигов хватает.
                              • НЛО прилетело и опубликовало эту надпись здесь
                              0
                              Кое-чего не понятно без примеров. Откуда модели будут знать URL? Сколько кода получится?
                              И зачем писать на клиенте модель, контроллер, если нужно просто получить список товаров в корзине. Не представляю ситуацию, когда информацию о товаре нужно срочно обновить.
                                0
                                Ну как же. Зашли в свою корзину и изменили количество заказываемых товаров какой-то конретной позиции.
                                  0
                                  А, неправильно понял. Я думал, речь идет о странице категории товаров.

                                  И все же, пожалуйста, дополните пост примерами.
                                0
                                А для чего данные упорядочивать на клиенте? Да еще и все?

                                Ну есть у меня на 200млн позиций БД под 160Гб размером, я все это должен разом выплюнуть клиенту? А там он пусть упорядочивает? Зачем?
                                И какой клиент это выдержит?
                                  0
                                  А кто говорит про ВСЕ данные? Создается несколько моделей на клиенте и загружаются только необходимые в данный момент объекты.
                                    0
                                    Про «ВСЕ» данные говорит автор статьи

                                    «Не надо ничего отправлять на сервер, получая ту же таблицу, только отфильтрованную, заново. Обьекты-то у вас уже есть, просто отфильтруйте коллекцию product'ов одной строкой кода на javascript. И да, они ещё и перерисуются сами»

                                    Понимаете ли, фильтрация по цене текущей страницы и всего списка товаров (которые занимают надцать страниц) это две разные вещи.
                                    В случае магазина первое вообще редко нужно. А для второго нужно загружать таки «все».

                                    Т.е. в целом я понимаю, бывают случаи когда нужно фильтровать и сортировать данные уже находящиеся на клиенте полностью. Вот в этих случаях — да, дергать бекэнд как-то глупо. В остальном же… для более менее крупных объемов данных я бы оставил сортировку на бекэнде.

                                Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                Самое читаемое