Как создать приложение Optimizely Connect Platform (OCP)
Платформа Optimizely Connect (OCP): объединение систем в экосистеме Optimizely One и изучили, как это позволяет системам Optimizely One обмениваться данными чисто и последовательно. Этот пост основан на этом фундаменте и представляет собой более практичный и ориентированный на разработчиков взгляд на платформу.
В этой статье мы рассмотрим процесс создания приложения OCP, которое подключается к Cloudinary, — от создания проекта и определения схемы данных до реализации заданий, проверки приложения и публикации интеграции.
Начиная
Прежде чем мы начнем создавать коннектор Cloudinary, необходимо выполнить несколько предварительных условий.
1. Получите учетную запись разработчика OCP.
Запросите доступ у Optimizely, чтобы получить инструменты и разрешения. См. Оптимизация документации для более подробной информации.
2. Настройте среду разработки
Требования:
Проверьте вашу настройку:
1ocp --version
3. Создайте новое приложение
1ocp app init
Это создает:
-
Определение приложения
-
Схема YAML
-
Папка с вакансиями
-
Папка функций
-
Форма настроек
Шаблонное приложение является отличной отправной точкой и обеспечивает основу для соединителя Cloudinary, используемого в этом примере.
Создание приложения
Теперь, когда приложение создано, мы можем перейти к созданию коннектора Cloudinary.
1. Настройки приложения
Нам необходимо получить учетные данные и конечную точку интеграции, чтобы приложение могло аутентифицироваться и взаимодействовать с Cloudinary. Можно расширить интерфейс OCP, чтобы захватывать их и безопасно сохранять для использования в приложении.
settings.yml
'settings.yml' файл используется для определения формы. В этой форме сохраняются все настройки, необходимые вашему приложению. Форма может содержать разделы, позволяющие логически группировать настройки.
1sections:
2 - key: authorisation
3 label: Authorisation
4 elements:
5 - type: text
6 key: url
7 label: Integration URL
8 help: The URL of the Cloudinary account to integrate with. This is typically in the format `https://api.cloudinary.com/v1_1/>`.
9 hint: https://api.cloudinary.com/v1_1/>
10 required: true
11 - type: text
12 key: username
13 label: Username
14 help: The username for the Cloudinary account.
15 required: true
16 - type: secret
17 key: password
18 label: Passwword
19 help: The password for the Cloudinary account.
20 required: true
21 - type: button
22 label: Save
23 action: save
24 style: primary
25 - type: button
26 label: Start Sync
27 action: sync
28
Форма может содержать поля разных типов. Поля могут включать проверку, а также ссылаться на внешние источники данных. Более подробную информацию можно найти в документации Optimizely: Дальнейшее чтение.
Жизненный цикл формы
Как описано выше, 'settings.yml' file используется для определения формы, но нам также нужен механизм взаимодействия со значениями, когда пользователь их вводит.
'Lifecycle.ts' Файл содержит функцию onSettingsForm, которая точно выполняет эту задачу.
1 public async onSettingsForm(
2 section: string,
3 _action: string,
4 formData: SubmittedFormData
5 ): Promise<LifecycleSettingsResult> {
6 const result = new LifecycleSettingsResult();
7 try {
8
9 if (section === 'authorisation') {
10
11 switch (_action) {
12 case 'save':
13 await storage.settings.put(section, formData);
14 await registerWebhooks(formData.url as string,
15 formData.username as string,
16 formData.password as string);
17
18 break;
19 }
20 }
21
22 return result;
23
24 } catch {
25 return result.addToast(
26 'danger',
27 'Sorry, an unexpected error occurred. Please try again in a moment.'
28 );
29 }
30 }
Приведенный выше код срабатывает, когда ‘Сохранять‘ нажата кнопка формы. В фрагменте кода
-
Значения формы сохраняются в хранилище OCP.
-
Пользовательская функция ‘зарегистрироватьсяВебхукиназывается. При этом вебхук OCP регистрируется в Cloudinary (подробнее об этом позже).
2. Схема данных
Поскольку это ‘Приложение синхронизации данных‘, нам нужно определить объекты данных, в которых будут храниться данные изображения, которые мы синхронизируем с Cloudinary.
Схемы объектов данных определяются как YML и должны быть расположены в папке 'schema' папка.
1name: cloudinary_image
2display_name: Cloudinary Image
3fields:
4 - name: asset_id
5 type: string
6 display_name: Asset ID
7 description: Unique identifier for the asset in Cloudinary.
8 primary: true
9
10 - name: public_id
11 type: string
12 display_name: Public ID
13 description: Public identifier for the asset, used in URLs and API calls.
14
15 - name: format
16 type: string
17 display_name: Format
18 description: Document format, e.g. jpg, png, etc.
19
20 - name: version
21 type: number
22 display_name: Version
23 description: Document version
24
25 - name: resource_type
26 type: string
27 display_name: Resource Type
28 description: Type of resource, e.g. image, video, etc.
29
30 - name: type
31 type: string
32 display_name: Type
33 description: Type of upload, e.g. upload, private, authenticated, etc.
34
3. Вакансии
В Optimizely есть концепцияРабота‘. Они предназначены для поддержки длительных процессов, например обработки начальной загрузки данных. Задания можно планировать (через cron) или запускать по требованию.
Основные понятия
Хотя задание поддерживает длительные процессы, оно должно быть написано так, чтобы обработка выполнялась пакетно, причем каждый пакет обрабатывает подмножество общих данных. В примере Cloudinary мы не будем загружать все изображения одновременно; мы будем загружать их меньшими частями.
Подготовить
Этот метод вызывается для настройки пакета для обработки задания. Здесь определяется состояние начальной загрузки или состояние предыдущего запуска передается следующему пакетному процессу.
Выполнять
Здесь осуществляется фактическая обработка. Он получает состояние, которое используется для определения следующего набора изображений для загрузки.
Время, необходимое для выполнения процесса, должно быть в пределах 60 секунд. Родительский процесс OCP может завершить что-либо более длительное.
1 public async perform(
2 status: HistoricalImportJobStatus
3 ): Promise<HistoricalImportJobStatus> {
4 const state = status.state;
5 let encounteredError = false;
6 try {
7 // fetch some assets from our API
8 const response = await this.fetch(state.cursor, 500);
9
10 logger.info(`response ${response.status}, ${response.statusText} `);
11
12 if (response.ok) {
13
14 const result = (await response.json()) as HistoricalImportResult;
15 const cursor = result.next_cursor ?? '';
16
17 // Update our state so the next iteration can continue where we left off
18 state.cursor = cursor;
19 state.count += result.resources.length;
20
21 // Transform our assets and send a batch to Optimizely Hub
22 if (result.resources.length > 0) {
23 await odp.object('cloudinary_image', result.resources.map(transformAssetToPayload));
24 }
25
26 // In this example, 0 assets means we have imported all the data
27 if (result.resources.length === 0 || cursor.length === 0) {
28 // Notify the customer we completed the import and provide some information to show it was successful
29 await notifications.success(
30 'Historical Import',
31 'Completed Historical Import',
32 `Imported ${state.count} assets.`
33 );
34 status.complete = true;
35 return status;
36 }
37
38 } else {
39 logger.error(
40 'Historical import error:',
41 response.status,
42 response.body.read().toString()
43 );
44 encounteredError = true;
45 }
46 } catch (e) {
47 // Log all handled errors for future investigation. Customers will not see these logs.
48 logger.error(e);
49 encounteredError = true;
50 }
51
52 // If we encountered an error, backoff and retry up to 5 times
53 if (encounteredError) {
54 if (state.retries >= 5) {
55 // Notify the customer there was a problem with the import
56 await notifications.error(
57 'Historical Import',
58 'Failed to complete historical import',
59 'Maximum retries exceeded'
60 );
61 status.complete = true;
62 } else {
63 state.retries++;
64 await this.sleep(state.retries * 5000);
65 }
66 }
67
68 // Our state has been updated inside status so we know where to resume
69 return status;
70 }
В приведенном выше коде я получаю следующие 500 изображений из Cloudinary. Если есть изображения, я сохраню их в базе данных OCP; в противном случае я предполагаю, что все изображения загружены, и обработка прекращается.
Полный код задания см. в моем репозитории на GitHub: Историческийимпорт.js
4. Вебхуки
Вебхуки позволяют обновлять данные по требованию, а не по расписанию. Это означает, что как только в Cloudinary будет добавлено новое изображение, база данных OCP также будет обновлена.
У OCP есть ‘Функция‘, что позволяет нам создать вебхук
Определить функции
Вам необходимо зарегистрировать свою функцию в 'app.yml' файл.
1
2functions:
3 handle_new_asset:
4 entry_point: HandleNewAsset
5 description: Webhook that handles new assets uploaded to Cloudinary
Реализация функции
1import { logger, Function, Response } from '@zaiusinc/app-sdk';
2import { odp } from '@zaiusinc/node-sdk';
3import { transformNotificationToPayload } from '../lib/transformAssetToPayload';
4import { CloudinatyImageUploadNotification } from '../data/CloudinatyImageUploadNotification';
5
6export class HandleNewAsset extends Function {
7 1011
12 public async perform(): Promise<Response> {
13
14 const notifcation = this.request.bodyJSON as CloudinatyImageUploadNotification;
15
16 if (!notifcation?.asset_id) {
17 return new Response(400, 'Unable to process request, invalid notification');
18 } else {
19 try {
20
21 const payload = transformNotificationToPayload(notifcation);
22 await odp.object('cloudinary_image', payload);
23
24 // return the appropriate status/response
25 return new Response(200);
26 } catch (e: any) {
27 logger.error(e);
28 return new Response(500, `An unexpected error occurred: ${e}`);
29 }
30 }
31 }
32}
Когда в Cloudinary добавляется новое изображение, оно вызывает зарегистрированный вебхук (см. 'Lifecyce.ts'). Затем вызывается функция, показанная выше, которая считывает полезную нагрузку Cloudinary и обновляет базу данных OCP.
Развертывание приложения
Приложение невозможно запустить локально; его можно запустить только на платформе OCP. Это означает, что крайне важно писать комплексные модульные тесты для проверки функциональности приложения. Интерфейс командной строки OCP ожидает этого и выполнит все тесты, обнаруженные в проекте. Платформа тестирования Jest устанавливается при создании шаблона приложения.
Хотя поначалу это может показаться ограничительным, сочетание модульных тестов и проверки CLI делает развертывание предсказуемым и повторяемым.
Чтобы протестировать и развернуть приложение, вам необходимо выполнить следующие шаги:
-
ocp app validate– Конфигурация проверена и выполнены все модульные тесты. -
ocp app prepare– Приложение подготовлено к публикации. -
ocp directory publish [email protected]– Опубликуйте приложение в OCP. -
ocp directory install [email protected] TRACKER_ID– Установите приложение в свой экземпляр.
Доступ к журналам
На вкладке «Устранение неполадок» вашего приложения будут отображаться все сообщения, записанные вами в журналы.

Заключительные мысли
Платформа Optimizely Connect предоставляет надежную и простую основу для интеграции внешних систем в экосистему Optimizely One. Для команд, уже работающих с Optimizely One, OCP устраняет большую часть операционных накладных расходов, традиционно связанных с разработкой интеграции.
Я обнаружил, что разработка соединителя Cloudinary на основе шаблонного приложения очень быстрая и простая.
Исходный код приложения доступен в моем репозитории GitHub: https://github.com/andrewmarkham/Cloudinary/
По теме

