Как осуществить интеграцию сторонних сервисов с SFCC и ничего не сломать

Підписуйтеся на Telegram-канал «DOU #tech», щоб не пропустити нові технічні статті.

Меня зовут Андрей, я 11 лет в разработке решений для ecommerce, 5 из них в Astound Commerce на позиции Web Development Lead. Данная статья будет полезна, прежде всего, тем, кто впервые встретился с платформой Salesforce Commerce Cloud, и особенно тем, кто никогда не интегрировал сторонние сервисы и\или не понимает, как и зачем это делать. Мы затронем Service Framework, его настройку, научимся правильно обрабатывать логи. Начнем с начала. Вспоминая рынок ecommerce (и не только его) 10 лет назад, на глаза наворачиваются слезы — насколько все было проще, солнце светлее, технологии понятнее, а список требований к функционалу и продукту вмещались на нескольких А4. А помните стек технологий, который требовался для принятия на работу веб разработчиком? Знаешь что такое HTML/CSS? Прекрасно! Нанят! Вот только прогресс был не согласен с тем что нам, разработчикам, так легко живется и каждый год, а то и месяц подкидывал новые вызовы.

  • А что, если перестать мучать пользователя формами регистраций и брать его данные из социальных сетей?
  • Как понять, какие кнопки нажимает пользователь, а какие зарастают мхом?
  • Зачем мы принимаем оплату заказов только наличными? Уже и банков много, и народ кредитными картами пользуется, может ну его, будем делать онлайн оплаты?
  • Ты, возможно, не заметил, но уже 70% трафика идет через мобильные устройства, а сайт твой до сих пор на таблицах. Надобно изволить адаптив завезти.
  • <огромный список примеров>

С каждым таким вызовом рождаются новые фреймворки и библиотеки, делающие разработку быстрее и гибче, но в тоже время список требований для соискателей той самой работы мечты становится раздутым, как щечки на слишком затянувшемся карантине. Вы ведь не забываете надевать маски? Растёт стек технологий, бюджеты, время разработки — растёт всё. И перед владельцами бизнеса становится вопрос — нанимать в штат дополнительный отряд девелоперов, которые будут разрабатывать фичи в ногу со временем и менеджеров, транслирующих описание «хотелок» на понятный нам с вами язык? Или воспользоваться готовыми решениями — интеграциями, которые позволяют за меньшее количество человеко-часов сделать всё тоже самое и с меньшей кровью? Зачем с нуля разрабатывать то, что есть по подписке за 15$ в месяц? Казалось бы, выбор очевиден, ведь даже на самую абсурдную «хотелку» в глубинах интернета можно найти готовый продукт или решение, и интегрировать его в свой продукт, как конструктор лего. Осталось только всё не сломать, интегрируя сторонние сервисы. Об этом и поговорим на примере Salesforce Commerce Cloud (далее SFCC) и Bing Adaptive URLs (сервис для быстрого индексирования страниц сайта).

Какую задачу решает Bing Adaptive URLs

Что такое Bing Adaptive URL’s (далее Adaptive URL’s)? Это сервис, позволяющий принудительно проиндексировать URL страницы, не дожидаясь автоматики (детальнее про web crawler здесь). Возьмем статистику охвата поисковиков, и предположим, что ежедневно ваш онлайн магазин работает по всему миру, а посещает его 1000 уникальных пользователей. При этом все эти потенциальные покупатели используют персональный компьютер (desktop). Статистика грубая, но тем не менее передаёт суть. Итак. (посмотреть чарт) 5.96% из 1.000 это 59.6 пользователей, которые пришли из поисковика Bing. Возьмём KPI за 2020 год, по сравнению с 2019 годом (привет Covid), revenue выросло на 100%, с 2% до 4%, (2019, 2020) что невероятно много! 4% из 59.6 пользователей это 2.38 покупки, при среднем чеке в $20 это $47.68 упущенной прибыли. Вроде не много, но в разрезе года это уже $15000, уже вроде цифра, а что если пользователей не 1.000 а 10.000. Заставляет подумать, да? Уговорил. Давай все же потыкаем палкой в Adaptive Urls и заставим его принудительно проиндексировать страницы? Давай.

