Если вы пишете SDK для внутренних нужд единственного проекта, то многие вещи упрощаются: вопросы обратной совместимости стоят не так остро, вместо написания подробной документации можно лично ответить на вопросы коллеги, а обнаруженные ошибки относительно легко включить в проект. Если SDK делается для всех проектов крупной компании, то проблем становится намного больше. Но если ваш SDK предназначен для сторонних разработчиков по всему миру, то без таких вещей, как хорошая документация и автотесты, обойтись просто нельзя. Именно к последним можно отнести наш APS JavaScript SDK, и в этой статье я хочу рассказать о том, как он устроен и как мы стараемся максимально облегчить жизнь сторонним разработчикам, которые используют или будут использовать наш SDK.
Слайды для тех кто не любит читать многобукв.
И для самых нетерпеливых: что у нас получилось и наша песочница.
Если вам интересны только технические детали и код, то советую начинать читать сразу со второй части.
Зачем
Собственный SDK нам понадобился не для того, чтобы придумать новый вид транспортного средства, изображенного на картинке для привлечения внимания.
Нам он был нужен, чтобы поддержать систему плагинов в рамках OSS системы Parallels Automation и панели управления
Чтобы получить четкое представление о Parallels Automation надо понимать, что такое OSS. Это класс систем эксплуатационной поддержки, управляющих сетевой инфраструктурой, учетом и выделением ресурсов. Основные потребители — телекомы и крупные хостеры. Для них важно не только управлять инфраструктурой как таковой, но и продавать сервисы своим клиентам. От продажи сервиса телекомы получают неплохие доходы. Представим телеком с 10 млн пользователей. Даже, если 1% пользователей купят сервис стоимостью в 1 доллар, его доход составит 100000$.
APS
Разработанный Parallels стандарт, с помощью которого возможно проинтегрировать сервисы в экосистему Parallels Automation. Последняя версия стандарта — APS 2, исправляющая ошибки версии 1.x.
APS 1.x
APS 1.x позволял интегрировать приложения, описав бизнес-модель интегрируемых объектов и декларативно в мета файле описать UI. Таким образом UI генерировался автоматически из бизнес-модели, что отрицательно сказывалось на UX. Сценарий-ориентированный пользовательский интерфейс было просто невозможно сделать. Поэтому был создан APS 2.x.
APS 2.x
В APS 2 бизнес модель была полностью отделена от презентационной логики. Бизнес-объекты стали доступны по REST.
После жарких дискуссий и дизайн-ревью мы остановились на thin server архитектуре, в которой UI стал описываться в HTML с очень активным использованием JavaScript. Это позволило создавать сценарий-ориентированный интерфейс без каких-либо существенных ограничений.
Ограничения
Хотя ограничения все же были.
Parallels Automation в отличие от, например, Parallels Desktop for Mac, нельзя просто поставить из коробки. Как правило, при его продаже заключаются серьезные контракты и бренд Parallels скрывается в пользу купившего хостера или телекома. Поэтому UI в APS должен был быть брендируемым. Как было сказано раннее, уровень разработчиков может серьезно различаться. Продукт коммерческий и copyleft ограничительные лицензии нам не подходили.
APS JS SDK
В итоге мы пришли к идее создания собственного JavaScript SDK, позволяющего абстрагировать разработчика от разметки. Абстрагирование служит для нас и конечных разработчиков дополнительным бонусом, т.к. legacy разметка, строго говоря, не является семантической.
Также мы сделали изначальный упор на документацию, т.к. JS SDK используется не только внутри компании.
Как
Как мы создавали свой фреймворк и каких правил придерживались?
- использовали существующие фреймворки;
- не забывали о продуманном API;
- придерживались Test Driven Development — изначально создавали автоматизированные тесты;
- работали над документацией;
- и над песочницей, в которой можно попробовать фреймворк в действии.
Использование существующих фреймворков
Создавать и поддерживать полностью свой JavaScript фреймворк очень трудоемкая задача. Поэтому решили взять за основу для построения своего JS SDK какой-либо из уже существующих фреймворков. Он должен был удовлетворять нескольким требованиям:
- т.к. у нас была уже существующая кодовая база верстки, которая могла в будущем меняться, то с элементами UI хотелось бы работать как с цельными объектами — виджетами, которые бы базировались на HTML шаблонах максимально изолированных от JS логики;
- задачи наших пользователей очень разные, поэтому фреймворк должен был быть достаточно гибок, чтобы поддерживать самые разнообразные варианты UX;
- мы не хотели заставлять разработчиков учить какие-то сложные проприетарные технологии, поэтому базовый фреймворк должен был быть максимально распространенным;
- как уже было сказано, пользователи могут кастомизировать дизайн своих приложений, поэтому фреймворк должен был позволять легко изменять внешний вид виджетов;
- кроме того хотелось найти целостный фреймворк, на основе которого можно было бы сразу начать создавать свои виджеты;
- ну и, конечно, все любят красивые и динамические виджеты, поэтому фреймворк должен был их содержать или, хотя бы, позволять создавать.
В качестве вариантов мы рассмотрели:
- ExtJS
- JQuery UI
- loader(RequireJS/CommonJS/...) + MV* Framework (KnockoutJS/AngularJS/...)
- Dojo Toolkit
ExtJS
Мощный, популярный, активно развивающийся фреймворк с большим количеством классных виджетов и удобной моделью связывания данных. Но все-таки это вещь, которая создана для того, чтобы ее брали и использовали. Это не фундамент для создания своего фреймворка. Его виджеты сложно кастомизируются, а кастомизация дизайна добавлена только в последних версиях. Но главным его минусом для нас, конечно, стали очень высокие лицензионные отчисления.
JQueryUI
Еще более популярный фреймворк, с низким порогом вхождения и отсутствием лицензионных отчислений. Виджетов, входящих в официальную поставку, маловато, но это не проблема, т. к. существует огромное количество виджетов от сторонних разработчиков. К сожалению, на момент исследования, API фреймворка еще не совсем устоялся и часто менялся. Кроме того, код виджетов не отделен от верстки.
loader + MV* Framework
На момент исследования будущее angular было туманным, knockout решал очень ограниченные задачи. Но есть заказчики, договора и жесткие сроки, поэтому, чтобы как можно скорее приступить к конечной разработке, нам нужен был целостный базовый фреймворк.
Dojo Toolkit
Мощный фреймворк с модульной структурой на базе AMD. Богатые, многочисленные легко кастомизируемые виджеты с поддержкой шаблонов. Многолетняя поддержка Deferred и Promise. Отсутствие лицензионный отчислений. Но в силе Dojo и его слабость. У него сравнительно большой порог вхождения из-за чего он слабо распространен. Кроме того, он не так давно стал активно двигаться в сторону мобильных устройств.
Но мы все же решили использовать Dojo:
- специфика системы не предполагает активного использования на мобильных устройствах (сейчас довольно многое изменилось, но, с другой стороны, соответствующие проблемы фреймворка мы решили);
- порог вхождения мы попытались снизить, о чем расскажем ниже.
Более подробно о достоинствах Dojo рассказано в переводе статьи Дэвида Уолша (David Walsh). Там же в комментариях можно почитать сравнения Dojo и ExtJS.
Продуманный API
Основа любого фреймворка — продуманный API.
AMD
Фундаментом нашего JS SDK выступают AMD модули которые представляют собой визуальные компоненты — виджеты, компоненты для работы с данными и различные вспомогательные утилиты. Кроме того, формат AMD позволяет включать в проект любые сторонние библиотеки поддерживающие данный формат упаковки. Тем самым мы не ограничиваем разработчиков в их предпочтениях.
Виджеты
Визуальные компоненты — виджеты — логически отделены от HTML представления. Они могут динамически менять значения своих свойств и наследоваться другу от друга. Для удобства разработчиков предусмотрены 3 способа описания виджетов. Причем на одном экране совершенно не обязательно использовать только один, способы можно свободно комбинировать.
Включение виджетов друг в друга
Виджеты могут включать друг друга на уровне шаблона. Например, виджет aps/Slider состоит из dijit/form/HorizontalSlider горизонтальной прокрутки и aps/TextBox:
Кроме описанного включения виджетов в шаблон, можно динамически или при описании экрана добавлять дочерние виджеты. Например, добавлять инпуты в форму.
Декларативное объявление
Наш фреймворк поддерживает 3 способа описания экранов. Первый декларативный. В нем расположение виджетов и их свойства задаются в виде HTML верстки с заданными специальными атрибутами. Иерархия виджетов задается иерархией HTML Element-ов. После загрузки страницы вызывается парсер, который создает виджеты. Парсер можно вызвать на всей странице или на отдельной ее части.
require(["dojo/parser", "aps/ready!"], function(parser){
parser.parse();
});
<fieldset data-dojo-type=”aps/FieldSet”
title=“I am aps/FieldSet”>
<input
type=”checkbox”
data-dojo-type="aps/CheckBox"
data-dojo-props="
label: 'CheckBox',
description: 'I am aps/CheckBox'"
>
</fieldset>
Программное объявление виджетов
Второй способ объявления виджетов — программный. С помощью require подключаются необходимые модули и путем вызова конструктора с параметрами создаются необходимые виджеты. Иерархия виджетов задается добавлением дочерних виджетов с помощью метода addChild.
require([
"aps/FieldSet",
"aps/CheckBox",
"aps/ready!"
], function (FieldSet, CheckBox) {
var fs = new FieldSet({
title : “I am aps / FieldSet”
}, "idDiv");
fs.addChild(new CheckBox({
label : “CheckBox”,
description : “I am aps / CheckBox”
});
fs.startup();
});
С помощью загрузчика
Третий способ описания экрана — с помощью загрузчика. Он разработан нами, и его мы и рекомендуем. Расположение виджетов и их свойства задаются в виде JSON-подобной структуры. Каждый виджет описывается тройкой: имя модуля, параметры конструктора и массив дочерних элементов. Второй и третий элемент не обязательны. Кроме виджетов загрузчик также может создавать HTML-теги.
- не тратится время на парсинг HTML (первый способ);
- в программном объявлении (второй способ) нарушается логический порядок следования — сначала создается дочерний виджет, а уже потом — родительский (контейнер), что неудобно и плохо читаемо.
require([
"aps/load",
"aps/ready!"
], function (load) {
load(["aps/FieldSet", {
title : "I am aps / FieldSet"
}, [
["aps / CheckBox", {
label : "CheckBox",
description : "I am aps / CheckBox"
}
]]]);
});
Источники данных
В качестве источника данных для виджета могут использоваться два модуля: Store для удаленного источника и Memory для локального.
Для запросов к серверу aps/Store использует расширенную версию языка запросов RQL, которая также поддерживается aps/Memory.
require([
"aps/Store",
"aps/Grid",
"aps/ready!"
], function (load) {
var store = new Store({
target : "http://localhost/resources"
});
var grid = new Grid({
columns: layoutSimpleGrid,
store: store
}, "gridDiv");
});
Связывание данных
Для двухсторонней связи данных и виджетов используются модули семейства Model и метод at. Если вы хотите связать данные и виджеты, то в качестве параметра конструктора вы указываете метод at с переданным ему именем модели и именем свойства объекта модели, на который производится мапинг. В дополнение к возможности мапинга модель поддерживает возможность слежения за значением своих свойств с помощью метода watch.
require([
"aps/TextBox",
"dojox/mvc/getStateful",
"dojox/mvc/at",
"aps/ready!"
], function (TextBox, getStateful, at) {
model = getStateful({val : "Hello, world!"});
new TextBox({value : at("model", "val")}, "divTB").startup();
});
Единые для всех модулей правила именования свойств и методов
Правила наименования просты и стандартны:
- имена приватных/защищенных свойств и методов начинаются с символа подчеркивания «_»;
- имена методов, функций и свойств начинаются с маленькой буквы, имена классов — с большой; если класс является миксином, то перед большой буквой ставится знак подчеркивания.
Единые для всех модулей способы взаимодействия между собой и внешним миром
- данные и виджеты связываются через модули Model и Store;
- слежение за состоянием виджета осуществляется с помощью метода watch, виджеты не генерируют никаких специальных событий, вся работа со свойствами выполняется только через методы set и get.
Пример взаимодействия с виджетом
В качестве примера взаимодействия с виджетами рассмотрим выделение строк в виджете Grid.
В простейшем случае для создания таблицы с возможностью выделения строк достаточно указать структуру колонок, режим выбора строк (можно ли выбрать одну или несколько строк одновременно) и источник данных.
В случае выделения строки ее id добавляется в специальный массив хранящийся в свойстве selectionArray. При снятии выделения он оттуда удаляется. Таким образом для слежения за выделением строк достаточно добавить callback метод watchElements и он автоматически вызовется при изменении набора выделенных строк.
Связь двухсторонняя. Поэтому чтобы визуально выделить строку в таблице нам достаточно добавить ее id в selectionArray.
var grid = new Grid({ //Грид с возможностью выбора строк
columns : layoutSimpleGrid,
selectionMode : "single",
store : store
});
grid.get("selectionArray") //Отследим события выбора строк
.watchElements(function (index, removals, adds) {
alert(adds);
});
//выделим строку программно
grid.get("selectionArray").push("ea7865aa");
Автоматизированные тесты
Тесты важны, а автоматизированные тесты — необходимы. Они позволяют держать код в стабильном состоянии. И лучше их разрабатывать с самого начала. Но перед разработкой тестов, нужно подумать о билд-системе.
Билд-система
Билд запускает Jenkins — стандартный build-scheduler. На сборочной машине развернут Maven, node.js и headless браузер phantomjs. Наша сборка встроена в maven жизненный цикл сборки и включает в себя проверку кода JSHint, компиляцию clojure compiler, упаковку, включая создание слоев (или слияние файлов), тестирование и развертывание сгенерированного архива в систему управления артефактами nexus.
Автоматизированное тестирование
Тесты могут определить всю дальнейшую судьбу фреймворка, т.к. стабильность — это залог развития.
Чего мы придерживались при создании автотестов:
- можно запустить без инфраструктуры, т.к. тесты могут служить примером для разработчиков;
- т.к. фреймворк модульный, то должен существовать строго отдельный тест для каждого компонента;
- тесты должны располагаться рядом с самим фреймворком, прямо в дистрибутиве, чтобы можно было их быстро прогнать и посмотреть как пользоваться фреймворком;
- и, наконец, чтобы разобраться в тестах — нужно строго придерживаться соглашений об именовании; в нашем случае директории с тестами начинаются с test, все остальное — для ручного тестирования.
Unit-тесты должны запускаться всегда при сборке. Часто бывает, что новая функциональность требуется незамедлительно. При этом в процессе разработки выясняется, что часть тестов опциональна «на первый взгляд» и их отключение «никому не помешает». Тест отключается. Затем другой. И со временем, количество несоответствий оказывается таким, что запускать unit-тесты становится бессмысленным.
Сборка отменяется, если хотя бы один тест не проходит. Отключать тесты нельзя.
Выбор инструментов
Мы выбрали QUnit и собственную систему, запускающую phantomjs, IE, Firefox и Safari в виртуальных машинах. Предварительно мы рассмотрели:
- TestSwarm;
- Buster.js;
- Dojoh Robot;
- External Farms (browsershots, browserling,etc);
- Selenium.
Почему QUnit
Главное для нас — простота встраивания. Один язык программирования JavaScript для написания и кода, и тестов, что очень удобно. QUnit прост и распространен — в случае необходимости это позволяет легко аутсорсить тестирование.
Почему не TestSwarm
TestSwarm сильно завязан на инфраструктуру. Но главное — билд помечается после сборки, а не во время, что противоречит принятой у нас методологии.
Почему не Buster.js
Фреймворк рассчитан на запуск модулей на сервере и на клиенте — у нас такой задачи не стоит. Кроме того, он не умеет самостоятельно запускать браузеры.
Почему не Dojox Robot
Фреймворк входящий в Dojo Toolkit. К сожалению, несмотря на это, заброшен и значительно устарел.
Почему не внешние фермы
Очень не хотелось завязываться на внешние системы, к тому же, это потребовало бы сложной настройки VPN-доступа к нашем сборочным машинам. Плюс ко всему их использование достаточно дорого.
Почему не Selenium
Громоздкий, требует настройки инфраструктуры.
Итого
У нас получился гибрид Selenium Server, который по команде снаружи запускает браузер на виртуальной машине. Все тесты запускаются в одной странице, при этом результаты могут быть доставлены сборщику или показаны в браузере, если тесты были запущены вручную.
Компоненты тестовой инфраструктуры
Диаграмма показывает компоненты тестовой инфраструктуры. На сборочном сервере параллельно запускается phantom.js (для получения быстрого результата) и тесты на различных браузерах работающих в различных ОС на виртуальных машинах.
Документация
Даже самый продуманный API требует удобной документации. Документация к APS JS SDK состоит из двух частей: API и Reference Guide.
API
Раздел API содержит краткое описание всех доступных модулей и их интерфейсов. Этот раздел в первую очередь ориентирован на разработчиков, которые уже разобрались в SDK и хотят быстро уточнить наличие и правильное написание того или иного метода или свойства. Эта информация генерируется на основании комментариев в коде во время сборки кода.
Reference Guide
В описании каждого модуля есть ссылка на соответствующую страницу второй части документации — Reference Guide. Этот раздел содержит расширенное (по сравнению с API) описание модулей APS SDK и их основных свойств и методов. Он регулярно обновляется и дополняется в соответствии с проблемами, с которыми сталкиваются пользователи нашего SDK. Также каждая статья Reference Guide содержит примеры использования модулей.
Примеры
Примеры использования и создания модуля даны для всех трех способов объявления виджетов: декларативного, программного и с помощью загрузчика. Но примеры, которые нельзя запустить и попробовать, малоинтересны. Страница каждого модуля в Reference Guide содержит ссылку на страницу с автотестами. Пользователь может сам посмотреть, как выполняются тесты, а заглянув в исходный код, — увидеть примеры демонстрирующие весь функционал виджета.
Песочница
Но всегда ведь хочется попробовать самому. И, желательно, быстро и просто. Для этого большинство примеров можно запустить в специальной песочнице.
При создании нашей песочницы мы вдохновлялись небезызвестным проектом jsfiddle, поэтому она называется apsfiddle. Как и в нем, пользователю доступные 4 области: редактор HTML, редактор CSS, редактор JS и результат выполнения кода (его можно посмотреть как в отведенной области экрана, так и в новой вкладке). Для создания редакторов кода мы использовали проект CodeMirror.
Кроме ручного ввода текста, в песочнице можно открывать свои файлы просто перетащив их в окно браузера. Редакторы заполнятся автоматически. Также файлы можно открывать и традиционным способом.
Для совместной работы используется TogetherJS.
Редакторы поддерживают
- подсветку синтаксиса;
- проверку вводимого кода на лету;
- автоподстановку закрывающих скобок и тэгов;
- сворачивание блоков кода;
- красивое форматирование;
- умное автодополнение кода.
Автодополнение
Обычно разработчики, создавая песочницу для HTML+JS, ограничиваются простыми редакторами и фреймом, в котором запускается введенный код. Мы считаем, что это не правильно. Человек, который пришел в песочницу, плохо знает API фреймворка и без максимума подсказок он, скорее всего, ничего не сможет сделать. Поэтому мы реализовали умное автодополнение кода со всплывающими подсказками.
Для автодополнения использован уже упоминавшийся на Хабре проект Tern. В своей работе он использует несколько словарей. Словарь методов и свойств ECMA5 и JQuery предоставлен автором проекта, для объектов APS JS SDK на основе документации генерируется свой словарь. Это позволяет редактору подсказывать методы и свойства объекта исходя из того, каким конструктором он был создан.
"MessageList":{
"!type":"fn(options: object)->!custom:MessageList_ctor",
"prototype":{
"add":{
"!type": "fn(description: string, error: string)",
"!doc": "added new message"
},
...
Часто бывает, что в рамках четверти экрана становится тесно, поэтому все редакторы поддерживают полноэкранный режим, в котором между экранами можно переключатся горячими клавишами, без выхода в обычный режим.
Горячие клавиши
Горячие клавиши — важный элемент работы с apsfiddle. Ими продублирован весь функционал, а некоторые операции можно сделать только ими. Это сделано, чтобы не захламлять экран кучей кнопок. Чтобы пользователь сразу узнал какие комбинации клавиш можно использовать, при открытии apsfiddle ему сначала показывается полный список клавиатурных комбинаций:
Системные
Ctrl + H — помощь
Ctrl + O — загрузка файла
Ctrl + R — очистка полей
Ctrl + Enter — запуск
Ctrl + 1 -фокус на редактор HTML
Ctrl + 2 — фокус на редактор CSS
Ctrl + 3 — фокус на редактор JavaScript
F11 — переключение полноэкранного режима
Esc — выход из полноэкранного режима
Общие
Ctrl + B — сделать красивое форматирование
Ctrl + F — поиск
Ctrl + K — сворачивание блока кода
Ctrl + / — комментирование
Ctrl + Space — автодополнение кода
HTML
Ctrl + J — переключение по тэгу
JavaScript
Ctrl + I — показать тип
Alt + . — прыжок к объявлению
Alt + , — прыжок обратно
Автосохранение
Чтобы избежать случайной потери данных, введенный код периодически сохраняется в localStorage. Последний сохраненный код подставляется в редакторы при новом открытии apsfiddle.
Примечание
Когда мы создавали нашу песочницу, то не ставили задачи разработать полноценную Cloud IDE. В первую очередь нам была нужна площадка, на которой наши пользователи могли бы легко опробовать наш SDK и быстро научиться с ним работать на базе предоставленных нами примеров. Поэтому мы не стали брать код таких больших проектов как, например, Cloud9.
В создании максимально умного и удобного автодополнения кода для нашего фреймворка мы только в самом начале пути. Сейчас для его работы требуется соблюдения соглашений о наименовании подключаемых модулей, также мы пока не умеем подсказывать имена свойств во время их неявного использования. Т.е. при объявлении объекта, выступающего аргументом конструктора, и при указании имени свойства в виде строки, например, для вызова инкапсулирующих методов.
Краткие итоги
Сейчас APS JS SDK
- обладает модульной структурой;
- включает в себя «толстые виджеты», связывание данных и шаблонизацию;
- снабжен «рукописной» и автогенерируемой документацией;
- проверяется автотестами и вручную;
- имеет песочницу с продвинутым автодополнением кода.
Все описанное можно попробовать здесь. Наши основные клиенты находятся на Западе, и сервера хостятся там же, поэтому первая загрузка apsfiddle может оказаться продолжительной.
В целом статья получилась обзорная, если интересны какие-то детали реализации, то спрашивайте в комментариях. Если про что-то будет много вопросов, то постараемся осветить в отдельной статье.
Автор: BuranLcme