image

Как создать АПИ для умных? Такое апи, чтобы создание клиента для него было не скучным механическим процессом, а настоящим приключением с элементами детектива, хоррора и мистики? Такое апи, о котором пользователи будут взахлёб рассказываете коллегам? Апи взрывающее мозг, заставляющее смеяться, кричать и плакать? Я постарался отобрать лучшие практики, с которыми пришлось столкнуться.




Делай так


  1. Избегай очевидных названий. checkInDate лучше записать как start_date, еще лучше как sd, еще лучше как d. Тогда длинное и некрасивое checkInDate/checkOutDate превратится в лаконичное d1/d2. Еще больше отличных идей по именованию можно почерпнуть в книге “Совершенный код” в главе “Сила имен переменных”
  2. Не используй стандарты. ISO 8601? Чтобы преобразовывать дату с использованием стандартных библиотек, имеющихся в любом языке программирования и не написав ни одной регулярки? Скукота.
  3. Не используй в названиях термины предметной области. Лучше придумай что-нибудь максимально абстрактное.
  4. Придумай свою систему обозначения состояний. К примеру: 1,2,3 — есть, нет, под заказ. Не вздумай писать состояния напрямую и уж тем более, никогда не используй справочник состояний.
  5. Передавай вложенные структуры как список плоских связанных через внутренние айдишники, сгенерированные специально под этот ответ.
    {
    Jacket: \[{MaterialsIds=”m1,m2”, Pockets: “p1”, Price: 12000}\], 
    Materials: \[{Name: “Stormscale”, Color: “Tear of Elune”,  InnerId=”m1”}, 
                 {Name: “Feather of Valor”, Color: “unpredictable”, InnerId=”m2”}\], 
    Pockets:\[{Type: “Invisible”, Contents: “Soap”, InnerId="p1"}\]}
    }
  6. Не повторяйся.


    a. Придумай разные системы для обозначения состояний разных объектов. К цифровой, описанной выше, можно добавить цветовую: green, red, yellow.


    b. Меняй не только формат данных, но и формат записи названий полей — srart_date: 25/12/05, endDay: 2012.11.23.


    c. Не забудь про огромный выбор разделителей: запятая, точка, точка с запятой. Добавь в него что-нибудь нестандартное — знак процента или решетку.


    j. Список можно продолжать, будь креативен.


  7. Замени True/False на 1/0, а ещё лучше на 0/1. Превратив простое “has_goods: True” в четыре вопроса — айдишник? количество? просто ‘да’? или, таки, “нет”?
  8. Используй битовые флаги. Так ты продемонстрируешь свою исключительную образованность. Переведи их в десятичную форму для меньшей очевидности. Не передавай маску вместе с флагами, или даже в отдельном словаре. “flgs: 16” в структуре и список флагов в документации — прекрасный вариант.
  9. Старайся засунуть в каждое поле как можно больше смыслов.
    has_goods может принимать значения [-2, -1, 0, любое положительное число], 
    что значит: [уточните, нет в продаже, нет в наличии, количество товара в наличии]
  10. Добавьте в запрос обязательный параметр, у которого может быть только одно значение.


  11. Добавь в запрос параметр, смысл которого будет менять в зависимости от значения другого, необязательного, параметра.
    http://example.com/myService?cloth_type=silk&and_better=1
    Где  "cloth_type"  - обозначает конкретный тип ткани (тут, естественно, требуется название ткани строкой, даже если есть справочник тканей, в котором указаны их айдишники), а "and_better" делает его началом диапазона с константным концом. Для “большей гибкости” можно дать параметру "and_better" второе значение (-1, 0, false, not) или добавить необязательный параметр "and_worst" и обрабатывать тот, который стоит первым/последним, либо сначала проверять наличие первого, а при его отсутствии - второго.
  12. Задавай диапазоны на перечислимых данных последовательным списком, а на данных, порядок которых определен только в твоём приложении, началом и концом диапазона, а еще лучше через дефис или другой разделитель по вкусу.
    http://example.com/myService?d=11.12.05&d=12.12.05&d=13.12.05&colors=green;yellow
  13. Используй однотипные названия для полей с разными типами данных.
    http://example.com/myService?cats=5&dogs=4&hamsters=0 где cats и dogs интовые поля обозначающие количество, а hamsters битовое поле обозначающее наличие.
  14. Не обрабатывай ошибки. Переданы несовместимые параметры? Пустой ответ должен стать достаточной подсказкой. Или отсутствие ответа. Или отправь эксепшн сгенерированный твоим приложением.
  15. Документация не нужна.


    a. Если пришлось документировать, пропусти наиболее “очевидные” места.


    b. Не усложняй документацию примерами.


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


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



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