Подготовка к интеграции

Прежде, чем бросать не выпитый чай и бежать «делать хоть что-то», нужно проанализировать систему, с которой предстоит интегрироваться. Для себя я выделил следующий список шагов, позволяющий собрать минимально необходимый набор данных, чтобы начать разработку.

  1. Определить целесообразность использования той или иной системы и доказать клиенту, что она ему не нужна. Стряхните пыль с калькулятора, возьмите ручку и бумажку, посчитайте стоит ли оно того. Я часто встречаюсь с ситуациями, когда клиенты берут на вооружение какую-то систему, и во время разработки приходит понимание, что она платная, 15-долларовой подписки явно не хватит, а энтерпрайз-версия стоит уже $1500. Да и из функционала нам нужна только десятая часть. Необходимо, чтобы клиент полностью понял ценовую политику, а не нажал «принять», не глядя на условия использования. Часто стоимость становится неким «стоп-словом».
  2. Определите ограничения системы, с которой вы хотите интегрироваться. Очень часто такие ограничения, если они есть, написаны мелким шрифтом где-то в недрах документации. Такие ограничения во время разработки могут свести на нет все ваши усилия и заставить начинать поиск с самого начала. Вы должны понимать, с чем работаете.
  3. Почитайте техническую документацию, посмотрите, как и в каком формате отправлять данные, что и как получать в ответ. Большую часть внимания я уделяю механизмам авторизации. Например, если нам нужно передать данные, доступные каждому пользователю на сайте (т.е. информация не конфиденциальна, метрики, ссылки) то я предпочту что-то простое — HTTP Authentication будет в самый раз. А в случае с Adaptive Urls, это простая передача токена в параметре URL. Использование OAuth 2.0, к примеру, раздувает человеко-часы, добавляет в и так сложный механизм дополнительные шестерни, а чем больше шестеренок, тем больше мест, где что-то может сломаться. Нужно соблюдать баланс между сложно и надежно (но никогда не забывать про надежно)
  4. Убедитесь, что ваша система будет поддерживать всё то, что требует техническая документация интеграции — протоколы, методы передачи и форматы данных. Всё это важно. Пройдя уже достаточно длинный путь, не хочется наткнуться на несовместимость систем, а городить костыли, чтобы обойти эти препятствия, зачастую — путь в никуда.
  5. Временная оценка разработки (разработка, документация, тестирование, релиз менеджмент). Если разработка интеграции займёт несколько тысяч часов только ради того, чтобы получить «ответ на главный вопрос жизни, вселенной и всего такого» на секунду раньше, то есть огромная вероятность, что приложенные усилия не оправдают результатов.

Для интеграции с Adaptive Urls, весь процесс подготовки будет выглядеть примерно так:

  • Целесообразность: Стоимость — бесплатно. Альтернатива — ждать, пока Adaptive Urls соизволит всё сделать сам — не вариант. Целесообразно.
  • Ограничения: импорт 10 ссылок в день для тестовых аккаунтов, и 10,000 в день для продакшн. Не фонтан, но можно выгрузить всё в несколько дней. Пойдёт.
  • API: Простая авторизация, запросы через POST в JSON. С авторизацией тоже не фонтан, но мы и выгружаем ссылки, которые доступны всем. Криминала нет. Пойдёт.
  • Совместимость. JSON мы генерировать умеем, ничего шифровать не нужно, в POST запросы также создавать умеем. Не вижу проблем, вижу направление. Пойдёт.
  • Оценка. Часов пять на разработку, столько же на документацию и тестирование. Пойдёт.

Сбор требований.

