Smalltalk по-русски
Advertisement

Stephane Ducasse, Universite de Savoie

Adrian Lienhard and Lukas Renggli, University of Bern, Switzerland

Оригинал статьи Seaside: A Flexible Environment for Building Dynamic Web Applications
вышла в журнале IEEE Software September/October 2007: Dynamically Typed Languages.

Введение[]

Странично-ориентированная веб-разработка приводит к разбиению приложения на отдельные скрипты, каждый из которых отвечает за обработку пользовательских запросов и генерацию ответа. Ссылки передают управление от одного скрипта другому. Это влечет за собой жесткое связывание потока выполнения по принципу "go to" переходов, то есть каждой странице приходится знать о том, что будет происходить далее. Несмотря на то, что разработчики программного обеспечения давно считают вредными операторы "go to" [1], они до сих пор присутствуют в современных мейнстрим-фреймворках, препятствуя повторному использованию страниц в различных частях приложения. Другой негативный аспект фреймворков веб-приложений -- это их ограниченная поддержка компоновки одной страницы из нескольких частей. Не имеющая состояния природа клиент-серверного взаимодействия требует передачи текущего состояния между сервером и браузером в обе стороны, что неизбежно приводит к нежелаемому связыванию этих частей приложения.

Новейшие фреймворки веб-приложений пытаются найти решения этих проблем. Продукты JWIG, RIFE, Jakarta Struts и JBoss SEAM моделируют поток выполнения явным образом. Однако большинство из этих подходов плохо интегрируются с остальным кодом, так как требуют либо задавать последовательность страничных переходов во внешних XML-файлах инструкций, либо использовать другой язык программирования. Подход основанный на продолжениях предоставляет механизмы моделирования страничных переходов потока выполнения простым образом в коде приложения [2] [3] [4]. С другой стороны, WebObjects, Ruby on Rails, Google Web Toolkit и .NET предлагают более высокий уровень абстракции за счет составления приложения из компонентов, но они не имеют возможности моделировать поток выполнения на высоком уровне. В любом из этих подходов комбинирование нескольких одновременных потоков внутри одной страницы затруднительно.

В этой статье, после небольшого вступления о ключевых проблемах современной веб-разработки, мы расскажем о Seaside (www.seaside.st) -- высоко-динамичном фреймворке для разработки веб-приложений на языке Smalltalk. Используя динамические свойства Smalltalk и его возможности рефлексии, Seaside предлагает уникальный способ получать одновременно несколько активных потоков выполнения на одной странице. Более того, он снимает необходимость рекомпилировать и перезапускать сервер после каждого внесения изменений. Веб-разработчики могут отлаживать и обновлять приложения налету, что приводит к значительному сокращению времени на разработку.

Проблемы веб-разработки[]

Чтобы поднять веб-разработку до уровня разработки настольных приложений, среда веб-разработки или язык должны обладать следующими ключевыми свойствами:

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

Во-вторых, уметь генерировать валидный XHTML. Создание валидного XHTML-кода и подключение его к логике приложения является трудной задачей, особенно если XHTML-код и логика приложения разрабатываются в различных средах. Генерации XHTML по шаблонам (templatebased XHTML generation) не достает мощности и выразительности базового языка (host language). Цельная интеграция инструментов разработки часто отсутствует.

В-третьих, иметь возможность горячей отладки (hot debugging). Эффективное обнаружение ошибок -- главнейшая проблема веб-разработчиков. Веб-приложения становятся все сложнее и с учетом особенностей клиент-серверного взаимодействия все это приводит к затруднению отладки. Основная причина состоит в том, что поддержка развитой системы отладки зачастую просто отсутствует в современных популярных подходах. Возможность горячей отладки исключительных ситуаций и использование точек останова могло бы значительно увеличить продуктивность разработки. В этом случае после исправления ошибки сессия может быть продолжена с остановленного места. И не нужно постоянно перезапускать приложение и переходить к той части, где возникает ошибка.

