Статьи

Непрерывная обратная совместимость

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

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

обзор

Наша команда разработчиков имеет два основных направления работы: наш шлюз и наши клиентские библиотеки . По мере того, как мы разрабатываем новые функции для шлюза, мы поддерживаем как ветки master, так и ветки release в git. Ветка релиза используется для исправления ошибок и отслеживания кода, который в данный момент находится в производстве. В основной ветке происходит разработка новых функций.

Рабочий процесс разработки для клиентских библиотек более сложен, но для целей этого поста мы примем ветку master и release для каждого языка. Мы также помечаем наш код перед каждым выпуском. Более подробное обсуждение процесса выпуска нашей клиентской библиотеки смотрите в нашем предыдущем посте .

обзор git

Общее решение

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

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

Небольшие сборки

С каждой клиентской библиотекой связана одна небольшая сборка. Эта сборка запускает тесты на главной ветви клиентской библиотеки по отношению к главной ветви нашего кода шлюза. Эти сборки запускаются двумя способами:

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

небольшие сборки

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

Big Build

Небольшие сборки хороши тем, что они запускаются быстро и часто, но у нас все еще есть проблема. Что, если это небольшое изменение, которое мы вводим в шлюз, разрушает библиотеку python, которую мы выпустили 3 месяца назад?

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

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

обзор git

Сначала мы клонируем репозитории git для шлюза и каждой клиентской библиотеки.

sh "git clone git@gitserver:gateway.git"
LANGUAGES.each do |language|
  sh "git clone git@gitserver:client-library-#{language}.git"
end

Затем мы переключаемся на ветку выпуска шлюза, перейдя в каталог шлюза и выбрав соответствующую ветку.

Dir.chdir(GATEWAY_ROOT) do
  sh "git checkout origin/release"
end

Для каждой библиотеки мы теперь перебираем все выпущенные теги, используем git, чтобы проверить этот тег и запустить задачу rake по умолчанию. Эта задача rake запускает набор тестов этой библиотеки для ветки выпуска шлюза.

def release_tags
  `git tag -l`
end
def clean_working_copy
  sh "git reset --hard"
  sh "git clean -d -x -f"
end
LANGUAGES.each do |language|
  Dir.chdir("client-library-#{language}") do
    release_tags.each do |tag|
      clean_working_copy
      sh "git checkout #{tag}"
      sh "rake"
    end
  end

Наконец, мы переключаемся на главную ветвь шлюза и повторяем предыдущий шаг.

Dir.chdir(GATEWAY_ROOT) do
  sh "git checkout origin/master"
end

Управление временем сборки

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

def end_of_life_tag?(language, tag)
  {
    "dotnet" => %w[tag_1 tag_2],
    "java" => %w[tag_1 tag_2],
    "php" => %w[tag_1 tag_2],
    "python" => %w[tag_1 tag_2],
    "ruby" => %w[tag_1 tag_2]
  }.fetch(language, []).include?(tag)
end
LANGUAGES.each do |language|
  Dir.chdir("client-library-#{language}") do
    release_tags.each do |tag|
      next if end_of_life_tag?(language, tag)
      clean_working_copy
      sh "git checkout #{tag}"
      sh "rake"
    end
  end
end

Заворачивать

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