Страх и ненависть при использовании Google Calendar API

| ~4 мин.
googlegoogle calendar apiграблиразработка

Лирическое отступление: когда я только-только входил в мир разработки программного обеспечения, я верил, что “взрослые” языки программирования и серьезные программные продукты, построенные профессионалами, лишены нелогичных и необъяснимых моментов и выверены настолько, насколько это возможно. Ох уж эта детская наивность… Эх.

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

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

Казалось бы, что может быть проще, да?

Решение, которое подсказывает интуиция link

Найти какой-то метод API в документации Google Calendar API, который позволил бы подписаться на изменение события в календаре. Хотелось бы, чтобы этот метод в теле запроса принимал URL вашего веб-хука, на который вы хотели бы принимать оповещения об изменении события с определенным ID.

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

Решение, продиктованное суровой реальностью link

Есть чудесный метод: /events/watch. Как можно заметить из HTTP-запроса, нас уже вряд ли ждёт что-то хорошее:

POST https://www.googleapis.com/calendar/v3/calendars/calendarId/events/watch

Как мы знаем из обычаев, принятых в REST API, такая запись говорит о том, что мы будем наблюдать за какой-то коллекцией событий. Мой мозг в этот момент отчаянно отвергал мысль, что нам будут прилетать оповещения обо всех изменениях во всех событиях календаря, но…

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

{
  "id": string,
  "token": string,
  "type": string,
  "address": string,
  "params": {
    "ttl": string
  }
}

Негусто. Всё ещё нет никаких упоминаний того, что можно как-то ограничить коллекцию наблюдаемых ресурсов. Мелькает какой-то token, но вряд ли это то, что нам нужно. Ладно, смотрим дальше.

{
  "kind": "api#channel",
  "id": string,
  "resourceId": string,
  "resourceUri": string,
  "token": string,
  "expiration": long
}

Так, видим, что в ответ на запрос приходит всё-таки какой-то ID ресурса. Документация не очень внятно поясняет, что же это за ресурс такой: resourceId: An opaque ID that identifies the resource being watched on this channel. Stable across different API versions.. Ну ладно, может и нельзя, но может есть возможность хотя бы посмотреть, какое и где там у нас изменение произошло, и фильтровать те оповещения, которые нас не интересуют? Так, а где на этой странце вообще что-то о том, что нам будет приходить после выполнения запроса?

В результате непродолжительного поиска удалось найти страницу Get Push Notifications. Google здесь явно дает понять, что “левым” разработчикам со своими приложениями тут не рады: запросы от watch будут отправляться только на домены, у которых есть SSL-сертификат. Никаких вам тут localhost! В общем-то, это понятное ограничение, но всё равно несколько неудобно, что нет никакого dev-режима, чтобы понять, как оно всё-таки работает.

Бегло просматриваем дальнейший текст, наполненный терминами, и попадаем на следующий пример, который говорит о многом:

  POST https://mydomain.com/notifications // Your receiving URL.
  Content-Type: application/json; utf-8
  Content-Length: 0
  X-Goog-Channel-ID: 4ba78bf0-6a47-11e2-bcfd-0800200c9a66
  X-Goog-Channel-Token: 398348u3tu83ut8uu38
  X-Goog-Channel-Expiration: Tue, 19 Nov 2013 01:13:52 GMT
  X-Goog-Resource-ID:  ret08u3rv24htgh289g
  X-Goog-Resource-URI: https://www.googleapis.com/calendar/v3/calendars/my_calendar@gmail.com/events
  X-Goog-Resource-State:  exists
  X-Goog-Message-Number: 10

Пример ответа от сервера

Нет никакого контента внутри оповещения? Окей. ID канала? Ну да, это полезно, тем более, что ID канала можно задать ручками и знать хотя бы, что это за канал такой. Resource-ID? Да он даже с ID календаря не совпадает! Channel-Token? Не очень понятно его назначение, если канал можно идентифицировать по паре (Channel ID, Resource ID). И, в общем-то, на этом всё. Google Calendar API действительно присылает только вот такой набор заголовков при каждом изменении календарных событий, и это всё. Ни sync-token’а, чтобы послать запрос и увидеть новые изменения, ни ID изменившихся событий нет. В общем-то, нет ничего, что могло бы облегчить жизнь рядового разработчика. Зато есть X-Goog-Message-Number, чёрт возьми!

Короче говоря, далее по каждому чиху приходилось смотреть в свою базу данных, искать там календарь с заданными channel_id и resource_id, а затем присоединять к нему все события, которые не были приняты. После этого по одному вытаскивать ответы из Google Calendar API для таких событий и записывать результат в базу данных. Логика упрощена, но суть ясна. Очень неудобно, очень непрактично, очень странно.

После всего этого у меня остался только один вопрос: Google, почему ты так ненавидишь разработчиков?