В-четвертых, поддерживать горячую рекомпиляцию. Рекомпиляция методов налету (рекомпиляция метода работающего приложения) важна тем, что дает возможность разработчикам обновлять код без перезапуска сессии. Это особенно полезно для веб-разработки, так как позволяет избежать больших затрат времени на постоянный перезапуск приложения.

Для более детального ознакомления с этими проблемами обратитесь к нашей предыдущей работе.[5]

Возможности рефлексии в Smalltalk[]

Seaside добавляет уровень абстракции над асинхронным протоколом клиент-серверного взаимодействия, чтобы предоставить ощущение разработки обычного настольного приложения. Эта возможность базируется на свойствах рефлексии Smalltalk. Smalltalk -- однородный язык, в котором систематически применяются простые принципы. В Smalltalk'е все является объектом, экземпляром класса. Объекты общаются исключительно через посылку сообщений. В дополнение, Smalltalk поддерживает замыкания -- анонимные функции, которые можно передавать, сохранять в переменных или выполнять отложено.

Smalltalk написан на самом себе и предоставляет мощные возможности рефлексии, как структурные, так и поведенческие [6]. В этом небольшом обзоре мы сосредоточим внимание на тех возможностях, которые позволили создать мощный фреймворк веб-разработки включающий множественную композицию потока выполнения, горячие отладку и рекомпиляцию. Возможности рефлексии Smalltalk схожи с используемыми в Common Lisp Object System (CLOS)[7], но Smalltalk предоставляет еще и полный доступ к стеку выполнения. Эти возможности следующие:

  • Изменение классов и шейпов. Объекты являются экземплярами классов. При изменении класса (например при добавлении, изменении или удалении переменных экземпляра) Smalltalk автоматически мигрирует все созданные экземпляры этого класса.
  • Горячая рекомпиляция. Разработчики могут определять и изменять методы налету. Выполняемые на момент изменения методы заканчивают работу по старому определению.
  • Материализация стека. В дополнение к self и super Smalltalk имеет третью псевдопеременную thisContext, которая предоставляет по требованию доступ к стеку выполнения. Для эффективности Smalltalk материализует стек (то есть представляет его в виде объекта) только в момент использования псевдопеременной. Объект тесно связан со представляемым стеком. Любое изменение сделанное в объекте отражается на стеке выполнения и разработчики могут управлять стеком для изменения хода выполнения программы.

Основные свойства Seaside[]

SFEBDWA

Рис. 1: Приложение to-do, построенное из трех различных Seaside компонентов.

Код Seaside открыт и множество приложений начали его использовать со времени первого релиза в 2002 г.. Изначально написанный Avi Bryant'ом (на данный момент он является консультантом по разработке и владельцем dabbledb.com), Seaside находится в стадии активной разработки растущим сообществом пользователей, включая третьего автора этой статьи. Здесь мы обсудим ключевые свойства Seaside и то, как они отвечают на вопросы веб-разработки обозначенные ранее. Из-за ограниченного объема статьи в рассмотрение не включен пакет улучшенной поддержки AJAX (Asynchronous JavaScript and XML), имеющийся в Seaside (см. http://scriptaculous.seasidehosting.st). В оставшейся части статьи мы приведем пример простого приложения позволяющего пользователю управлять списком to-do заданий. На Рис. 1 изображено главное окно, состоящее из трех компонентов. Главный компонент – TodoApplication - состоит из двух подкомпонентов. Один – TodoFilterView – реализует фильтры, применяемые к списку to-do элементов другого компонента – TodoListView. To-do элемент состоит из заголовка, даты и статуса. Пользователь может изменять статус элемента выбором соответствующего checkbox.

