https://habrahabr.ru/company/odin_ingram_micro/blog/326232/- Разработка под e-commerce
- Разработка веб-сайтов
- JavaScript
- Блог компании Odin (Ingram Micro)
В прошлый раз мы
рассказали об APS (Application Packaging Standard) — нашей открытой технологии интегрирования приложений в платформу по продаже облачных сервисов (SaaS marketplace)
Odin Automation. Наша платформа связывает разработчиков и потребителей облачных сервисов через инфраструктуру крупных сервис-провайдеров (поставщиков телекоммуникационных и хостинг-услуг), одновременно предоставляя точку входа для конечных пользователей: контрольную панель или портал, с помощью которого можно создать сайт, настроить почту, купить антивирус или виртуальную машину в облаке. В этом посте мы более подробно остановимся на том, как устроен фронтенд контрольной панели и APS-приложений и какие возможности предоставляет APS JavaScript SDK.
Контрольная панель и экраны приложений
Для управления приложениями, докупки (upsell) и перекрестной продажи (cross-sell) cервисов конечные пользователи могут использовать контрольную панель. Она предоставляет общий интерфейс, в который разработчики APS-приложений встраивают свой пользовательский интерфейс.
Каждый сервис-провайдер брендирует инсталляцию Odin Automation в свои цвета. Поэтому мы отказались от использования проприетарной разметки и используем популярную разметку Twitter Bootstrap с CSS-препроцессором LESS. Поскольку все приложения используют APS JS SDK, разработчику темы достаточно указать всего несколько параметров, чтобы получить оформление, соответствующее бренд-буку сервис-провайдера или реселлера.
В поддержке мобильных устройств мы не стали ограничиваться адаптивной разметкой и сеткой из Bootstrap, которой может пользоваться разработчик приложения для определения размеров виджетов на различных устройствах, а пошли дальше. Некоторые сложные виджеты полностью меняют свое отображение на мобильных устройствах. Например, таблица отображается в виде плиток; а слайдер с ползунком, которым, очевидно, неудобно пользоваться на touch-устройствах, превращается в спиннер, состоящий из инпута и кнопок "+"/"-".
Сама панель, и каждое приложение являются Single Page Applications. В простейшем случае каждое APS-приложение изолировано в своем IFrame, которые роутер контрольной панели не удаляет, а скрывает и показывает. Аналогично, внутри IFrame экраны при смене не удаляются, а прячутся и показываются. Iframe необходимы, чтобы изолировать приложения друг от друга, т.к. приложения пишут различные вендоры. При этом изоляция между экранами одного приложения слабее: каждый экран — это просто JavaScript-модуль, унаследованный от соответствующего класса, а его виджеты отображения помещаются в div. Таким образом, у нас получился SPA поверх SPA.
Single Page Application
Рассмотрим более подробно наш SPA. Каждое APS-приложение описывается специальным файлом APP-Meta. В частности, этот файл содержит описание всех экранов приложения и связанных с ними данных. Там же описываются взаимосвязи приложений. Например, если приложение А предоставляет визард, в который хочет встроиться приложение Б, то приложение А декларирует поддержку встраивания, объявляя так называемый placeholder, а приложение Б декларирует желание встроить свой экран в этот placeholder.
<!—Приложение А -->
<wizard id="addUser" label="Add New Users" …>
<placeholder id="http://www.aps-standard.org/ui/service/suwizard.new/2"/>
…
</wizard>
<!—Приложение B -->
<view id="signupfilesharing" label="File Sharing" … >
<plugs-to id="http://www.aps-standard.org/ui/service/suwizard.signup/2"/>
</view>
При этом placeholder не привязан к конкретному приложению. Несколько приложений может объявить один и тот же placeholder, и тогда экран приложения Б будет встроен во все эти приложения.
Вернемся в браузер и поясним, как работает SPA на небольшом примере.
- Пользователь открывает в контрольной панели экран view-11 приложения A.
- Роутер контрольной панели создаёт IFrame и загружает в него стартовый bootstrapApp.html, общий для всех приложений. После чего подключает модуль view А1. Инстанс этого модуль останется в IFrame, даже если пользователь переключится на другой экран.
- В том же приложении пользователь переключается на экран view А2.
- Роутер подключает в IFrame модуль view А2. Теперь все переходы между этими экранами будут происходить в одном IFrame.
- Пользователь переключается на экран view В1 приложения В.
- Роутер создаёт новый IFrame и загружает в него bootstrapApp.html с исходным кодом view В1. Теперь этот код останется в IFrame, даже если пользователь переключится на другой экран.
Дальше всё точно так же, как в приложении
A.
Остановимся подробнее на жизненном цикле экрана приложения. Он состоит из следующих фаз:
- Инициализация (метод init). В ней приложение должно объявить виджеты и связать их с моделью.
- Подготовка к показу (метод onShow). Здесь приложение может выполнить подготовительные действия, которые не требуют получения данных.
- Показ (метод onContext). В этой фазе экран получил от контрольной панели данные, которые были заранее описаны в файле APP-Meta. Конечно, экран может сам сходить на сервер за данными, но мы советуем использовать декларацию, так как это позволяет экономить время загрузки. Дело в том, что для показа каждого экрана фронтенд делает запрос к серверу, потому что структура экранов могла изменится. Если данные были заранее описаны, то они будут получены в том же запросе. Если же экран сам пойдет за данными, то конечному пользователю придется ждать, пока будет получен первый запрос, а потом еще ждать второй и последующие. После того, как экран получил данные, он кладет их в модель, и виджеты меняют свое состояние.
- Скрытие (метод onHide). Здесь приложение очищает виджеты и возвращает их в нейтральное состояние. Для этого у экрана есть специальный метод.
Выше был описан простейший способ интеграции. Но бывают ситуации посложнее. Возьмём такой пример: есть dashboard-приложение А, и приложение Б хочет с помощью виджета выводить в А какую-то информацию. Для такой точечной интеграции мы разработали view-плагины. Встраивание view-plugin-ов полностью аналогично описанному выше механизму placeholder-ов. Чтобы сохранить изоляцию между А и Б, всё общение между ними осуществляется через медиатор. Это специальный объект, который содержит описание API view-плагина в виде JSON-схемы, и сначала проверяет плагин на наличие всех обязательных свойств и методов, а потом контролирует всё общение между экраном-хостом и view-плагином.
Разберем на примере. Медиатор предоставляет данные о
resourceUsage
и кастомную операцию
getWidget
, которая принимает опциональный аргумент в виде булева значения:
{
"properties": {
"resourceUsage": {
// type указывает на тип объектов, которые должны содержатся в поле resourceUsage
// URI-подобные типы описывают специальные APS ресурсы, которые хранятся в post-noSQL базе данных APS, о которой мы расскажем в следующих постах
"type": "http://aps-standard.org/types/core/subscription/1.0#SubscriptionResource",
}
},
"operations": {
"getWidget": {
"parameters": {
"withData": { "type": "boolean", "required": false }
},
"response": {
"type": "string",
"required": false
}
}
}
}
View-плагины могут предоставлять не только UI, но и логику. В этом случае в медиаторе размещается некий API, скрывающий логику из внешней системы, например, биллинг-системы. Приложение не должно знать о том, какая биллинг-система развернута у сервис-провайдера. Поэтому специфика работы с ней спрятана за унифицированный, описанный в стандарте API, а имплементация этого API в каждой конкретной системе делается в виде подключаемого view-плагина.
SDK
Первая версия APS JS SDK была разработана больше пяти лет назад и с тех пор непрерывно развивается вместе со стандартом APS. За основу был взят фреймворк Dojo. Сейчас это может показаться странным, но по меркам Web-мира это было целую эпоху назад. Тогда Angular только начинался, а React-а вообще еще не существовало.
Что же нам понравилось в Dojo:
- готовый загрузчик и модульная система на базе AMD;
- встроенная поддержка классов с множественным наследованием;
- имплементация промисов;
- большое количество различных вспомогательных модулей;
- продуманные API и развитая документация.
Сейчас наш фреймворк предоставляет разработчикам APS-приложений следующие модули:
- большое количество различных виджетов (spinner, slider, grid, password и т.д.);
- модули для работы с данными (как клиентские, так и серверные хранилища) и модули для двухстороннего связывания данных и отображения;
- различные вспомогательные модули: API для работы с биллинговыми системами, утилиты локализации и интернационализации, генератор паролей по заданной политике безопасности и многое другое.
Также разработчики могут подключать сторонние библиотеки как напрямую в AMD-формате, так и в виде ES2015 модулей, которые будут преобразованы в AMD.
Виджеты
Виджеты — это «строительные блоки» пользовательского интерфейса. В APS JS SDK они логически отделены от HTML-представления и могут:
- динамически менять значения своих свойств;
- наследоваться друг от друга;
- включать в себя другие виджеты на уровне шаблона.
Есть возможность добавлять дочерние виджеты, как динамически, так и при описании экрана. Доступны два способа описания виджетов, и по мере необходимости эти способы могут сочетаться на одном экране в любых комбинациях. Рассмотрим эти способы подробнее.
Создание виджетов с помощью конструкторов. Сначала нужно подключить нужные модули с помощью функции
require()
или использовать ключевое слово
import
, если используется транспайлер, а затем создать виджеты с помощью вызова конструктора с необходимыми параметрами. Их иерархия определяется посредством метода
addChild
, который добавляет дочерние виджеты.
import Button from "aps/Button";
var btn = new Button({
id: "example1",
label: "I am simple button"
});
Создания виджетов с помощью декларации. Иерархия виджетов и их свойства определяются в виде JSON-подобной структуры, которая передается в функцию load. Декларация каждого виджета представляет собой JavaScript-массив, который может содержать три элемента:
- имя виджета;
- (опционально) набор свойств виджета,
- (опционально) массив, содержащий дочерние элементы.
import load from "aps/load";
load([ "aps/ProgressBar", { value: "35%" } ]);
Метод
load
сам подключает необходимые модули, поэтому работает асинхронно и возвращает промис, который будет разрешен виджетом, объявленным в корне переданной структуры.
load([ "aps/ProgressBar", { id: "myProgBar", value: 0 } ])
.then(function(pb) {
pb.set("value", 41);
});
При использовании
load
-a код получается более логичным и удобочитаемым: сначала идёт родительский виджет, а затем дочерние. Большие структуры можно разделить на секции и разложить в отдельные переменные с понятными названиями, а потом уже соединить в одной структуре.
Работа с данными
Очевидно, что одними виджетами при создании UI не обойтись — им нужны данные. Источники данных для виджетов бывают в виде модулей двух типов: модули типа Model и модули типа Store.
Модули типа Model
— набор модулей для двустороннего или одностороннего связывания виджетов и данных. С помощью метода
at()
выполняется связка с виджетом. Для отслеживания изменений в Model применяется метод
watch()
. Для работы со свойствами
Model
используются методы
get()
и
set()
.
Пример инициализации
Model
из JSON-представления:
require([
"dojox/mvc/getStateful",
...
"aps/json!./newoffer.json"
], function (getStateful, ..., newOffer) {
/* Declare the data source */
var model = getStateful(JSON.parse(newOffer));
...
});
Привязка
Model
к виджетам:
var widgets =
["aps/PageContainer", { id: "page"}, [
["aps/FieldSet", { title: true}, [
["aps/TextBox", {
id: "offerName",
label: _("Offer Name"),
value: at(model, "name"),
required: true
}],
["aps/TextBox", {
label: _("Description"),
value: at(model, "description")
}]
]],
...
]];
load(widgets);
Модули типа Store
предназначены для работы с различными источниками данных. Источники бывают локальными, когда все данные находятся на клиенте, и удаленными, когда данные находятся на бекенде. Так как удаленным источником обычно является APS-контроллер, то модуль для работы с ним обеспечивает передачу аутентификационной информации и поддерживает свойства, связанные со спецификой APS, например, apsType. Вне зависимости от типа источника данных взаимодействие с виджетами, отображающими данные, идет в одностороннем порядке. Для отражения изменений в виджетах необходимо явно вызывать обновление данных.
Запросы к любым источникам данных в конечном итоге делаются с помощью Resource Query Language (RQL). RQL является языком запросов, разработанным для использования в URI, для работы с объектно-подобными структурами данных. Более подробно о нем мы расскажем в следующих постах.
Пример объявления
Store
:
import Store from "aps/ResourceStore";
var offerStore = new Store({
apsType: "http://aps-standard.org/samples/vpscloud/offer/1.0",
target: "/aps/2/resources/" + aps.context.vars.cloud.aps.id + "/offers"
...
});
Привязка
Store
к виджету, отображающему таблицу:
load(["aps/PageContainer", { id: "page" }, [
["aps/Grid", {
id: "grid",
columns: [
{ field: "offername", name: "Name", type: "resourceName" },
{ field: "hardware.memory", name: "RAM, MB" },
...
],
store: offerStore
}, ...
]
]]);
Документация и песочница
Наша платформа APS ориентирована на сторонних разработчиков, поэтому мы должны обеспечивать стабильность и простоту разработки. Без проработанной документации это было бы невозможно.
Мы создали портал для разработчиков, на котором доступна
вся необходимая документация по созданию UI с примерами кода. Это полноценный справочник: сначала даётся некое общее описание интерфейса, модуля или метода, а во вложенных уровнях — более подробная информация. При этом часть документации генерируется автоматически на основании текущего кода нашей платформы. Внутри описываются свойства, методы и возвращаемые значения.
Ещё одной «фишкой» является песочница, интегрированная в портал для разработчиков. Попасть в неё очень просто: нажмите кнопку “Run demo”, которая есть в каждом примере кода:
Наш APS Fiddle:
- знает все API наших виджетов и умеет подсказывать названия свойств и сигнатуры методов;
- позволяет сравнивать поведение кода в разных версиях стандарта APS;
- умеет переключаться с мобильного представления на десктопное;
- предоставляет ссылки на фрагменты кода, которые можно отправлять своим коллегам или службе поддержки (Share);
- позволяет работать над кодом совместно (Collab);
- может сгенерировать готовый файл с вашим кодом, словно это отдельный экран приложения, и этот файл можно сразу закидывать в реальный проект и тестировать.
Подробное описание работы с песочницей доступно тут:
Development Tools —> APS Fiddle.
В заключение
Мы предоставляем публичный API, от которого зависит работоспособность свыше 500 приложений с суммарной аудиторией в несколько миллионов пользователей. Это большая ответственность. Чтобы облегчить труд сторонних разработчиков и максимально упростить работу с нашей платформой, мы сделали подробную документацию и песочницу. А чтобы ненароком что-нибудь не сломать, мы обеспечили очень высокое покрытие кода тестами. Как мы этого добились — об этом читайте в следующем посте.