Сегодня мы хотим поговорить о сокровенном — у нас есть API.

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



Процесс создания API uCoz иногда напоминал сюжет сериала The Knick («Больница Никербокер») — с неудачными операциями, кишками и экспериментами на живых людях.

Стадия первая – Отрицание


По концепции пяти стадий:
На первом этапе человек отказывается принимать то, что с ним случилось.

Вообще, API для конструкторов сайтов — редкость и сейчас. А в 2010-м такого инструмента не было еще ни у кого на рынке.

С одной стороны, определенная потребность в API была: мы видели, что люди активно гуглили “скрипты для uCoz”, получали запросы напрямую — часть пользователей хотела создавать свои дополнения и модификации. С другой стороны — и это была проблема, — в те времена все ресурсы были брошены на другие проекты.

Вопрос «быть или не быть» решился просто. API потребовалось нам самим — для запуска функции поддержки PHP в конструкторе. Мы выделили одного разработчика, и он за полгода сделал наше “начальное API” — это был get-only интерфейс, к которым можно было получать данные страниц из 11 модулей. Данные отдавались только в XML. К моменту запланированного анонса PHP мы не успевали наладить еще хотя бы добавление контента, но у API были и плюсы: с ним можно было запускать пхп-скрипты в бесплатном варианте юкоза.

В общем, мы вышли к пользователям. Получилось прям по классике:



Нас не очень-то приняли… Точнее, в основном хвалили. Но за PHP. А в самом “эпиай” люди не увидели того, что представлялось им при этом волшебном слове. В техподдержку посыпались вопросы: «Как добавлять материалы? Как редактировать? Как получить в json?» А этого всего не было.

Стадия вторая – Гнев


По концепции пяти стадий:
На этом этапе проявляется агрессия ко всему окружающему миру.

К вопросу, что API нужно развивать, в компании вернулись где-то через год. В приоритетах были мобильные клиенты — и мы решили писать все заново с учетом требований ребят, делавших клиенты для iOS и Android. Начальная версия осталась жить сама по себе (и даже до сих пор жива, потому что некоторые ей все же пользуются), а мы стали подбирать исполнителя на новый проект.

“Сам себе менеджер”. В ростовский офис как раз пришел толковый парень Илья: он знал Perl, на котором была написана часть старого uCoz, а когда ему по традиции предложили несколько задач, он выбрал из них работу над API. Проблема была в том, что на время парню пришлось стать самому себе менеджером.

Тут наступил гнев: «Как выяснилось в процессе, синтаксис Perl я понимал, а вот дух — нет.



Потому я долго пытался не работу, а мир лучше сделать: думал все переписать, объекты-классы-паттерны внедрить. Очень много времени ушло, чтобы освоиться с идеологией языка. И вот тогда то, что мне поначалу казалось страшным и хотелось переписать, стало казаться нормой».

К моменту просветления появился менеджер и стал требовать отчет. А была готова только отдача контента по нескольким модулям… В общем, еще раз гнев, теперь с другой стороны, — и Илью перевели на новый проект. Его версии API так и не суждено было увидеть свет.


… Между стадий ...


“Идеальная обучающая задача”. К тому моменту компания пробовала открыть офис R&D в Казани. На месте нужен был носитель знания о всей системе — и появилась идея “вырастить” его через работу над API, которое затронет основные модули системы.

Так в этой истории появился Ринат:



С одной стороны, он был в меру азартен, чтобы взяться за проект (он у нас вообще немного экстремал). С другой — в работе спокоен и рассудителен: все же за плечами не только 700+ прыжков с парашютом, но и 20 лет опыта в ИТ.

Он также был знаком с Perl и имел свежий опыт работы с чужими API — интегрировал «Метрику» и GA в панель веб-мастера.

Первое, что нам пригодилось, так это его рассудительность. Ринат попросил паузу, чтобы детальнее поразбираться в запутанных “кишках” системы, — и только затем озвучить сроки и план реализации. Спустя месяц он вышел с таким предложением:

* Переписываем и дублируем часть функций чисто под API — за время создания системы в ней скопилось много старого кода. И некоторые функции, попробуй ты туда еще что-нибудь засунуть, получились бы «километровыми». Значит, нужны подчищенные двойники чисто под API.