Компоненты являются основными блоками, из которых строится Seaside приложение. Компонент – это экземпляр определенного пользователем подкласса Component, который задает внешний вид и поведение части страницы. Для каждого запроса сессия позволяет компоненту выполнять обратные вызовы (action callbacks) и отрисовывать свое текущее визуальное представление. Через обратные вызовы компоненты могут изменять поток выполнения, например, временно передавать управление другому компоненту. Рассмотрим три ключевых свойства Seaside, которые появились благодаря динамическим возможностям Smalltalk:

  • Программная генерация XHTML. Следуя принципам Smalltalk, представлять все в виде объекта, Seaside предоставляет абсолютно иной подход к генерации XHTML, нежели это предлагается в системах шаблонов. Seaside создает XHTML на объектном уровне через полноценную посылку сообщений. Используя замыкания (block closures) Seaside связывает обратные вызовы с компонетами.
  • Несколько потоков выполнения одновременно. Каждый компонент может определить собственный поток выполнения независимо от других компонентов отображенных на той же странице. Это позволяет реализовывать бизнес логику распределенную по нескольким страницам одним куском кода. Такая возможность появилась благодаря идее продолжений, используя которые Smalltalk может управлять стеком выполнения.
  • Горячие отладка, редактирование и рекомпиляция кода. Seaside имеет отличную поддержку процесса разработки. Особенно следует отметить цельную интеграцию отладчика с кодом веб-приложения. Также Seaside позволяет осуществлять горячую рекомпиляцию кода. Работу с исключениями происходит как с объектами первого класса, которые можно инспектировать и возобновлять.

Специализированный язык предметной области для генерации XHTML[]

SFEBDWA

Рис. 2: Пример отрисовки в TodoApplicaton: (a) реализация (b) полученный код XHTML.

Seaside не хранит фрагменты XHTML кода во внешних файлах и не использует систему шаблонов. Вместо этого высокоуровневый интерфейс избавляет разработчика от проверки правильности вложения тегов и атрибутов. При помощи синтаксиса блоков в Smalltalk определяется специализированный язык предметной области (domain-specific language (DSL)) для генерации XHTML. Таким образом, через этот специальный язык, определенный в синтаксисе Smalltalk, Seaside позволяет разработчикам программно создавать XHTML код.

Отрисовка[]

