javascript

Технология APS: фронтенд контрольной панели и возможности JS SDK

  • четверг, 13 апреля 2017 г. в 03:15:00
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 на небольшом примере.

  1. Пользователь открывает в контрольной панели экран view-11 приложения A.
  2. Роутер контрольной панели создаёт IFrame и загружает в него стартовый bootstrapApp.html, общий для всех приложений. После чего подключает модуль view А1. Инстанс этого модуль останется в IFrame, даже если пользователь переключится на другой экран.
  3. В том же приложении пользователь переключается на экран view А2.
  4. Роутер подключает в IFrame модуль view А2. Теперь все переходы между этими экранами будут происходить в одном IFrame.
  5. Пользователь переключается на экран view В1 приложения В.
  6. Роутер создаёт новый 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 приложений с суммарной аудиторией в несколько миллионов пользователей. Это большая ответственность. Чтобы облегчить труд сторонних разработчиков и максимально упростить работу с нашей платформой, мы сделали подробную документацию и песочницу. А чтобы ненароком что-нибудь не сломать, мы обеспечили очень высокое покрытие кода тестами. Как мы этого добились — об этом читайте в следующем посте.