* Используем REST — для упрощения архитектуры запроса, что поможет увеличить производительность.

* Используем Oauth 1.0a — аутентификацию, показавшуюся самой защищенной на тот момент.

* Отдаем в разных форматах — JSON, XML, Text Plain.

* Ну и: get, post, put, delete, мир, труд, май…


Третья стадия – Торг


По концепции пяти стадий:
Тут у вас появляются мысли о том, чтобы договориться с кем-то о лучшей участи

К общему пониманию, как сделать удобно, мы вроде пришли. Но дьявол крылся в деталях и спорах.

Принципиальным вопросом стала область видимости токенов. Наша серверная система, как говорит Ринат, «это законченная инкапсулированная фиговина»: на каждом сервере крутится N юзеров, а когда место на нем кончается, берется новое железо.

Ринат хотел использовать один токен, чтобы получить доступ ко всем сайтам. “Ок, давай поговорим об этом”, — и мы позвали в скайп-чат еще коллег. Как это бывает в коллективных чатах, спорили-спорили, но к коллективному решению не пришли. А в угаре разработки тема подзабылась и вновь всплыла, когда была готова интеграция для половины модулей.



Иного варианта, как тиражировать токен, у нас не было: так появилась архитектура с одним основным авторизационным сервером — получив один токен там, его можно использовать везде.

Оформить как массив или объект? Для нас это была не проблема разряда “что правильно, а что нет”. Это была проблема деталей.

Например, мы отдаем данные в JSON — и возникает проблема с типизированными языками. Не все структуры, полученные от API, удобно распарсить — потому что увеличивается количество кода на клиентской стороне.

А ведь API ориентировано на веб-приблуды, поэтому мы прислушались к мнению разработчиков на Java и C++ и пришли к стандарту: поля отдаем в любом случае, именованные параметры дополняем кодом.

Поля и параметры. Тут торги и обсуждения шли все время реализации. Например, стоит ли выдавать пустой параметр? Поскольку мы ориентировались и под мобильную разработку с использованием API, Ринат в процессе доказал — не нужно: ведь в мобильных приложениях важен трафик.

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



Четвертая стадия –… (и о сроках)


По концепции пяти стадий:
На четвертой стадии у вас наступает депрессия. [Мы как-то миновали этот этап]

Работа, запланированная на год, заняла почти два. В процессе возникали совсем непредвиденные сложности. Мы быстро поняли, что:



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

Мы расписали гугл-док, поставив модули в алфавитном порядке. Работу по ним разделили между исполнителями пополам. Определили график — один модуль в месяц от каждого. Когда пора было приступать, второй человек ушел. А мы уже делали новый проект — конструктор uKit, на который бросили основные ресурсы. С потерей второго программиста, к сроку реализации добавились почти 7 месяцев.

Проблемы с тестированием. В теории процесс задумывался таким: после сервера разработчика все уходит на альфа-сервер, а если тестер говорит “давай дальше”, то проходит бета-тестирование и обновление разливается по сотне серверов.

Но оказалось, что наши обычные тестировщики не очень подходили для работы с API — ведь они “заточены” под тестинг веб-страниц. API же — тонкая вещь. Например, когда происходило изменение внутри модуля, с которым мы интегрировались, что-то могло отвалиться — но тестировщики сначала этого не замечали (потом мы их научили).

Тогда мы открыли 4 тестовых сервера и пригласили продвинутых пользователей на самых ранних этапах. Такое крауд, а потому не очень контролируемое (ты не уверен, когда человек что-то сделает и не бросит ли), тестирование тоже сказалось на увеличении сроков.


Пятая стадия – Смирение (и выводы)


По концепции пяти стадий:
По канонам, наступает согласие с неизбежным

В конце концов, мы смирились с неизбежным — API, как и ремонт, можно начать, но не так-то просто закончить. Вот несколько рецептов, как организовать процесс, чтобы вам было проще.

1. Наладьте обратную связь. Больше связи.