Когда Seaside отвечает на запрос, у каждого видимого компонента вызывается hook-метод renderContentOn:. Таким образом, компонент может отрисовывать собственное представление на передаваемом в метод экземпляре RendererCanvas, по соглашению называющимся html. (В Smalltalk'е чтение значений атрибутов и локальных переменных осуществляется использованием соответствуюшего имени в выражении. Запись в переменные производится через конструкцию :=. Cообщения передаются в виде receiver methodName1: arg1 name2: arg2, что эквивалентно Java синтаксису: receiver.methodName1Name2(arg1, arg2).) На Рис 2. показан пример отрисовки компонента TodoApplication. Канва отрисовки возвращает экземпляры XHTML-тегов называемых brushes. Например, в коде html div будет возвращен brush для div-тега. Brushes определяют интерфейс задания атрибутов и содержания тегов. Seaside передает содержание (вложенные теги или строки) сообщению width:. Замыкания задают правильную вложенность тегов. Забыть поставить закрывающий тег невозможно: компилятор сообщит об ошибке в исходном коде. Строки, как на Рис. 2, могут передаваться непосредственно канве отрисовщика. Seaside гарантирует валидность объявлений тегов, так как атрибуты устанавливаются через методы-аксессоры.

Обратные вызовы[]

Итак, мы обсудили как компоненты отрисовывают себя. Но компоненты также могут реагировать на действия вызываемые пользователем через обратные вызовы. Seaside предоставляет возможность использовать замыкания для определения действий как по нажатию ссылок и кнопок, так и элементов форм: полей ввода, чекбоксов и списков выбора. Выполнение замыканий откладывается на момент вызова действия пользователем. Кнопки и ссылки используют замыкания без аргументов. Остальные поля формы используют замыкания с одним аргументом: текущим значением элемента на момент вызова действия. Следующий код из класса TodoListView отображает чекбокс элемента:

html checkbox
    submitOnClick;
    value: anItem done;
    callback: [:value |
        anItem done: value].
    html span: anItem title

Выражение html checkbox возвращает brush чекбокса, который автоматически подтверждает (submit) форму при нажатии. Далее он устанавливает выбранное булевское значение в зависимости от текущего статуса элемента. И, наконец, обратный вызов, который происходит при нажатии чекбокса пользователем, ожидает один булевский аргумент -- новый статус элемента.

Обратные вызовы предоставляют высокий уровень абстракции над низкоуровневым HTTP протоколом. Разработчики приложений освобождены от ручного извлечения параметров из HTTP запросов или валидации и синтаксического разбора строк каждого запроса. Вместо этого Seaside автоматически вызывает нужные обработчики с реальными объектами в качестве параметров.

Поток выполнения[]

SFEBDWA

Рис. 3: Использование TodoFilterView>>due для установки диапазона дат фильтра.

Как только пользователь запрашивает новую страницу нажатием на ссылку или кнопку, Seaside инициирует обратный вызов компонента. В обратном вызове компонент может изменить состояние приложения, как мы обсуждали в предыдущей части. Более того, обратные вызовы могут использоваться для изменения потока выполнения, в то время как остальные компоненты страницы остаются неизменными. Каждый компонент может иметь собственный поток выполнения описывающий последовательность, по которой он может временно заменяться на другие компоненты. Потоки выполнения в Seaside могут быть нелинейными. Разработчики вправе использовать управляющие выражения (control statements), циклы, вызовы методов и код предметной области отображения компонентов. И все это описывается цельным исходным кодом -- никаких конечных автоматов.

Пользователи могут использовать метод TodoFilterView>>due для установки фильтра по дате. Метод TodoFilterView>>due последовательно отображает два компонента календаря и позволяет пользователю выбрать начальную и конечную даты фильтра элементов. Метод сначала создает экземпляр календаря и вызывает его для выбора начальной даты. После выбора эта дата возвращается и сохраняется в локальной переменной. Дата является объектом и разработчик может использовать ее во втором компоненте календаря для проверки, что конечная дата позднее начальной. В последней строчке метода замыкание, ссылающееся на начальную и конечную даты, устанавливает фильтр. Это замыкание используется фильтром to-do элементов, см. Рис. 3.

На Рис. 3 видно, что нет необходимости кодировать состояния в строки и передавать информацию из одной страницы в другую. Для отслеживания состояния нескольких этапов выполнения используются временные переменные. Управление потоком выполнения в Seaside производится взаимодействием методов call: и answer:, как показано на Рис. 4. Выделенный фрагмент Calendar в методе due является экземпляром Seaside компонента. Seaside запускает метод select: из обратного вызова когда пользователь подтверждает выбор даты.

SFEBDWA

Рис. 4: Базовые элементы потока выполнения в Seaside: call: и answer:.

Вызов[]

Один компонент может передавать управление другому таким образом, что вызванный компонент временно заменяет вызвавший. Для этого разработчик использует метод call: старого компонента с новым в качестве аргумента. На Рис. 4 вызов метода call: с аргументом Calendar поместит календарь поверх компонента Filter и передаст ему управление. Остальные компоненты страницы останутся рабочими и могут использоваться независимо от нового компонента.

Ответ[]

Вызванный компонент может возвращать управление используя метод answer:. При возврате компонент может вернуть объект вызывающему компоненту. На Рис. 4. выражение self answer: aDate в объекте календаря возвращает объект даты компоненту фильтра.

Составление компонентов[]

В основном пользовательский интерфейс строится из различных видимых на одном экране частей как в нашем примере. В Seaside эти части реализованы как компоненты, которые могут быть составлены из других компонентов. Следующий код показывает как в приложении компонент верхнего уровня соединяет фильтр и todo-список. Seaside хранит два компонента в переменных экземпляра TodoApplication и помещает их для отрисовки в соответствующем порядке внутри XHTML div-элементов. Над двумя компонентами отображается заголовок.

TodoApplication>>renderContentOn: html
    self renderTitleOn: html.
    html div
        class: 'filter’;
        with: filterView.
    html div
        class: 'list’;
        with: listView
SFEBDWA

Рис. 5: Несколько потоков на одной странице интерфейса приложения to-do. Пользователь может свободно взаимодействовать с различными компонентами

Ранее мы рассматривали поток выполнения компонента фильтра, и выбора начальной и конечной дат. Компонент списка тоже определяет несколько потоков выполнения, например, диалоговые окна подтверждения удаления элемента, добавления и редактирования элементов.

На Рис. 5 изображен процесс работы в веб-браузере в виде четырех состояний интерфейса приложения to-do. Сначала пользователь определяет фильтр для элементов. Когда он собирается удалить первый элемент и нажимает на ссылку remove, появляется диалоговое окно подтверждения. Сейчас одновременно активными являются два потока и пользователь может продолжить работу с любым из них. В этом примере пользователь выбирает начальную дату и сразу видит ограничения для конечной даты.

Аспекты реализации[]

Seaside унаследовал большинство динамических возможностей от мощных рефлексивных свойств Smalltalk. В этой части мы обсудим основные аспекты реализации, позволяющие существовать поддержке отладки и протоколу вызов-ответ (call-answer protocol) в Seaside.

Горячие отладка и рекомпиляция[]

Большинство современных веб-фреймворков предоставляют слабую поддержку отладки веб-приложений. Обычно, в результате возникновения исключительной ситуации выводится только номер строки и протокол стека. Нахождение и исправление ошибок утомительно и зачастую требует применения журналов для получения дополнительных данных.

Seaside перенял Smalltalk-философию инкрементного программирования в интерактивной среде. Разработчики могут добавлять или редактировать код во время работы веб-приложения. Это сильно упрощает разработку. Рис. 6 показывает пример процесса отладки в Seaside: исправление ошибки "Индекс за пределами массива" без перезапуска приложения.

Исключения Smalltalk являются объектами первого класса, ссылающимися на исходный контекст выполнения, из которого они были вызваны. Исключения не "раскручивают" стек до момента обработки. Таким образом, Seaside может сохранять вызванное ранее исключение в переменной экземпляра, а затем открывать отладчик для этого исключения по желанию разработчика. Разработчик может исправить проблему во встроенной среде разработки и продолжить выполнение приложения с остановленного места. Это свойство делает отладку веб-приложения очень мощным инструментом. В рекомпиляции и перезапуске веб-сервера нет необходимости. Разработчик также может вернуться назад к проблемной странице и проверить правильно ли была исправлена ошибка и далее продолжить сессию тестирования.

SFEBDWA

Рис. 6: Отладка приложения Seaside: (a) при возникновении необработанного исключения браузер отображает стек выполнения со ссылкой debug; (b) разработчик кликает по ссылке для запуска отладчика в среде разработки чтобы просматривать переменные и изменять код налету; (c) отладчик отображает рекомпилированный метод, на котором браузер ожидает ответа сервера; (d) после нажатия proceed Seaside возобновляет обработку вызвавшего ошибку запроса и результирующая страница отображается в браузере.

Материализация стека в механизме вызов-ответ[]

Seaside реализует механизм вызов-ответ используя продолжения, которые являются неизменяемыми представлениями стека выполнения на конкретный момент времени. Проще говоря, продолжения -- это остановленный процесс, который можно возобновить из сохраненного состояния несколько раз. Следующий отрывок кода из фреймворка Seaside демонстрирует немного упрощенную реализацию механизма вызова-ответа:

Component>>call: aComponent
    ^ Continuation
        currentDo: [:continuation |
            self replaceWith: aComponent.
            aComponent
                onAnswer: [:result |
                    aComponent replaceWith: self.
                    continuation value: result].
            WARenderNotification raiseSignal]

При вызове компонента -- при помощи метода call: с параметром экземпляра компонента -- происходит захват продолжения и передача его в замыкание в качестве переменной. Это замыкание заменяет текущий компонент (self) на переданный через параметр aComponent и назначает обработчик события вызываемому компоненту. Этот обработчик будет вызван при посылке сообщения answer:. Последняя строка вызывает исключение, по которому Seaside останавливает поток выполнения и перерисовывает страницу с новым компонентом aComponent. Вызов call: у компонента не возвратит немедленно сообщение answer: вызвавшему компоненту после завершения метода.

Позже, во время последующего HTTP запроса, компонент aComponent может послать сообщение answer: и Seaside выполнит замыкание обработчика ответа. Выполнение сообщения заменит вызванный компонент обратно на исходный и возобновит продолжение с аргументом ответа, который был передан обработчику. Это позволяет продолжению вернуться к точке, на которой оно было захвачено (то есть, к началу метода call:), и возвратить результат вызвавшему компоненту.

Благодаря рефлексивным возможностям Smalltalk, реализация продолжений занимает меньше 30 строчек кода. Продолжение захватывает текущий стек выполнения, получает доступ к активному контексту при помощи псевдопеременной thisContext и продолжает выполнение копируя все фреймы.

При последующем вызове продолжения Seaside обнуляет активный стек, восстанавливает и активизирует захваченный ранее. На последнем шаге возвращается значение, переданное в продолжение как аргумент, делая возможным вернуть ответ выражению call:.

Seaside хранит все захваченные продолжения в кэше, таким образом использование кнопки Назад в браузере не составляет проблемы. Например, когда задается фильтр в TodoApplication, пользователь может изменить начальную дату после ее установки нажатием кнопки Назад до момента появления диалога выбора начальной даты. Из-за возможности вызывать продолжения несколько раз, the send message to the call: method returns a second time, но теперь уже с другой начальной датой и поток продолжит выполнение с этого момента.

Заключение[]

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

Seaside на протяжении последних пяти лет успешно используется многими коммерческими и открытыми проектами веб-приложений. Активное сообщество постоянно усовершенствует Seaside, например, при помощи интерфейса стыковки с AJAX. Недавно Seaside был адаптирован под другие Smallltalk платформы, среди которых и коммерческие фирмы, такие как Cincom Smalltalk и GemStone.

Ссылки[]

  1. E.W. Dijkstra, “Go To Statement Considered Harmful” Comm. ACM, vol. 11, no. 3, 1968, pp. 147–148.
  2. C. Queinnec, “The Influence of Browsers on Evaluators or, Continuations to Program Web Servers,” Proc. ACM SIGPLAN Int’l Conf. Functional Programming, ACM Press, 2000, pp. 23–33.
  3. P. Graunke et al., “Programming the Web with High-Level Programming Languages,” Proc. European Symp. Programming (ESOP 01), LNCS 2028, Springer, 2001, pp. 122–136.
  4. C. Queinnec, “Inverting Back the Inversion of Control or, Continuations Versus Page-Centric Programming,” SIGPLAN Notices, vol. 38, no. 2, 2003, pp. 57–64.
  5. S. Ducasse, A. Lienhard, and L. Renggli, “Seaside—A Multiple Control Flow Web Application Framework,” Proc. 12th Int’l Smalltalk Conf. (ISC 04), European Smalltalk User Group, 2004, pp. 231–257; http://www.iam.unibe.ch/~scg/Archive/Papers/Duca04eSeaside.pdf.
  6. F. Rivard, “Smalltalk: A Reflective Language,” Proc. Reflection Conf., Xerox, 1996, pp. 21–38; http://www2.parc.com/csl/groups/sda/projects/reflection96/docs/rivard/rivard.html.
  7. G. Kiczales, J. des Rivières, and D.G. Bobrow, The Art of the Metaobject Protocol, MIT Press, 1991.

Перевод Daniq и помощь Eugenius 09:52, 29 ноября 2007 (UTC)

Advertisement