Пишем API gem: выбор структуры и инструментов

Illustration of a construction site with a crane lifting a star-shaped structure, a barricade with orange and white stripes, a pile of dirt, and construction tools. Illustration of a construction site with a crane lifting a star-shaped structure, a barricade with orange and white stripes, a pile of dirt, and construction tools.

Первые действия очень простые: создать репозиторий и инициализировать в нём простой гем. Новый гем можно создать командой bundle gem имя_гема, а репозиторий создать на Github. Вот он, кстати: https://github.com/Fodoj/groovehq.

В первую очередь я добавлю возможность аутенфицировать запросы к GrooveHQ API, а затем напишу минимальный необходимый код для получения списка всех тикетов. К счастью, документация у API этого сервиса подробная и понятная, поэтому произвести один GET-запрос труда не составит.

Минимальный клиент

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

Я начну с того, что напишу небольшой класс GrooveHQ::Client, который будет ответственен за проведение запросов к API. Конструктор этого класса будет принимать access token.

# ./lib/groovehq/client.rb

module GrooveHQ

  class Client

    def initialize(access_token = nil)
      @access_token = access_token || ENV["GROOVEHQ_ACCESS_TOKEN"]
    end

  end

end

Как обращаться к API

Теперь нужно разобраться, как обращаться к API. До этого момента я никогда не использовал гем httparty для проведения запросов. Мой опыт ограничивается библиотеками RestClient и Faraday. Не думаю, что есть большая разница, какую библиотеку использовать, но чтобы сделать процесс более интересным выберу httparty. Тем более что у него звёздочек на GitHub много :)

На самом деле, терпеть не могу Faraday.

Добавляю следующую строчку в groovehq.gemspec:


# ./groovehq.gemspec

  # ...

  spec.add_dependency httparty

  # ...

и выполняю bundle install. Осталось только подключить httparty внутри ./lib/groovehq.rb:

# ./lib/groovehq.rb
require "httparty"
require "groovehq/version"
require "groovehq/client"

module GrooveHQ
  # Your code goes here...
end

Делаем первый запрос к API

Чтобы проверить, что всё работает как нужно, я добавлю метод perform_request, который будет принимать путь к точке API и возвращать JSON с результатом запроса. Для авторизации я буду использовать HTTP заголовок Authorization, так, как это указано в документации к API. Вариант с query параметром мне не нравится, так как он не будет работать для POST-запросов.

# ./lib/groovehq/client.rb
# ...
    def perform_request(path)
      url = "https://api.groovehq.com/v1/#{path}"
      response = HTTParty.get(url, headers: { 'Authorization' => "Bearer #{@access_token}" })
      JSON.parse(response.body)
    end
# ...

Проверим, всё ли работает как нужно, выполнив в папке с гемом команду bundle exec irb. Затем один за другим выполним следующие куски кода:

require "./lib/groovehq.rb"
client = GrooveHQ::Client.new("your_access_token")
client.perform_request("me") # me обычно отвечает за возврат данных о владельце токена

В результате, если использован корректный access token, ты получишь в консоли хэш примерно такого вида:

{"agent" => 
  {
    "email" => "fodojyko@gmail.com", 
    "first_name" => "Kirill", 
    "last_name" => "Shirinkin", 
    "href" => "https://api.groovehq.com/v1/agents/fodojyko@gmail.com", 
    "links" => {
      "tickets" => {
        "href" => "https://api.groovehq.com/v1/tickets?assignee=fodojyko%40gmail.com"
      }
    }
  }
}

Кажется, у нас есть минимально работающая версия гема! Коммит с изменениями здесь: f7d9eef.

Метод #perform_request создан не более чем для дебаггинга, поэтому он не содержит какого-либо более правильного создания строк-ссылок.

Добавляем структуру

Изучив документацию к httparty, я увидел, что этот гем позволяет писать простые и красивые классы, ответственные за проведение запросов. Можно указать глобальные настройки для каждого запроса (base uri, заголовки и т.п.), а каждый метод класса будет ответственен за какой-то конкретный запрос.

Переписанный GrooveHQ::Client таким образом выглядит так (метод #perform_request уже не нужен):

module GrooveHQ
  class Client
    include HTTParty
    base_uri "https://api.groovehq.com/v1"
    format :json

    def initialize(access_token = nil)
      access_token ||= ENV["GROOVEHQ_ACCESS_TOKEN"]
      self.class.default_options.merge!(headers: { 'Authorization' => "Bearer #{access_token}" })
    end

  end
end

Теперь нужно добавить отдельные методы для каждой API точки. Чтобы не городить десятки методов прямо в классе GrooveHQ::Client, я разобью их на отдельные модули, где каждый модуль соответствует определённому ресурсу.

Точно так же это реализовано в геме octokit, где я и подглядел этот подход.

Добавляю папку lib/groovehq/client с файлом tickets.rb:

# ./lib/groovehq/client/tickets.rb
module GrooveHQ
  class Client

    module Tickets

      def tickets(options = {})
        response = self.class.get("/tickets", { query: options })
        response.parsed_response["tickets"]
      end

    end

  end
end

И подключу его:

# ./lib/groovehq/client.rb
require "groovehq/client/tickets"

module GrooveHQ

  class Client
    include HTTParty
    include GrooveHQ::Client::Tickets
# ...

Эксперимент в irb показал, что всё работает как нужно и первый API endpoint обёрнут гемом и готов к использованию.

client = GrooveHQ::Client.new("ACCESS_TOKEN")
client.tickets => возвращает массив тикетов

Коммит: 61f5e78

Что дальше?

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