Что нам нужно для того, чтобы начать разработку? Определить минимальный набор требований. Начнём.

  • У Adaptive URLs есть авторизация по токену, нужно получить этот токен. Регистрируемся в webmaster dashboard и получаем токен.
  • Регистрируем домен нашего магазина, для которого мы будем экспортировать ссылки.
  • Определяем, какие именно ссылки должны быть индексируемыми, а какие — нет. Например, поисковику совершенно не нужно знать, на какой странице лежит профиль пользователя, и где находится корзина. А вот ссылки на продукты, категории, статьи — это пожалуйста.
  • Поскольку есть ограничение в 10,000 ссылок в день, можно сделать выгрузку по нажатию на кнопку либо автоматизировать процесс (JOB). Пойдем вторым путем.
  • Ах да, нужно же всё сделать так, чтобы ничего не сломать. Дiдько.

Разработка

Как я уже говорил в начале статьи, мы будем использовать платформу SFCC как демонстрационный стенд. Для того, чтобы магазин и Adaptive URLs могли общаться друг с другом, нужно наладить коммуникацию между ними. Для этих целей в инфраструктуре SFCC существует механизм сервисов, который позволяет на уровне конфигурации из Business Manager указывать ряд параметров и ограничений, по которым будет работать этот сервис. Создаем профиль сервиса. Указываем, что если от Adaptive URLs в течении двух секунд не получаем ответа, то пробуем связаться с Adaptive URLs еще 3 раза с задержкой в 5 секунд. Если это не поможет — завершаем работу сервиса с ошибкой TIMEOUT. Мы заботимся не только о состоянии нашей системы, но и о состоянии сервиса, с которым мы хотим интегрироваться, потому не нужно в настройках указывать, что если через 2 секунды не получаем ответ, то еще 100000 раз пробуем связаться без задержки. Удаленный сервис может счесть столь настойчивые попытки что-то ему передать как DDOS атаку, и как результат, IP-адрес, с которого идут запросы, может попасть в чёрный список. Далее, создаем учетные данные сервиса, указываем URL, по которому наш сервис должен отправлять свои запросы. Имя пользователя и Пароль оставляем пустыми, так как авторизация осуществляется через токен в параметре URL. Сохраним токен где-нибудь в другом месте (Site Preferences будет хорошим выбором). И, наконец, создаем сам сервис. Всегда давайте имя сервису такое, будто вас среди ночи разбудил телефон, и голос на другом конце ласковым матом просит ваш сервис отключить. Имя должно быть лаконичным, отображающим суть сервиса, чтобы его было просто найти в бесконечном списке других интеграций. В нашем случае это будет adaptive.urls.http (<название интеграции>.<протокол коммуникации>). Всегда давайте вашим сервисам отдельные имена лог-файлов, куда он будет записывать информацию по коммуникации (запрос\ответ). Сервис сконфигурирован. Теперь нужно программно вытащить все нужные нам ссылки, сформировать список в формате, который требует Adaptive URLs, и отправить его через сервис, который мы только что определили в Business Manager. Поехали... ... Ах да, нужно же ничего не сломать, вечно забываю. Чтобы максимально изолировать потенциально опасный (ломучий) функционал, лучше создать отдельный картридж. В случае ошибки в коде, которая тянет за собой вереницу проблем, картридж можно просто отключить. Плюс, не нужно будет редактировать уже существующие файлы на вашем проекте. Название картриджа так же, как и с сервисами, создаем лаконичное и простое — int_adaptive_urls («int» от «integration»). Выгрузка больших массивов данных — это то, что нам предстоит, и для этих целей Jobs Framework именно то, что нужно использовать в случае с нашей интеграцией. Создаём в корне картриджа файл steptypes.json и прописываем там наш Job Step Пример: gist.github.com/...​a3ffd729e6e19547a42e294a9 В module не забываем указать путь к вашему файлу, который будет вызываться каждый раз при запуске Job. steptypes.json создан. Создаем исполнительный файл, путь к которому был прописан в json. С задачей всё вроде понятно, но нужно подумать, какие проблемы могут возникнуть в процессе, и как их избежать (очепятки в учет не берем). Но всё по порядку. У нас есть пустой файл и тонна фантазии, которую нужно конвертировать в строки кода. «А зачем мы делали сервис в Business Manager?» Правильно! Пишем дефинишн сервиса: gist.github.com/...​956ad260c214018af5113fb43

