Pull to refresh

Создание API

Reading time3 min
Views3.8K
Original author: John Nunemaker
Несколько недель назад, мы публично выпустили Gauges API. Несмотря на уже существующий Gauges, было не мало работы во время написания API. Необходимо подробно разобраться с деталями.

1. Пишите документацию во время создания API

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

2. Будьте постоянным

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

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

Замена массивов на хеши означала то, что нам нужно пространство имен (namespace) для массивов с ключами. Далее мы заметили, что не все имело свое пространство имен. И снова, мы придумали правило. В этом случае, все объекты верхнего уровня должны иметь пространство имен, но детям этих объектов или наборам нескольких объектов не обязательны пространства имен.
{users:[{user:{...}}, {user:{...}}]} // нет
{users:[{...}, {...}]} // да
{username: 'jnunemaker'} // нет
{user: {username:'jnunemaker'}} // да

Ну вы поняли. Постоянство ― важно. Всегда следует придерживаться одного формата.


3. Предоставляйте URL'ы

Большинство моей работы с открытым ПО было обертывание различных API. И одна вещь меня очень раздражала, это было генерирование url'ов. Каждый ресурс должен знать URL'ы важные для него. Например, пользователи в Gauges имеют несколько URL'ов, которые могут быть вызваны для получения некоторых данных:
{
  "user": {
    "name": "John Doe",
    "urls": {
      "self": "https://secure.gaug.es/me",
      "gauges": "https://secure.gaug.es/gauges",
      "clients": "https://secure.gaug.es/clients"
    },
    "id": "4e206261e5947c1d38000001",
    "last_name": "Doe",
    "email": "john@doe.com",
    "first_name": "John"
  }
}

Предыдущий JSON ― это ответ от ресурса /me. Он возвращает данные о аутентифицированном пользователе а также URL'ы для обновления себя (self), получения всей статистики (/gauges), и получения всех клиентов API (/clients). Предположим следующий ваш запрос /gauges. Каждая gauge (статистика) возвращает еще несколько адресов для получения дополнительной информации.
{
  "gauges": [
    {
      // various attributes
      "urls": {
        "self":"https://secure.gaug.es/gauges/4ea97a8be5947ccda1000001",
        "referrers":"https://secure.gaug.es/gauges/4ea97a8be5947ccda1000001/referrers",
        "technology":"https://secure.gaug.es/gauges/4ea97a8be5947ccda1000001/technology",
        // ... etc
      },
    }
  ]
}

Мы думаем это докажет свою полезность. И увидим в будущем будет ли это хорошо работать.

4. Представление данных

И наконец, никогда не используйте to_json и его друзей из контроллера или из блоков синатры get/post/put. После того как вы начнете вызывать to_json с :methods, :except, :only, или с другими опциями, вы возможно захотите переместить это все в отдельный класс.

Для Gauges, мы зовем эти классы презентерами (presenter). Например вот упрощенная версия UserPresenter.
class UserPresenter
  def initialize(user)
    @user = user
  end

  def as_json(*)
    {
      'id'          => @user.id,
      'email'       => @user.email,
      'name'        => @user.name,
      'first_name'  => @user.first_name,
      'last_name'   => @user.last_name,
      'urls'        => {
        'self'    => "#{Gauges.api_url}/me",
        'gauges'  => "#{Gauges.api_url}/gauges",
        'clients' => "#{Gauges.api_url}/clients",
      }
    }
  end
end

Ничего страшного. Простой руби класс, расположенный в app/presenters. Вот пример того, как маршрут для /me выглядит в нашем Синатра приложении.
get('/me') do
  content_type(:json)
  sign_in_required
  {:user => UserPresenter.new(current_user)}.to_json
end

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

Уверен, что ничего из выше написанного не было шокирующим или супер-вдохновляющим, но я надеюсь статья сохранит немного времени для вашего следующего публичного API.
Tags:
Hubs:
Total votes 62: ↑53 and ↓9+44
Comments19

Articles