вторник, 17 ноября 2009 г.

Зачем нужна Closure Templates

Традиционные шаблоны

Хорошо известная концепция разделения логики приложения и логики представления с помощью шаблонов не работает для AJAX-приложений. Проблема заключается в том, что использование JavaScript для модификации страницы имеет следующие последствия:
  • Код шаблонов должен учитывать, что страница будет модифицироваться с помощью JavaScript, т.е. вы больше не можете отдать шаблоны полностью на откуп дизайнерам, а должны также привлекать и JavaScript-программиста в процессе их разработки
  • JavaScript код должен обладать высокой степенью информированности о структуре страницы
  • JavaScript код должен находиться в полностью согласованном состоянии с кодом шаблонов
  • В случае сложного дизайна, его (JavaScript код) просто сложно писать и каждое изменение в дизайне приложение связано с двойной работой и будет вызывать боль

XSLT

Данные проблемы могла бы решить технология, позволяющая использовать один и тот же код генерации контента как на стороне сервера, так и на стороне клиента. Кроме того, необходима простая возможность генерации как целых страниц, так и её отдельных фрагментов. И такая технология есть уже достаточно давно - это XSLT. Я имею очень хороший (и успешный) многолетний опыт использования XSLT для решения различных задач. И я использую эту технологию в своем основном приложения именно для распределения логики представления между серверной и клиентской частью. И по началу всё шло просто чудесно, но недавно я стал понимать, что имею серьёзные проблемы в этой части. И это при том, что основой приложения является XML REST служба и все данные изначально представлены в XML, а в качестве клиента используется исключительно Firefox: для отображения контента используется XUL и SVG. Т.е. среда для использования XSLT очень даже благоприятная. Кроме того, моя попытки использовать XSLT в более общих web-приложениях не увенчалась успехом: на практике проще обойтись без XSLT. В общем, факт в том, что XSLT не получил действительно широкого распространения в той области, для которой он, собственно, и предназначался. Я вижу следующие причины этого:
  • Тяжеловесность технологии XML. Я имею ввиду, в том числе, что всегда нужно написать достаточно много строчек кода, что бы использовать его даже для простейших операций. Если обмен данными идёт большими кусками, то с этим ещё можно мириться, но когда есть множество функций, оперирующих крохотными кусочками данных, - overhead становится просто невыносим.
  • Ограниченные возможности самого языка преобразований. Он идеально подходит именно для трансформации данных, но на практике часто нужно делать что-то ещё. При использовании XSLT на стороне сервера возможно применения собственных функций расширения, но это не работает на клиенте, что исключает возможность использования расширений в логике представления, работающей как на сервере, так и на клиенте.
  • Различный уровень поддержки технологии со стороны браузеров
  • Проблема дизайна XML-данных. Если он плох (а как показывает практика, далеко не все могут его готовить), то система очень скоро начнёт напоминать монстра, и XML-ненавистники обязательно обратят на это внимание :)
Концептуально XSLT разрабатывался как технология, которая могла быть решить большинство проблем, связанных с логикой представления, но её фактическое состояния, не позволяют говорить об этом всерьёз для широкого круга приложений (что не исключает возможности успешного применения в частных случаях).

Closure Templates

В последнее время, ввиду накопившихся у меня проблем я был занят поиском подходящего решения в это области, и не мог ничего найти, мне ничего не нравилось, не решало моих принципиальных проблем. В сети встречаются описания схем, ориентированных на решение описанных проблем, но они типично сложны в понимании, сложны в технической реализации, плохо масштабируемые, накладывают различные ограничения на структуру приложения т.п.. И тут на помощь пришёл Google, опубликовавший свою библиотеку Closure Templates, которая показалась мне глотком свежего воздуха. Это система шаблонов, которая, как мне представляется, лишена описанных выше недостатков и обладает следующими свойствами:
  • Удивительно простое решение, простое для понимания, простое для использования, не накладывающее никаких ограничений на структуру приложения
  • Один и тот же код, может быть использован как на клиенте, так и на сервере
  • Для использования данной системы нет необходимости в поддержке браузером каких-либо специфичных технологий, поскольку шаблоны компилируются в эффективный JavaScript-код
  • Шаблоны можно разбивать на настолько маленькие компоненты, насколько это необходимо, что даёт простую возможность как генерации больших кусков контента, так и обновления самых небольших фрагментов
  • Входным форматов для шаблонов на стороне клиента, фактически, является JSON
  • Результатом вызова шаблона, в несколько упрощённом виде, является строка
Последние два пункта, между прочим, позволяют легко сочетать данную систему с современными javascript фрэймворками, в частности, я попробовал использовать совместно с jquery и осталось весьма доволен результатом, кажется они просто созданы друг для друга :)