var LocalServiceRegistry = require('dw/svc/LocalServiceRegistry');
var sitepref = require('util/sitepref');
var APIKey = sitepref.getValue('AdaptiveURLsAPIKey');
 
module.exports = LocalServiceRegistry.createService('adaptive.urls.export.http', {
   createRequest: function (svc, URLs) {
       svc.setRequestMethod('POST');
       svc.addHeader('Content-Type', 'application/json');
       svc.addHeader('Accept', 'application/json');
       svc.setURL(svc.URL + '?apikey=' + APIKey);
 
       return JSON.stringify({
           siteUrl: URLUtils.httpsHome().toString(),
           urlList: URLs.toArray()
       });
   },
   parseResponse: function (svc, serviceResponse) {
       return serviceResponse.statusCode;
   }
});

Вроде красиво, но... что, если кто-то ошибся при настройке сервиса на Production, и включил для него Communication Logs? Тогда всё, что будет уходить от нас и приходить от удалённого сервиса, будет логироваться. И в случае, если злоумышленник получит доступ к логам, то он сможет как минимум узнать наш API Key, что уже есть не хорошо. Если не фильтровать логи, можно и до Log Injection доиграться.

var LocalServiceRegistry = require('dw/svc/LocalServiceRegistry');
var sitepref = require('util/sitepref');
var APIKey = sitepref.getValue('AdaptiveURLsAPIKey');
 