P.S. Напишите о своих героях и их былинных деяниях в комментариях.

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


  1. greabock
    15.12.2017 14:33
    +1

    Soap, вы забыли про soap!


    1. alprk
      15.12.2017 16:32

      Soap который возвращает строку c JSON :)


      1. vlivyur
        15.12.2017 18:59

        В двух кодировках.


      1. lany
        16.12.2017 11:55

        Просто JSON в Soap — это скучно, такое почти в любой системе с достаточной историей можно найти. А вот когда строки в JSON — это сериализованные Java-объекты, закодированные в Base64, тут уже становится интереснее.


        1. Klajnor
          16.12.2017 14:28

          Из собственного опыта:
          Soap, в нем json, в нем имя файла и base64 строка, в ней zip архив, а в нем xml файл


          1. vlreshet
            18.12.2017 10:34

            Сказка на новый лад — иголка в яйце, яйцо в утке, утка в зайце, заяц в архиве, архив в Base64…


          1. ncopiy
            18.12.2017 13:58

            этот xml способен убить Кощея?


  1. vmm86
    15.12.2017 14:39

    Если явно не указать, что это "вредные советы", кто-то вполне может воспринять их как руководство к действию.-)


    1. OKyJIucT
      15.12.2017 23:33

      Пока не прочитал первое предложение первого пункта, я так и думал)


  1. ivaaaan
    15.12.2017 14:43

    Спасибо! Но не хватает практических примеров, как эти советы использовать.


  1. vlreshet
    15.12.2017 14:45

    И вовсе не обязательно присылать ответ в том же запрос! Это так скучно и банально. Выкладывайте ответы в виде файлов на FTP, которые нужно стягивать после запроса. Даже если там две строчки ответа. Так интереснее.


    1. izzholtik
      15.12.2017 18:22
      +2

      А что сразу 1С-то?


  1. Melkij
    15.12.2017 15:12

    Из своей практики работы со сторонними API:
    — сообщения об ошибках в нескольких разных форматах — plaintext, строка в json и массив в json. А в документации чёрным по белому формат описан и он один. Чтобы было совсем не скучно — у сообщения об ошибке нет машиночитаемого кода, только описание — которое для некоторых ошибок может возвращаться на разных языках.
    — разные размерности одних и тех же данных. Здесь amount — сумма в валюте клиента, а тут (той же самой сущности рекламного объявления!) amount — всегда в центах USD. О, или ещё лучше: если у клиента валюта «рубли» — то сумма в рублях, если USD — то сумма передаётся… — в центах! Для красоты саму валюту клиента через API не узнать никак.
    — в документации в принципе отсутствуют допустимые значения для строк-перечислений, при том как для параметров так и для возвращаемых значений. status => string. Ура! Пишем логирование всех приходящих статусов и разбираемся что есть что по ходу пьесы, т.к. список возможных получить так и не смогли.
    — запрос к сервису через API скидывает авторизацию пользователя, с ключом которого запрос выполнили — было крайне нескучно постоянно авторизовываться.
    — неожиданные ограничения API — например, у пользователя есть логин и email, в API надо передать email. И вдруг получаем от части пользователей багрепорт, где выясняется что по email только некоторых пользователей авторизовать нельзя. А по логину — нет возможности в API.
    — ошибиться в документации с написанием имени параметра. Ну то есть написать checkOutDate вместо реально используемого check_out_date
    — в ответе на ошибочный запрос с полусотней параметров сказать «some error occured» без указания, а что именно не так или хотя бы в каком параметре


    1. vmm86
      16.12.2017 13:23

      Когда тебе в ответе приходят поля вида Nom_bil_kn, cod_t или cod_h — разобраться бывает непросто как при наличии документации, так и при её отсутствии.-/


  1. azhira
    15.12.2017 15:13
    +1

    • Обновляй API как можно чаще: беззастенчиво удаляй лишние запросы, но добавляй новые (обязательные!) параметры в уже существующие. Уведомлять пользователей нет необходимости — сами увидят, когда все сломается.
    • Если ты так щедр, что предоставляешь тестовый сервис, то пусть он будет отставать от боевого. И работает раз в неделю. Хотя нет, работать ему необязательно.


    1. OKyJIucT
      15.12.2017 23:37

      А ещё можно тестить измененные методы на боевом сервере, отправлять фейковые ответы, а переданные данные не сохранять.


  1. Weks
    15.12.2017 15:17

    Забудь про версионирование API. Пускай каждую неделю/месяц разработчики сами подстраиваются и переписывают приложение под десятки breaking changes. Зато у них всегда будут твои новые фичи.


    1. vbif
      15.12.2017 18:29

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


      1. Weks
        15.12.2017 18:36

        Не уверен, что компетентен ответить на этот вопрос. Моя боль с клиентской стороны состояла в постоянном изменении структуры запроса/ответа, их полей и типов.


      1. Free_ze
        15.12.2017 18:50

        Обычно принято мажорную версию менять при любом breaking change. Ибо для клиента это может стать сюрпризом.
        Если бы обязательный сделали необязательным — ОК, обратная совместимость поддерживается.


  1. malltshik
    15.12.2017 15:32

    Тот случай, когда стыдно признаться

    про меня на хабре написали


  1. kosmonaFFFt
    15.12.2017 15:35

    В ответ на запрос отправлять «запрос обработан», а чтобы получить ответ, надо вызывать отдельный метод «дай_ответ», который будет отправлять ответ на какой-нибудь запрос (маппить запрос-ответ будет клиент API по айдишникам).


    1. lromanov
      17.12.2017 15:05
      +1

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


    1. ilnuribat
      17.12.2017 16:42

      вот это уже стандарт — есть HTTP код 202 accepted, который означает, что запрос принят на обработку
      и в лучших сценариях в ответе есть url, или иной способ получить статус действия


  1. dolphin4ik
    15.12.2017 15:50

    Если у автора фамилия не «Остер», то он явно его родственник


  1. QDeathNick
    15.12.2017 15:52

    checkInDate/checkOutDate

    Смотрел на эту конструкцию, как среда на пятницу.


  1. vesper-bot
    15.12.2017 15:53

    Очень похоже на описание внутренней структуры SAP'a.


  1. Avenger911
    15.12.2017 15:57

    Если нужно вернуть код ответа, отличный от дефолтного, посылайте
    { code: 403, message: "У вас нет прав" }
    с HTTP кодом 200


    1. NtsDK
      15.12.2017 19:27

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


    1. franzose
      19.12.2017 00:54

      Ага. Некоторые еще не видят разницы между POST, PUT/PATCH, DELETE. Типа «а нафига оно надо, если есть POST?».


  1. jonic
    15.12.2017 15:58

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


  1. Akuma
    15.12.2017 16:11

    Пфф. Все гораздо проще. Возьмем API ВКонтакте для примера:

    1. Введите маленький лимит на число запросов для замедления работы всех приложений.
    2. Возвращайте каптчу на запросы от серверного API и предлагайте показать ее пользователям, которые оффлайн.
    3. Когда лень показывать каптчу, просто возвращайте ошибку «Flood control», т.к. запросы к API посылаются, внимание, слишком однотипные (ведь в API нужно использовать как минимум 99% из доступных методов, хотя они вам не нужны).

    P.S. Извините, накипело.


  1. MetaDone
    15.12.2017 16:15

    example.com/myService?d=11.12.05&d=12.12.05&d=13.12.05&colors=green;yellow

    На сервер передастся d=13.12.05
    example.com/myService?d[1]=11.12.05&d[2]=12.12.05&d[3]=13.12.05&colors=green;yellow

    — так понятнее будет


    1. nosterx Автор
      15.12.2017 16:23

      Зависит от обработчика, вот обсуждение на SO.


      1. MetaDone
        15.12.2017 16:26

        если делать в духе статьи то вообще лучше так
        example.com/myService?d_1=11.12.05&d_2=12.12.05&d_3=13.12.05&colors=green;yellow


    1. Avenger911
      15.12.2017 16:25

      Отнюдь, нужно использовать первый вариант, а потом вручную парсить строку запроса на сервере, даже если бэкенд написан на PHP. Пусть программисты, которым поддерживать этот код после вас, поломают головы :-)


      Кроме шуток, встречал такое разок.


      1. Akuma
        15.12.2017 16:31

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

        Но это те еще страдания: 99.9% библиотек не поддерживают такой формат :)


        1. izzholtik
          15.12.2017 18:40

          Ещё можно внезапно передавать параметр одновременно в теле POST и в заголовке. Причём в теле date — начало, а в пути — конец диапазона ;)


  1. T-362
    15.12.2017 16:16

    Черт, пара пунктов исполнена, но это из-за легаси и обратной совместимости, честно!


  1. KirEv
    15.12.2017 17:18

    бесят эти ендпоинты и гэт-параметры,

    лучше эндпоинт писать в хедер

    MY-HEADER-ENDPOINT="/users"


    дада… именно в верхнем регистре, это подчеркнет властность над глупыми машинами!

    и гэт параметры:

    MY-HEADER-GET=«key1=value1<ITS-MY-SEPARATOR>key2=value2»


    так будет урл красивый! И данные спрятаны под капотом.

    А чтобы совсем не скучно было:

    1. эндпоинт хешируем: md5(base64(..))
    2. бекэнд смотрит таблицу эндпоинтов в md5(base64(..))
    3. если мд5-баса64 не найдет — статус писать 200-ОК, респонс писать {BAD} — и так чтобы было похоже на ясон, но не получилось прочитать как ясон, пусть знают как неправильные ендпоинты отправлять!!!
    4. обязательно нужно обернуть хедер гетов в какойнибуть base64, можно даже несколько раз, или свою функцию написать в свободное время

    ох, самое важное — отовсюду в коде стирать свои копирайты, емейлы, телефоны!…
    А еще лучше — вписать кругом контактные данные одноклассника, который у тебя булочки отбирал, будет знать!!!


    1. OKyJIucT
      15.12.2017 23:45

      Я бы посоветовал вам ещё использовать функцию crypt в php или ее аналог, если используете другой язык, чтобы для одинаковых строк хеш всегда был разным. Ну и на мой взгляд, здесь явно не хватает XML.


  1. bjornd
    15.12.2017 17:21

    В POST-запрос всегда одну половину параметров передавай в body запроса и другую как в query-параметры в URL.


    1. Dreyk
      15.12.2017 18:10

      та лаадно, там все равно на сервере все операции проводятся над $_REQUEST


      1. izzholtik
        15.12.2017 18:43

        А кто сказал, что они не могут пересекаться по именам?


  1. vbif
    15.12.2017 18:24

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

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

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

    Ещё — очень хорошо требовать определённый порядок полей в XML.


  1. vbif
    15.12.2017 19:05

    А вообще, какой-такой API, давайте мы будем присылать экселевские файлики по почте. Или на FTP.


    1. TheShock
      16.12.2017 02:03

      Хе, на почту. На 1.44 дискетку в дочерном офисе во Владивостоке с паспортом и бумажным заявлением с мокрой печатью. Если ексельник 100-метровый — архивируется и каждый архив кидается на дискету. Для самой смакотки вместо 43-й части можно повторно кинуть 42-у.


  1. iproger
    16.12.2017 01:51

    Надо было скомбинировать данные стороннего api и сервиса для создания api для нужд сайта. Сделал франкенштейна из этих данных и это угнетает. Как было бы сделать правильно?


  1. ilnuribat
    16.12.2017 02:10

    Зачем нужны коды ошибок?
    Только подробное описание, которое, может быть, разработчик поймет

    {
       errorMessage: "DB: uniqueConstraint error for asdf@email.ru"
    }


    1. shushu
      16.12.2017 10:34

      Вы слишком детально расписываете проблему!

      {
         errorMessage: "DB: uniqueConstraint error"
      }

      Так более чем достаточно! Сам несколько раз встреча подобное!


      1. vlreshet
        18.12.2017 10:38
        +1

        Так и вы переборщили!

        {
             success: false,
             errorCode: "24672b"
        }
        

        Всё, достаточно! А таблицу кодов прячем на 237-ую страницу мануала, ссылку на который нужно найти во втором мануале. Который высылают только по требованию, после оплаты dev-аккаунта. Через 4 дня.


        1. vlivyur
          18.12.2017 11:44
          +3

          Только этого кода там нет.


  1. vlsinitsyn
    16.12.2017 04:53

    ISO 8601 — это еще тот стандарт для любителей веселухи.
    Из практики, почти всегда можно найти вариант, который будет полностью соответствовать стандарту, но не поддерживаться или неправильно интерпретироваться конкретной библиотекой.
    Так что, да, указывайте просто ISO 8601, вместо например RFC 3339. Тестерам это реально доставит :-).


  1. bano-notit
    16.12.2017 14:57

    Это всё конечно хорошо, но вот в пункте 8 у меня появился вопрос: где же хранить сами флаги, если не в документации и в коде, использующем их?
    У меня вот иногда под флаги отдельный файл сделан, чтобы в нём хранить их… Чяднт?


    1. sasha1024
      16.12.2017 16:11

      Да, тут пункты, по-моему, несоизмеримы.
      Часть пунктов — реально жесть, но некоторые — скорее отображают неприятие автором некоторых вещей (которые, если их правильно готовить, в принципе неплохи (а если готовить неправильно, так что угодно сломать можно)).
      Хотя это юмор, так что требовать точности — вряд ли уместно.


  1. sasha1024
    16.12.2017 15:10

    Я лично таких API не писал, но, по-моему, некоторые из пунктов (или, точнее, некоторые из частей некоторых пунктов) — например: 5 («передавай вложенные структуры как список плоских»), 7 («замени True/False на 1/0» — только тогда, конечно, точные названия выбирать надо), 8 («используй битовые флаги»), 9 («[уточните, нет в продаже, нет в наличии, количество товара в наличии]» — если эти значения действительно взаимоисключаемые по логике приложения), 10 («обязательный параметр, у которого может быть только одно значение» — например, номер версии) — вполне приемлемые в определённых сферах или, по крайней мере, пусть и плохи, но на порядок лучше других (то есть степень вреда разных пунктов несоизмерима; например, если нет вменяемого отчёта об ошибке, отличимого от успешного ответа — это бардак; а если возвращается битовая маска, но она хорошо задокументирована, то это ок).


  1. kakutuzov
    17.12.2017 15:06

    Из моего, недавнего:
    1) в разных методах апи возвращайте разные объекты(в документации сделайте копипаст)
    2) внутри апи сделайте разные методы, которые возвращают объекты и счётчик по ним, а потом исправьте ошибки только в одном из этих методов


  1. A1essandro
    17.12.2017 15:06

    По 7 пункту я сразу вспомнил фьюзы у микроконтроллеров AVR. Где 0 — это установлен. И можно было бы привыкнуть, но некоторые программы для прошивки инвертируют их. Что запутывает окончательно, особенно когда используешь несколько таких программ.


    1. quwy
      18.12.2017 03:57

      Это не от балды так сделано. В дофлешовые времена фьюзы реально были плавкими перемычками, которые нужно было пережигать импульсом тока. И вполне логично, что целая перемычка — это 1, а пережженная — 0.


  1. julioh
    17.12.2017 15:07

    Документация? Конечно, должна быть! Такая, ммм, по всем правилам из поста. Без примером и ограничений. И при любой проблеме мы будем отправлять пользователя перечитывать ее. Не зря ж писали.