Данная система имеет только один серьёзный недостаток - язык разработки Java :( Но теперь это не проблема, поскольку доступна cl-closure-template - Common Lisp реализация этой идеи (см. мои предыдущие сообщения).

Пример использования

Для cl-closure-template я написал очень небольшое демонстрационное приложение (которое включил в состав исходного кода), этакую "доску сообщений". Пользователи могут заходить и создавать свои сообщения. Сообщения включают в себя информацию об авторе, заголовок и тело. Пользователь может увидеть список всех сообщений, которые при этом отображаются в сокращённом виде (без тела), а также получить "расширенную" версию выбранного сообщения. Все действия производятся в рамках одной страницы, без перезагрузки. Собственно, программа обладает следующим поведением:
  • При загрузке страницы пользователю показывается списком сообщений (для простоты они хранятся в памяти сервера): заголовок и автор
  • Заголовок сообщения обозначается как ссылка, при клике мышкой по нему с сервера асинхронно запрашивается более подробная информация о сообщении, которая преобразуется в html-разметку и происходит замена оригинального html-фрагмента (с сокращенным описание) на сгенерированный
  • Внизу страницы отображается в виде ссылки команда "New message", при клике на которую создаётся (не показывается, а именно создаётся) форма для ввода нового сообщения
  • Пользователь вводит данные и нажимает "Создать": данные формы асинхронно отправляются на сервер, на котором создаётся новый объект и возвращается его описание в формате JSON. На основе возвращённых данных создаётся html-фрагмент для нового сообщения и помещается в начале списка
Для упрощения реализации я использовал jQuery и jQuery Form Plugin. Вся логика представления расположена в одном файле шаблонов: messages.tmpl. В итоге, для реализации описанного выше поведения мне пришлось написать чуть более 30 строк простейшего кода на JavaScript: edit.js. При этом, для большинства случаев разумного изменения дизайна приложения необходимо изменить только файл шаблонов, без необходимости править JavaScript. Весь код приложения можно посмотреть здесь.

6 комментариев:

  1. мне поначалу показалось, что это перевод (хорошо еще не машинный):

    «Я имею очень хороший (и успешный) многолетний опыт использования XSLT для решения различных задач. И я использую эту технологию в своем основном приложения именно для распределения логики представления между серверной и клиентской частью. И по началу всё шло просто чудесно, но недавно я стал понимать, что имею серьёзные проблемы в этой части.»

    ОтветитьУдалить
  2. @banan

    Да, слог сегодня не тот :(, что не удивительно, учитывая в каком графике я работал последнюю неделю, но мне было важно написать этот пост сегодня, что бы можно было спокойно сегодня выспаться и уже завтра переключиться на другую задачу.

    ОтветитьУдалить
  3. Спасибо за описание.
    Один вопрос: а почему с сервера возвращается уже готовый HTML, а не JSON-данные, которые можно подставить в шаблон на клиенте?

    ОтветитьУдалить
  4. @Vsevolod
    Брр... Запутался, не понял вопроса. HTML может генериться на сервере. Либо на сервере можно сгенерить JavaScript код, которому на клиенте можно перед JSON-данные и уже на клиенте сгенерить HTML.

    ОтветитьУдалить
  5. Я имел в виду: "Заголовок сообщения обозначается как ссылка, при клике мышкой по нему с сервера асинхронно запрашивается более подробная информация о сообщении, которая преобразуется в html-разметку и происходит замена оригинального html-фрагмента (с сокращенным описание) на сгенерированный"
    может, сам не допонял: на сервере генерируется уже готовый html-фрагмент, который росто вставляется в страницу (obj.replaceWith(messages.showMessageDetail(data)))? Или передаеются JSON-данные, которые обрабатываются Javascript'ом на клиенте с помощью тех же closure-шаблонов и вставляются на страницу? Т.е. я не вижу никаких проблем так сделать, но в твоем варианте не так?

    ОтветитьУдалить
  6. Всё так. Вот кода:

    function showDetailMsg () {
    $.getJSON($(this).attr("href"), function (obj) {
    return function (data) {
    obj.replaceWith(messages.showMessageDetail(data));
    }
    }($(this).parent()));
    }

    $.getJSON - это функция из JQuery, она принимает два аргемента: ссылку и функцию обработчик JSON-данных. replaceWith - это тоже функция из JQuery.

    messages.showMessageDetail - это функция, сгенерированная на основе шаблона. Она принимает на вход данные (распарсенный JSON) и на основе этих данных генерит разметку. Т.е. для обмена с сервером используется только JSON.

    ОтветитьУдалить