module.exports = LocalServiceRegistry.createService('adaptive.urls.export.http', {
   createRequest: function (svc, URLs) {
       ...
   parseResponse: function (svc, serviceResponse) {
       ...
   },
filterLogMessage: function (msg) {
       if (dw.system.System.getInstanceType() === dw.system.System.PRODUCTION_SYSTEM) {
           try {
               if (empty(msg)) {
                   return 'Message Missing';
               }
               return msg;
           } catch (e) {
               return 'Unable to parse Service log message.';
           }
       } else {
           return msg;
       }
   }
});

Вот, теперь чудно. Всегда фильтруйте логи. Будьте зайкой. Теперь нужно стряхнуть пыль на полке с рекурсиями и написать скрипт, который бы собирал все неэкспортированные ссылки (ассеты, продукты, категории) и передавал их для обработки сервису. Если сервис отрабатывает без ошибок, то проставлять ВСЕМ ассетам, продуктам и категориям флаг, что они экспортировались, в ином случае — не делать ничего. Здесь есть 2 проблемы:

  • Если Job’a запускается единожды, и ссылок миллион, то в случае ошибки — зачем собирать заново все ссылки?
  • Есть ли смысл ждать, пока одна единственная Job’a переварит миллионы ссылок?

Для первой проблемы мы можем для каждого класса (Content, Product, Catalog) сделать флаг (custom attribute), который будет отвечать за то, экспортировался ли инстанс класса или нет. Ну а с монструозными Job’aми всегда можно побороться, разбив их на chunk’и или в нашем случае — поставить ограничитель, сколько именно ссылок может быть экспортировано за один запуск Job’ы. Добавляем для каждого класса кастомный аттрибут: isUrlExported Добавляем новый параметр в steptypes:

{
                   "@name": "maxUrlsToExport",
                   "description": "Due to limitation only limited amount of links can be exported once a day",
                   "@type": "string",
                   "@required": false,
                   "default-value": "10"
               }

Так, пометить контент, что он был экспортирован мы можем, не придется перебирать весь массив данных по новой. И даже если Job’е суждено работать много часов, чтобы перебрать весь контент, мы можем ожидать результатов уже здесь и сейчас (благодаря тому, что за раз выгружается N ссылок, а не все возможные, малыми партиями, но часто). Вроде неплохо. Перенесем это в код: gist.github.com/...​fbb9a5ccf3245a6a1fbc9f922 Как можно заметить, в коде появились транзакции. Зачем? Допустим, у нас есть 10 готовых для экспорта ссылок. Последняя ссылка в списке имеет ошибку, по какой-то причине имя домена сгенерировалось неправильно. Поскольку ссылки мы отправляем пачкой, то узнать, в какой именно ссылке интеграция нашла проблему, весьма затруднительно (особенно, если интеграция возвращает просто код ответа, как в нашем случае). Для таких случаев будем использовать транзакцию, если очередная пачка ссылок прошла проверку огнем. Коммитим транзакцию, и все экземпляры контента получают заветный флаг true, в ином случае — вся пачка ссылок считается не валидной и логируем ошибку. Весь процесс отлавливания потенциальных проблем чем-то похож на модный в свое время подход TDD:

  • Представляем, какие данные нам нужно отправить
  • Прикидываем, какие данные мы получим, и что с ними делать
  • Разбираемся, как контролировать поток данных так, чтобы не нагружать систему, и сделать процесс отказоустойчивым
  • Пишем код

Готово. Мы заслужили три чая.

Послесловие

Очень часто сама разработка того или иного функционала занимает не так много времени. Куда больше часов уходит на «допилить» этот функционал до рабочего состояния через багофикс. Баги всё появляются и появляются, за окном дождь сменяет снег, а баги всё хлынут нескончаемым потоком. Ведь, создав свой «шедевр», ты поломал не только задумку автора, но еще и смежный функционал... Никогда не ленитесь в начале разобраться с теми сервисами, с которыми вам предстоит работать. Не всегда первое впечатление верное. Попробуйте уточнить у клиента, что действительно ему нужно. Очень крутое пособие на эту тему: Карл Вигерс — Разработка требований для ПО. Лучше потратить 2 дня на вычитывание документации и прототипирование каких-то кусков логики, чем сразу ринуться в скрипты и переписывать их по несколько раз, потому что вскрылся какой-то «подвох» на третьей странице документации по API. Вспомнили себя? Никогда не стесняйтесь попросить помощь, если возникают проблемы. На дворе 2021 год, в 95% случаев это уже кто-то делал за вас. Выстройте в голове весь dataflow. Если взаимосвязей много — нарисуйте диаграмму. Reference: creately.com/diagram/example/iotv875q1/ Старайтесь думать наперёд, «а что если» — отличный вопрос для любого случая. Если вы думаете, что «такой ситуации точно не произойдет», я вас заверяю — произойдёт. Наши покупатели находили баги вроде «если открыть этот поп-ап, и 10 минут ничего не делать, то он подпрыгнет на 3 пикселя, и кнопка Add to Cart не будет нажиматься». Понятно, что предугадать абсолютно все не получится, но какое-то количество ситуаций вы все же предусмотрите.

👍НравитсяПонравилось5
В избранноеВ избранном4
LinkedIn
Допустимые теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Допустимые теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Interessant artikel. Ik ben trouwens nog steeds een voorstander van chunks.
Wachten op Signor-Pomidor in Amsterdam.

5.96% из 1000 — это 59.6... это как-то превратилось в 57... дальше пробовал читать, но вот слегка засело 5.96% из 1000 = 57 %)

еще хотелось бы увидеть разницу между этим adaptive urls и банальным sitemap.xml... где тоже, не заставляют бота лазить повсюду, а предоставляют карту

Статья в целом не про Bing а про создание интеграции под SFCC, и Bing был приведен как пример, потому что с ним раньше я не работал и было интересно покопаться в новой API.

Отвечая на вопрос: Разница в том что боты не парсят sitemap в режиме realtime (чем выше ранг сайта тем чаще это происходит) тогда как Adaptive URL’s позволяет проиндексировать страницу принудительно, здесь и сейчас.

Подписаться на комментарии