Помимо ветки на форуме и постов в блог, т.е. публичных средств обмена комментариями, я завел на сайте с новой документацией раздел “Лаборатория”. По сути, это была форма обратной связи, где можно было обсудить свой случай в личной переписке.

О новом API мы сообщили ранним пользователям в феврале 2015-го, а процесс выкатки всех модулей на сервера завершили лишь в текущем году. Все это время мы получали через “Лабораторию” репорты, предложения и интересные случаи от пользователей (которые я в том числе использовал в «торгах» с Ринатом).


Поток обращений снизился лишь в последние два месяца

Каждый раз, получив обращение «как сделать вот это или как тут поступить», мы выделяли время и садились смотреть, а как же лучше? Иногда из этого рождались интересные истории.

2. Лично помогите тем, кто не разобрался или [Скрипт в подарок].

Бывало, сообщение из “Лаборатории” заинтересовывало настолько, что мы начинали писать скрипт сами. А затем дарили готовое решение пользователю,

В чем профит? Как говорится, «хочешь найти баг – будь багом». А если хочешь погрузиться в проект – сделай на нем как можно больше всего, чтобы проверить работоспособность и удобство. Заодно так на бирже uScript появились первые решения с использованием uAPI — авторизация через соцсети и неглючный вывод картинок в блоке рекомендованных материалов.

3. Проведите внутренний хакатон.

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


Ринат обычно не общался напрямую с пользователями, но на хакатон к ростовским разработчикам приехал из Казани. Говорит, “за исключением троллинга, остался доволен собранными в процессе данными и критикой”.


4. Попробуйте что-нибудь автоматизировать.

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



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

5. Советуйтесь и меняйте документацию.

Изначально мы расписывали пример запроса в PHP и CURL.


Так было. Как выяснилось в процессе, CURL никто не пользовался.

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

Мы решили, что должен быть дополнительный модуль (написан по современному, ООП), где будет автоматически генерироваться подпись для oauth. Один раз вызвал этот модуль – и далее просто пишешь путь и метод запроса.

Параллельно я прошелся по нашим программистам и спросил – документацию к каким API они считают достойным примером для подражания? Посмотрел рекомендованные примеры и с их учетом составил новую версию документации:



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

6. Реализуйте мобильность.

Во-первых, это поможет получать хорошие отзывы и расширит вашу аудиторию:

Давно была мечта о создании мобильного приложения для своего сайта. Буду изучать uAPI, чтобы наконец-таки воплотить ее в жизнь

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


Тут можно посмотреть код приложения

7. Не ограничивайте желания пользователей.

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



Наконец, мы пришли к тому, что API — это двустороннее движение. Сегодня с помощью uAPI можно сделать авторизацию для любого сайта, переносить материалы с uCoz и наоборот, использовать только нашу БД, но выводить данные на стороннем сайте. Чем больше юзкейсов вы вбираете в инструмент, тем дольше реализация. Но одновременно растет и область его применения.


P.S.

Весь процесс выкатки и боевого тестирования новой версии занял у нас 14 месяцев и 20 обновлений. Вот визуализация.

Бывает, после очередного обновления нам пишут: “Когда же вы его уже допишете?” Но процесс и правда очень сложно остановить (мы не шутили про ремонт). Иногда — по техническим причинам: скажем, когда апдейт системы требует изменений в API. А иногда — по творческим. Например, сейчас мы думаем: когда все интеграции с модулями закончены, почему бы не проработать темы изменения дизайна и настроек сайта по API?

