Как создать АПИ для умных? Такое апи, чтобы создание клиента для него было не скучным механическим процессом, а настоящим приключением с элементами детектива, хоррора и мистики? Такое апи, о котором пользователи будут взахлёб рассказываете коллегам? Апи взрывающее мозг, заставляющее смеяться, кричать и плакать? Я постарался отобрать лучшие практики, с которыми пришлось столкнуться.
Делай так
- Избегай очевидных названий. checkInDate лучше записать как start_date, еще лучше как sd, еще лучше как d. Тогда длинное и некрасивое checkInDate/checkOutDate превратится в лаконичное d1/d2. Еще больше отличных идей по именованию можно почерпнуть в книге “Совершенный код” в главе “Сила имен переменных”
- Не используй стандарты. ISO 8601? Чтобы преобразовывать дату с использованием стандартных библиотек, имеющихся в любом языке программирования и не написав ни одной регулярки? Скукота.
- Не используй в названиях термины предметной области. Лучше придумай что-нибудь максимально абстрактное.
- Придумай свою систему обозначения состояний. К примеру: 1,2,3 — есть, нет, под заказ. Не вздумай писать состояния напрямую и уж тем более, никогда не используй справочник состояний.
- Передавай вложенные структуры как список плоских связанных через внутренние айдишники, сгенерированные специально под этот ответ.
{ 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"}\]} }
Не повторяйся.
a. Придумай разные системы для обозначения состояний разных объектов. К цифровой, описанной выше, можно добавить цветовую: green, red, yellow.
b. Меняй не только формат данных, но и формат записи названий полей — srart_date: 25/12/05, endDay: 2012.11.23.
c. Не забудь про огромный выбор разделителей: запятая, точка, точка с запятой. Добавь в него что-нибудь нестандартное — знак процента или решетку.
j. Список можно продолжать, будь креативен.
- Замени True/False на 1/0, а ещё лучше на 0/1. Превратив простое “has_goods: True” в четыре вопроса — айдишник? количество? просто ‘да’? или, таки, “нет”?
- Используй битовые флаги. Так ты продемонстрируешь свою исключительную образованность. Переведи их в десятичную форму для меньшей очевидности. Не передавай маску вместе с флагами, или даже в отдельном словаре. “flgs: 16” в структуре и список флагов в документации — прекрасный вариант.
- Старайся засунуть в каждое поле как можно больше смыслов.
has_goods может принимать значения [-2, -1, 0, любое положительное число], что значит: [уточните, нет в продаже, нет в наличии, количество товара в наличии]
Добавьте в запрос обязательный параметр, у которого может быть только одно значение.
- Добавь в запрос параметр, смысл которого будет менять в зависимости от значения другого, необязательного, параметра.
http://example.com/myService?cloth_type=silk&and_better=1 Где "cloth_type" - обозначает конкретный тип ткани (тут, естественно, требуется название ткани строкой, даже если есть справочник тканей, в котором указаны их айдишники), а "and_better" делает его началом диапазона с константным концом. Для “большей гибкости” можно дать параметру "and_better" второе значение (-1, 0, false, not) или добавить необязательный параметр "and_worst" и обрабатывать тот, который стоит первым/последним, либо сначала проверять наличие первого, а при его отсутствии - второго.
- Задавай диапазоны на перечислимых данных последовательным списком, а на данных, порядок которых определен только в твоём приложении, началом и концом диапазона, а еще лучше через дефис или другой разделитель по вкусу.
http://example.com/myService?d=11.12.05&d=12.12.05&d=13.12.05&colors=green;yellow
- Используй однотипные названия для полей с разными типами данных.
http://example.com/myService?cats=5&dogs=4&hamsters=0 где cats и dogs интовые поля обозначающие количество, а hamsters битовое поле обозначающее наличие.
- Не обрабатывай ошибки. Переданы несовместимые параметры? Пустой ответ должен стать достаточной подсказкой. Или отсутствие ответа. Или отправь эксепшн сгенерированный твоим приложением.
Документация не нужна.
a. Если пришлось документировать, пропусти наиболее “очевидные” места.
b. Не усложняй документацию примерами.
c. Задокументируй отсутствующий функционал, который ты обязательно допишешь потом.
d. Раздели документацию на несколько частей и каждую отдавай только тем пользователям, которые догадаются про неё спросить.
Этот список основан на реальных событиях, имена изменены в интересах конфиденциальности, все совпадения не случайны.
P.S. Напишите о своих героях и их былинных деяниях в комментариях.
Комментарии (60)
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» без указания, а что именно не так или хотя бы в каком параметреvmm86
16.12.2017 13:23Когда тебе в ответе приходят поля вида Nom_bil_kn, cod_t или cod_h — разобраться бывает непросто как при наличии документации, так и при её отсутствии.-/
azhira
15.12.2017 15:13+1- Обновляй API как можно чаще: беззастенчиво удаляй лишние запросы, но добавляй новые (обязательные!) параметры в уже существующие. Уведомлять пользователей нет необходимости — сами увидят, когда все сломается.
- Если ты так щедр, что предоставляешь тестовый сервис, то пусть он будет отставать от боевого. И работает раз в неделю. Хотя нет, работать ему необязательно.
OKyJIucT
15.12.2017 23:37А ещё можно тестить измененные методы на боевом сервере, отправлять фейковые ответы, а переданные данные не сохранять.
Weks
15.12.2017 15:17Забудь про версионирование API. Пускай каждую неделю/месяц разработчики сами подстраиваются и переписывают приложение под десятки breaking changes. Зато у них всегда будут твои новые фичи.
vbif
15.12.2017 18:29Если необязательный параметр решили сделать обязательным, или ввести для него ограничения — для этого тоже что ли версию API менять?
Weks
15.12.2017 18:36Не уверен, что компетентен ответить на этот вопрос. Моя боль с клиентской стороны состояла в постоянном изменении структуры запроса/ответа, их полей и типов.
Free_ze
15.12.2017 18:50Обычно принято мажорную версию менять при любом breaking change. Ибо для клиента это может стать сюрпризом.
Если бы обязательный сделали необязательным — ОК, обратная совместимость поддерживается.
kosmonaFFFt
15.12.2017 15:35В ответ на запрос отправлять «запрос обработан», а чтобы получить ответ, надо вызывать отдельный метод «дай_ответ», который будет отправлять ответ на какой-нибудь запрос (маппить запрос-ответ будет клиент API по айдишникам).
lromanov
17.12.2017 15:05+1Вообще, если ваш запрос был заявкой на длительную операцию по обработке, то это вполне разумный организации асинхронных запросов.
ilnuribat
17.12.2017 16:42вот это уже стандарт — есть HTTP код 202 accepted, который означает, что запрос принят на обработку
и в лучших сценариях в ответе есть url, или иной способ получить статус действия
QDeathNick
15.12.2017 15:52checkInDate/checkOutDate
Смотрел на эту конструкцию, как среда на пятницу.
Avenger911
15.12.2017 15:57Если нужно вернуть код ответа, отличный от дефолтного, посылайте
{ code: 403, message: "У вас нет прав" }
с HTTP кодом 200NtsDK
15.12.2017 19:27Что-то знакомое. Кажется для флеша надо было любой ценой возвращать ответ 200, даже если внутри по факту ошибка.
franzose
19.12.2017 00:54Ага. Некоторые еще не видят разницы между POST, PUT/PATCH, DELETE. Типа «а нафига оно надо, если есть POST?».
jonic
15.12.2017 15:58Вот буквально в час ночи от сегодня в одном API сменились лимиты и вот хоть стой хоть падай, пока тех поддержка отвечала, выяснил с внезапно изменившейся документацией без версий и логов, что лимиты так прилично зарезали. Очень классный кипеш был с утра в паре интернет магазинов. И это я уже научен горьким опытом от греха хранить полный ответ сервера апи.
Akuma
15.12.2017 16:11Пфф. Все гораздо проще. Возьмем API ВКонтакте для примера:
1. Введите маленький лимит на число запросов для замедления работы всех приложений.
2. Возвращайте каптчу на запросы от серверного API и предлагайте показать ее пользователям, которые оффлайн.
3. Когда лень показывать каптчу, просто возвращайте ошибку «Flood control», т.к. запросы к API посылаются, внимание, слишком однотипные (ведь в API нужно использовать как минимум 99% из доступных методов, хотя они вам не нужны).
P.S. Извините, накипело.
MetaDone
15.12.2017 16:15example.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
— так понятнее будетnosterx Автор
15.12.2017 16:23Зависит от обработчика, вот обсуждение на SO.
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
Avenger911
15.12.2017 16:25Отнюдь, нужно использовать первый вариант, а потом вручную парсить строку запроса на сервере, даже если бэкенд написан на PHP. Пусть программисты, которым поддерживать этот код после вас, поломают головы :-)
Кроме шуток, встречал такое разок.
Akuma
15.12.2017 16:31Поддержу. Тоже как-то встретил, прям офигел. Всю жизнь думал, что так делать нельзя, а оказалось это даже обрабатывается некоторыми веб-серверами.
Но это те еще страдания: 99.9% библиотек не поддерживают такой формат :)izzholtik
15.12.2017 18:40Ещё можно внезапно передавать параметр одновременно в теле POST и в заголовке. Причём в теле date — начало, а в пути — конец диапазона ;)
T-362
15.12.2017 16:16Черт, пара пунктов исполнена, но это из-за легаси и обратной совместимости, честно!
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, можно даже несколько раз, или свою функцию написать в свободное время
ох, самое важное — отовсюду в коде стирать свои копирайты, емейлы, телефоны!…
А еще лучше — вписать кругом контактные данные одноклассника, который у тебя булочки отбирал, будет знать!!!OKyJIucT
15.12.2017 23:45Я бы посоветовал вам ещё использовать функцию crypt в php или ее аналог, если используете другой язык, чтобы для одинаковых строк хеш всегда был разным. Ну и на мой взгляд, здесь явно не хватает XML.
bjornd
15.12.2017 17:21В POST-запрос всегда одну половину параметров передавай в body запроса и другую как в query-параметры в URL.
vbif
15.12.2017 18:24Единицы измерения: очень хорошо, когда одно и то же значение должно в разных местах задаваться разными единицами измерения. Или чтобы прочитать можно было только в одной единице, а записывать — только в другой.
Требуйте обязательного заполнения как можно большего количества полей. Особенно, если бизнес-процесс предполагает, что некоторые данные иногда сразу получить невозможно.
Требуйте наличия поля «дата документа», которое должно совпадать с текущей датой. Особенно, если данные поступают круглосуточно. Особенно, если принимаются данные из другого часового пояса.
Ещё — очень хорошо требовать определённый порядок полей в XML.
vbif
15.12.2017 19:05А вообще, какой-такой API, давайте мы будем присылать экселевские файлики по почте. Или на FTP.
TheShock
16.12.2017 02:03Хе, на почту. На 1.44 дискетку в дочерном офисе во Владивостоке с паспортом и бумажным заявлением с мокрой печатью. Если ексельник 100-метровый — архивируется и каждый архив кидается на дискету. Для самой смакотки вместо 43-й части можно повторно кинуть 42-у.
iproger
16.12.2017 01:51Надо было скомбинировать данные стороннего api и сервиса для создания api для нужд сайта. Сделал франкенштейна из этих данных и это угнетает. Как было бы сделать правильно?
ilnuribat
16.12.2017 02:10Зачем нужны коды ошибок?
Только подробное описание, которое, может быть, разработчик поймет
{ errorMessage: "DB: uniqueConstraint error for asdf@email.ru" }
shushu
16.12.2017 10:34Вы слишком детально расписываете проблему!
{ errorMessage: "DB: uniqueConstraint error" }
Так более чем достаточно! Сам несколько раз встреча подобное!vlreshet
18.12.2017 10:38+1Так и вы переборщили!
{ success: false, errorCode: "24672b" }
Всё, достаточно! А таблицу кодов прячем на 237-ую страницу мануала, ссылку на который нужно найти во втором мануале. Который высылают только по требованию, после оплаты dev-аккаунта. Через 4 дня.
vlsinitsyn
16.12.2017 04:53ISO 8601 — это еще тот стандарт для любителей веселухи.
Из практики, почти всегда можно найти вариант, который будет полностью соответствовать стандарту, но не поддерживаться или неправильно интерпретироваться конкретной библиотекой.
Так что, да, указывайте просто ISO 8601, вместо например RFC 3339. Тестерам это реально доставит :-).
bano-notit
16.12.2017 14:57Это всё конечно хорошо, но вот в пункте 8 у меня появился вопрос: где же хранить сами флаги, если не в документации и в коде, использующем их?
У меня вот иногда под флаги отдельный файл сделан, чтобы в нём хранить их… Чяднт?sasha1024
16.12.2017 16:11Да, тут пункты, по-моему, несоизмеримы.
Часть пунктов — реально жесть, но некоторые — скорее отображают неприятие автором некоторых вещей (которые, если их правильно готовить, в принципе неплохи (а если готовить неправильно, так что угодно сломать можно)).
Хотя это юмор, так что требовать точности — вряд ли уместно.
sasha1024
16.12.2017 15:10Я лично таких API не писал, но, по-моему, некоторые из пунктов (или, точнее, некоторые из частей некоторых пунктов) — например: 5 («передавай вложенные структуры как список плоских»), 7 («замени True/False на 1/0» — только тогда, конечно, точные названия выбирать надо), 8 («используй битовые флаги»), 9 («[уточните, нет в продаже, нет в наличии, количество товара в наличии]» — если эти значения действительно взаимоисключаемые по логике приложения), 10 («обязательный параметр, у которого может быть только одно значение» — например, номер версии) — вполне приемлемые в определённых сферах или, по крайней мере, пусть и плохи, но на порядок лучше других (то есть степень вреда разных пунктов несоизмерима; например, если нет вменяемого отчёта об ошибке, отличимого от успешного ответа — это бардак; а если возвращается битовая маска, но она хорошо задокументирована, то это ок).
kakutuzov
17.12.2017 15:06Из моего, недавнего:
1) в разных методах апи возвращайте разные объекты(в документации сделайте копипаст)
2) внутри апи сделайте разные методы, которые возвращают объекты и счётчик по ним, а потом исправьте ошибки только в одном из этих методов
A1essandro
17.12.2017 15:06По 7 пункту я сразу вспомнил фьюзы у микроконтроллеров AVR. Где 0 — это установлен. И можно было бы привыкнуть, но некоторые программы для прошивки инвертируют их. Что запутывает окончательно, особенно когда используешь несколько таких программ.
quwy
18.12.2017 03:57Это не от балды так сделано. В дофлешовые времена фьюзы реально были плавкими перемычками, которые нужно было пережигать импульсом тока. И вполне логично, что целая перемычка — это 1, а пережженная — 0.
julioh
17.12.2017 15:07Документация? Конечно, должна быть! Такая, ммм, по всем правилам из поста. Без примером и ограничений. И при любой проблеме мы будем отправлять пользователя перечитывать ее. Не зря ж писали.
greabock
Soap, вы забыли про soap!
alprk
Soap который возвращает строку c JSON :)
vlivyur
В двух кодировках.
lany
Просто JSON в Soap — это скучно, такое почти в любой системе с достаточной историей можно найти. А вот когда строки в JSON — это сериализованные Java-объекты, закодированные в Base64, тут уже становится интереснее.
Klajnor
Из собственного опыта:
Soap, в нем json, в нем имя файла и base64 строка, в ней zip архив, а в нем xml файл
vlreshet
Сказка на новый лад — иголка в яйце, яйцо в утке, утка в зайце, заяц в архиве, архив в Base64…
ncopiy
этот xml способен убить Кощея?