Создание SDK с нуля

Сказка об отчаянии и преодолении

Наш Javascript SDK - один из последних проектов, выпущенных Bynder с открытым исходным кодом. Аббревиатура SDK расшифровывается как Software Development Kit, что является довольно расплывчатым описанием, поэтому конкретным результатом этого проекта стала библиотека Javascript, доступная через NPM, которая обеспечивает простой и легкий способ взаимодействия с API Bynder.

Вступление

Все началось в Garage Beer, одном из многих пивных заведений, где мы случайно оказались менее чем в 10 минутах ходьбы от нашего офиса в Барселоне, с обсуждения, которое некоторые из нас, принадлежащих к отделу интеграции, вели о будущем команда с точки зрения работы и автономности.

В то время в Bynder уже было несколько SDK на продвинутой стадии разработки (например, C # и Java), и мы говорили о том, насколько актуально было бы иметь JavaScript SDK для обработки запросов, которые мы уже есть и дополняют наше предложение с точки зрения интеграции. Так все и началось.

Пару недель спустя мы собрались вместе, чтобы обсудить детали проекта, а именно с точки зрения реализации и безопасности (что очень важно, учитывая, что на стороне клиента нет способа скрыть код, который он выполняется).

Поскольку мы начинали его с нуля, у нас была возможность применить правила и методы, которые мы считали лучшими в то время. Мы решили следовать Behavior Driven Development в качестве руководства, и для каждого завершенного модуля и функции мы позаботились о том, чтобы у нас было что-то осязаемое и готовое к поставке, с надлежащей документацией и рабочими тестами. Это потребовало некоторой дополнительной работы в начале настройки, как объясняется ниже, но в конечном итоге окупилось, когда мы поняли, что добавление новой функции занимает всего пару минут, при этом все работает так же, как и раньше.

Принципы развития:

  • Следует использовать последнюю версию языка на момент ES2015 (включая некоторые основные функции, такие как классы, стрелочные функции, обещания и т. Д.), Используя преимущества Babel для транспиляции кода и обеспечения обратной совместимости. .
  • Весь код должен быть разделен на модули, которые соответствуют набору функций (например, в модуле Asset¹ мы инкапсулируем все функции, касающиеся управления активами).
  • Функциональность каждого модуля будет полностью описана в заявке, которая будет назначена одному разработчику, чтобы убедиться, что они независимы и над ними можно одновременно работать.
  • Результатом каждого билета будет пул-реквест, который будет рассмотрен несколькими разработчиками из группы интеграции, имеющей опыт работы с другими нашими SDK, и командой внешнего интерфейса, имеющей опыт работы с языком, на котором он был разработан.
  • Каждый билет будет считаться завершенным только после реализации кода, написания документации для каждой функции и класса, рабочих тестов для всех методов SDK и примеров, показывающих, как использовать все функции, реализованные в этом билете.
  • Каждый общедоступный метод SDK (за исключением некоторых редких случаев) получает в качестве параметра JavaScript Object, где каждое из его свойств работает как переменная для этой функции. Таким образом гарантируется, что даже если API будет обновлен и какой-то метод будет изменен, SDK останется совместимым, сохраняя в качестве единственного источника достоверной информации документацию по API.

Весь этот процесс помогает удостовериться, что SDK соответствует всем требованиям качества, которые должны быть выпущены.

Процесс проектирования

Решение сделать этот проект общедоступным SDK и инициативой с открытым исходным кодом было принято задолго до начала его разработки. В Bynder мы очень ценим усилия, прилагаемые к проектам с открытым исходным кодом и обмену знаниями, поэтому мы подумали, что было бы интересно предпринять некоторые действия в этом направлении и поделиться некоторыми своими работами с внешним миром, извлекая выгоду из возможного взаимодействия с сообществом.

Учитывая, что вся цель этого SDK - выполнять асинхронные вызовы API Bynder, мы решили реализовать класс APICall, который будет получать все данные запроса, а также не менее важные токены аутентификации. Каждый запрос должен быть аутентифицирован с использованием протокола OAuth 1.0a, и, поскольку это часть спецификации, все они должны включать данные аутентификации в заголовок. Кроме того, учитывая характер некоторых запросов, они требуют определенного способа шифрования передаваемой информации. На основе всего этого было реализовано несколько методов, чтобы удовлетворить потребность в надежном, но простом решении для выполнения вызовов API. Здесь у нас есть диаграмма классов, отображающая свойства, необходимые для создания класса и реализованных методов:

Это пример того, как это будет реализовано в одном из методов SDK:

Для процесса аутентификации мы использовали стороннюю зависимость под названием oauth-1.0a, которая была тщательно проверена нашей командой Infosec перед утверждением для использования.

Нюансы реализации

Чтобы обеспечить наилучший возможный опыт разработки, мы решили создать интерфейс, полностью основанный на обещаниях, предоставляя пользователю полный контроль над его асинхронным кодом. Это пример использования SDK с использованием классической записи then()/catch():

Здесь у нас тот же код, но с использованием функции ES2017 async/await нотация:

Эта функция позволяет любому разработчику писать свой код, делая асинхронные вызовы API Bynder похожими на любую другую строку однопоточного JavaScript.

Процессы строительства и автоматизации

К тому времени, когда мы начали работу над этим проектом, мы поняли, что нам нужен способ автоматизации основных задач, который можно было бы легко настраивать, запускать и поддерживать. Основными процессами будут транспиляция (с использованием Babel), сборка (с использованием Webpack) и запуск тестов (с использованием Jasmine).

Мы решили использовать Gulp, который нельзя рассматривать как зависимость, поскольку технически мы не зависим от него. Благодаря большому сообществу с открытым исходным кодом у Gulp есть несколько плагинов, которые упрощают создание простых задач. Простой запуск gulp запустит выполнение ESLint, Jasmine, Babel и JSDocs, сэкономив время на запуск каждого из них по отдельности и получив возможность управлять потоком в зависимости от того, что создает каждая из задач.

Поскольку мы решили использовать ES2015, а некоторые из его функций несовместимы с целым рядом сред, на которые мы хотели ориентироваться, мы решили использовать Babel. Мы использовали env preset только для того, чтобы убедиться, что функции, которые мы используем, были надежными и стабильными.

Когда мы проводили тесты для использования SDK в браузере, мы заметили, что система модулей несовместима с тем, как Babel транслировал код, и поэтому нам нужен был способ, чтобы тот же самый код был совместим с прямым использованием в браузер. После некоторого исследования мы поняли, что лучшим вариантом было использование webpack для объединения исходного кода в статические ресурсы, которые можно было использовать в браузере. Еще раз, чтобы обеспечить обратную совместимость, Babel использовался (с той же предустановкой) для транспиляции кода и его использования обратно в IE11.

Позже в процессе разработки этого проекта один из первых пользователей нашего SDK заметил, что его установка со всеми последними зависимостями с использованием npm install вынудит его загрузить версии зависимостей, которые не были протестированы и вызвали сбой библиотеки. Мы быстро отладили его и обнаружили, что зависимости не были детерминированными, или, другими словами, мы не могли всегда определять, каким было наше дерево зависимостей, что создавало шансы на несоответствия при обновлении некоторых пакетов . Из-за этого мы решили использовать Yarn, который благодаря созданию файла блокировки (который необходимо добавить в репозиторий) создает подробное дерево зависимостей, в котором можно найти все пакеты зависимостей и их разрешенные версии. Из-за этого после выполнения yarn install, независимо от того, где и когда вы это делаете, он гарантирует, что у вас установлены точно такие же зависимости и подресурсы, что устраняет проблему, которую наш пользователь обнаружил в первую очередь. К тому времени, когда мы выпустили SDK, последняя версия npm (v5) уже могла решить ту же проблему.

Документация

Следуя еще одному из наших установленных принципов, мы решили, что с самого начала мы должны задокументировать каждую функцию и класс, которые мы передадим в репозиторий. Мы сделали это, потому что это хорошо известная передовая практика для всего сообщества разработчиков, и если мы не сделаем это с самого начала, это будет чертовски скучно.

Мы приняли JSDocs, потому что он казался нам наиболее подходящим для наших нужд, а также очень гибким и полным. Это пример того, как мы документируем наш код в этом проекте:

А это пример вывода:

Тесты

Тестирование в значительной степени является краеугольным камнем любого программного проекта. Чтобы построить на основе того, что у вас есть, вам нужно убедиться, что он достаточно стабилен, и добавление тестов в код гарантирует именно это. Кроме того, это в значительной степени хороший принцип для всего сообщества, и здесь, в Bynder, у нас нет проблем с признанием:

Весь код виновен, пока его невиновность не доказана. - анонимный

Мы полностью осознаем, что несовершенство является частью человеческой природы, и именно поэтому с самого начала мы начали создавать тесты для всех без исключения общедоступных методов SDK. Процесс выпуска возможен только при прохождении всех тестов.

Для создания наших тестов мы решили использовать хорошо известный фреймворк для тестирования BDD Jasmine, поскольку он очень легкий (не имеет внешних зависимостей), а синтаксис довольно прост, его легко читать и писать тесты. Что касается самих тестов, мы пошли на довольно неортодоксальный и, честно говоря, весьма сомнительный подход. Мы запускаем наши тесты в среде разработки в отслеживаемом цикле, что означает, что все сделанные изменения будут безопасно отменены, а среда завершится в том же состоянии, в котором она была запущена. Таким образом, мы можем протестировать SDK в любой среде без каких-либо рисков.

Возьмем, к примеру, диаграмму состояний теста metaproperties²:

Этот подход немного смешивает интеграционное тестирование с приемочным тестированием, поскольку мы тестируем поведение API и в то же время гарантируем, что методы SDK делают именно то, что они должны делать. Есть явные неудобства, а также некоторые преимущества, но, в конце концов, все это был вопрос выбора, и особенно, поскольку у нас была большая свобода в этом проекте, это был способ попробовать не такой традиционный подход.

Процесс выпуска

Последним, но не менее важным этапом развития этого проекта стал релиз. Прежде чем пролить первые солнечные лучи на наш код, мы должны были убедиться, что все было идеально, весь код был исправен, и знаете что? Не было. Мы всего лишь люди, а люди совершают ошибки, поэтому нам нужен был способ отделить наши личные и интимные обязательства от того, что мы действительно хотели, чтобы люди увидели.

Поскольку мы столкнулись с этой проблемой уже при выпуске других общедоступных SDK, мы, по сути, использовали то же решение, которое довольно простое и к тому же очень эффективное. Метод был описан Дрю Олсоном в Техническом блоге Брейнтри и в основном заключается в наличии двух репозиториев, одного открытого и одного частного. Вы будете продолжать выполнять всю обычную работу в частной ветке и оставлять одну ветку только для выпусков. Когда вы чувствуете, что готовы к выпуску, вы сжимаете все свои неприятные коммиты в очень красивый и аккуратный, гарантируя, что все личные сообщения о коммитах останутся приватными и никогда не станут общедоступными. После этого вы просто помещаете эту ветку в общедоступный репозиторий et voilà.

Как только он появится, последний шаг - опубликовать его на npm и сделать его доступным для использования всему миру.

Заключительные соображения

Статья, которую вы только что прочитали, представляет собой реальный рассказ о проекте, над которым мы работали, и о том, как мы решили подойти к нему. Мы полностью отдаем себе отчет в том, что существуют разные альтернативы, и это только один из них, который доказал свою эффективность.

Он также предназначен для того, чтобы поделиться нашим видением проблемы и некоторыми принципами, которым, по нашему мнению, могут следовать наши разработчики. Были добавлены некоторые подробные сведения о разработке, чтобы добавить некоторую техническую ценность, не делая его слишком скучным для чтения. Мы знаем, что это важно.

В заключение, исходный код этого проекта полностью доступен на Github, и любой внешний вклад приветствуется.

[1] - [цифровой] актив - это нечто, представленное в цифровой форме, имеющее внутреннюю или приобретенную стоимость. Например, изображения, видео, документы, аудиофайлы и т. Д.

[2] - Метасвойства - это метаданные, которые вы используете для лучшей классификации и определения ваших активов. Они помогают классифицировать активы и выполнять поиск по ним.