Комментарии (22)


  1. wentout
    25.04.2016 16:04
    +4

    Привет :)
    Спасибо за трепетную душешипательную историю, занятно написано :)

    Смотрю вот на Суть.

    Интересно, что под капотом?
    Оно как-то само собирается и т.п. и т.д., или есть ещё к этому редактор?

    М.б. чем-то пользуетесь/планируете и т.п., например:
    Swagger, apiary или apiblueprint?


    1. Dmitry_DM
      25.04.2016 18:18
      +1

      Интересно, что под капотом?
      Оно как-то само собирается и т.п. и т.д., или есть ещё к этому редактор?

      Под капотом PHP в связке с MySQL.

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

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


    1. Dmitry_DM
      25.04.2016 20:27

      М.б. чем-то пользуетесь/планируете и т.п., например:
      Swagger, apiary или apiblueprint?

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


  1. Big_Shark
    25.04.2016 19:26

    Распространения php модуля для API по средствам zip? Думаю лучше залить его на гитхаб, и добавить на packagist?
    Также не увидел тестов, и неймспейсов, да и вообще будем говорить на на чистоту, код на пхп явно попахивает.
    Да там еще и global (((


    1. Fedcomp
      25.04.2016 19:39
      +1

      Это же ucoz


    1. Dmitry_DM
      25.04.2016 20:15

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


      1. Big_Shark
        25.04.2016 20:24

        То есть вы говорите что ваша аудитория застряла году эдак в 2010 и это нормально?


        1. Dmitry_DM
          25.04.2016 20:41
          +2

          Нет, я о том, что фломастеры разные. Можно сравнить с культурой разных городов, стран.

          Возможно, нам стоит задуматься на тему – документация с github, документация с zip. Как я и говорил, это тема, которую сложно перестать развивать


          1. dima_smol
            25.04.2016 21:26
            +3

            Все можно хранить на гитхабе, если версионировать, то можно давать ссылки на zip, который генерит гитхаб


            1. Dmitry_DM
              26.04.2016 15:25

              Отдельное спасибо за подсказку. Будем внедрять в самое ближайшее время


  1. lowadka
    25.04.2016 20:17
    +2

    Заголовок не оправдывает ожиданий.

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


    1. spasibo_kep
      25.04.2016 21:23

      Илья, пока две версии API живут отдельно (у старой есть свои пользователи, их не стали трогать), но если столкнемся с вашими вопросами, обязательно расскажем.

      Надеемся, опыт «было-стало» тоже может быть полезен.


    1. alaska332
      26.04.2016 00:38

      Да, я тоже не понял что же они поняли?


      1. Dmitry_DM
        26.04.2016 22:42
        +1

        Коротко: мы поняли, как делать удобнее. А я — как стать лучше:

        1) Когда я начал заниматься им, собирал информацию, о чем просили клиенты когда-то: какие-то дополнения, фишки, скриптики. И в процессе создания АПИ я понял, что это все возможно. Нет ограничений, ты свободный художник. Вопрос лишь в ресурсах (ну и борьбе с ленью, конечно).

        2) Понял, как интересно общение с клиентами. Это невероятное чувство, когда тебе клиент пишет какую-то задумку, и вместе с ним ты погружаешься в эту идею и помогаешь ему, в том числе с кодом.

        3) Ну и пару лет назад не поверил бы, что смогу написать свою систему управления документацией. И это [извините]


  1. jMas
    26.04.2016 00:47
    +3

    Но оказалось, что наши обычные тестировщики не очень подходили для работы с API — ведь они “заточены” под тестинг веб-страниц. API же — тонкая вещь.

    Покрыть API тестами и прогонять их каждый раз чтобы понимать что ничего не сломалось — не подходит? Как по мне — мануальное тестирование для API — это перебор. Или что нужно понимать под тестированием? Закрытая публичная версия?

    Например, когда происходило изменение внутри модуля, с которым мы интегрировались, что-то могло отвалиться — но тестировщики сначала этого не замечали (потом мы их научили).

    Судя по всему, автоматизированным тестированием занимаются мануальные тестеры. Хм…


    1. Dmitry_DM
      26.04.2016 15:20

      «А запрос там вообще не отвалился? Он вообще что-нибудь отдает?» — это случай для автотестов. Но, как я писал в постскриптуме, само по себе апи очень чувствительно относится к любым имениям для веб-версии.

      Например, после недавнего обновления мы обнаружили забавный баг: ответ от API приходил и вроде бы все хорошо… но данные для одного поля он выдавал вообще левые. Вопрос – а как быть в таких случаях? Пока лучше ручного теста – не нашли. Заодно ребята-тестировщики обучаются составлению запросов и другим интересным вещам.


      1. saw_tooth
        26.04.2016 15:37
        +1

        >>ответ от API приходил и вроде бы все хорошо
        Это как понять? Вы считаете код 200 ответом, или я не так Вас понял?

        В первую очередь Ваш API должен возвращать данные, написанные в вашей спецификации к нему — она ведь у Вас есть?
        Второе, это кольцевое тестирование. Когда за запрос к серверу дублируется запросом в базу а результаты сравниваются, естественно такое тестирование не всегде покроет все случаи, но скажем простые GET/POST/UPDATE/DELETE запросы к данным он проверит идеально.

        PS. Я глубоко удивлен Вашем повествованием о тестеровщиках, которые толи не знают, толи не понимают как создать HTTP запрос… боюсь спрашивать о использовании таких инструментах как Jmeter/SoapUI вашими тестеровщиками.


        1. Dmitry_DM
          26.04.2016 16:25
          +1

          Нет, речь не про 200 код. Я про то, что если в ответе пришло что-то наподобие success или большой набор полей – понятно, что запрос не отвалился. Вот понять, что «он скорее жив» или наоборот – это уже следующая задача.

          Покажу на примере, про который говорил:
          image

          Поступало:

          "goods_list":"<table><tr><td>текст</td></tr></table>
          


          Должно было:

          "goods_list":{
                   "1":{
                      "entry_is_in_discount":1,
                      "entry_weight":{
                         "weight":0,
                         "weight_raw":"0.0000"
                      },
                      "entry_stock":{
                         "stock_total":"0",
                         "stock":3
                      },
                      "entry_brief":"Если вам нужен красивый и запоминающийся UIN, этот товар для Вас.\r Данный товар имеет тип \"Электронный код\". Это означает, что пользователь после оплаты получит код, указанный при создании товара. Один код может быть куплен один раз."
          


          Думаю, ваш вариант по поводу кольцевого тестирования – да, вероятно, помог бы в этой ситуации. Буду думать, спасибо.


          1. jMas
            29.04.2016 15:17

            Чем «честнее» апи, тем лучше. То есть если вы отдаете 200, когда на самом деле все плохо — это ведь не очень хорошо, верно?


      1. jMas
        26.04.2016 15:58

        В таком случае пишется тест:
        1. Первый запрос на добавление / изменение данных
        2. Второй запрос на получение данных и проверку корректности данных (вы должны получить те же данные, что и сохраняли, проверить по определенным признакам, иногда прибегая к регулярным выражениям или косвенным признакам — наличии определенной последовательности символов / данных)
        Либо если запрос сразу возвращает ответ — проверка полученных в ответе данных.
        Или вы пытались описать какую то иную проблему?


  1. saw_tooth
    26.04.2016 01:40
    +1

    Не хочу показаться занудой, но кажется Вы только что сделали то, что нужно было еще вчера.
    Про тестеров доставило.


  1. Calc
    26.04.2016 13:28
    +2

    может и ошибусь, но странно в современном мире видеть Map (php-array) структуру данных для API
    Если у вас строгий запрос — создайте класс, люди будут вам благодарны.
    Определите полностью модель поведения и спроектируйте интерфейс.
    Создайте реализацию интерфейса на рандомных данных, обвесьте тестами рандомные данные.
    Делайте реализацию интерфейса.
    На endpoint API вешаете версионность через враппер интерфейса.
    Мы на своей команде тестировали.
    У нас джуниор нулевой написал приложение (мобильное) как аналог whatsapp в качестве задачи по обучению (изображения + медиаданные) за 2 дня. Сервер пилился по мере написания приложения. (но интерфейс api и рандомные данные были готовы)
    Делали аналог rpc
    Response: {status:code, error:null, data:{somedataobject}}
    В поле data приходят нужные данные.
    array в PHP это мощный инструмент, но он дает путь к множеству ошибок и непонимания со стороны разработчиков. Аналог array в Java это будет Map<String, ...>, в других языках это будет более сложный объект.
    Если вы следуете понятию Json-Объект, то лучше и создавать классы из которых будут порождаться объекты, на этом этапе у вас мобильный API из коробки будет (java-gson-retrofit). Ну а дальше не долго на какой либо protobuf перейти и транспорт сменить при желании.