Как стать автором
Обновить
60.73

Как написать вредное API

Время на прочтение5 мин
Количество просмотров6.6K

Пишите код так, как будто сопровождать его будет склонный к насилию психопат, который знает, где вы живёте.


Всем привет!


Я работаю тимлидом команды Integration Development в сервисе онлайн-бронирования отелей Ostrovok.ru и сегодня хотел бы поделиться своим опытом работы с различными API.



Как разработчик системы, работающей с внешними поставщиками, я часто встречаюсь с различными API – чаще всего это SOAP/REST или что-то на них похожее. Однако от работы со многими из них остается впечатление, что их писали, не руководствуясь ни техническими правилами, ни здравым смыслом – как будто бы по книге “Вредные советы” Григория Остера. В данной статье я постараюсь описать такие случаи в стиле “вредных советов” и рассмотрю примеры, связанные с XML. Комментарии и обсуждение приветствуются.


Историческая справка

SOAP (от англ. Simple Object Access Protocol – простой протокол доступа к объектам) – протокол обмена структурированными сообщениями в распределённой вычислительной среде. Первоначально SOAP предназначался в основном для реализации удалённого вызова процедур (RPC). Сейчас протокол используется для обмена произвольными сообщениями в формате XML, а не только для вызова процедур.


Переходим к примерам


1. Передача xml в url


Чего больше всего хотят пользователи API? Конечно же, простоты, надёжности и лаконичности. Так давайте не будем читать тело запроса, а будем принимать XML как url-encoded информацию как параметр пути запроса! Что может быть лучше:


http://exapmple.com/xml/form.jsp?RequestName%3DHotelRequest2%26XML%3D%3C%3Fxml%2Bversion%3D%221.0%22%2Bencoding%3D%22UTF-8%22%3F%3E%0A%3CHotelRequest2%2BBuyerId%3D%22test%22%2BUserId%3D%22test%22%2BPassword%3D%22test%22%2BLanguage%3D%22en%22%2BHotel%3D%22-100%22%2BProductCode%3D%221--%22%2BArrivalDate%3D%2223.12.2018%22%2BDepartureDate%3D%2224.12.2018%22%2BArrivalTime%3D%22%22%2BDepartureTime%3D%22%22%2BCurrency%3D%222%22%2BWhereToPay%3D%223%22%2BNumberOfGuests%3D%220%22%2BNumberOfExtraBedsAdult%3D%220%22%2BNumberOfExtraBedsChild%3D%220%22%2BNumberOfExtraBedsInfant%3D%220%22%2B%2F%3E

Всё становится просто, и не надо вычитывать какое-то тело из запроса – мало ли, какие с ним могут быть проблемы.


Спойлер

Я ума не приложу, почему так было сделано. Проблемы здесь следующие: у многих серверов есть ограничение на длину пути запроса, которое в них может пройти. Если XML будет большой по объёму данных, то можно вызвать ошибку 413 Entity Too Large как один из вариантов развития событий. Кроме того, увеличивается количество информации, так как мы производим url-encoding перед отправкой.


2. Передача информации путём избыточной вложенности объектов данных


Давайте подумаем, как бы сделать информацию в ответах как можно более сложнодоступной? Давайте использовать вложенные структуры, да ещё и в разных форматах! Сказано, сделано —


<Request>
    <InnerRequest>
        <RQ>[{"someInfo":"base64Data"}]
</RQ>
    </InnerRequest>
</Request>

Действительно, верхнеуровневый xml, внутри него ещё один xml, а внутри него json, в котором данные представлены в base64, а в нём снова json, и в нём уже будет нужная нам информация! Прекрасное решение, практически как из сказки про смерть Кощея, спрятанную в яйце.


Спойлер

Один из самых заметных минусов — это замедление работы парсинга ответа, пока все вложенные структуры будут пройдены, а после может оказаться, что код ошибки зашит в json, а не уровнями выше. Я понимаю, что кодирование бинарных данных в base64 внутри xml/json – это распространённая практика, но кодирование другого формата внутри другого формата – это уже за гранью добра и зла.


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


Предположим, к нам в теле запроса приходит XML, мы обрабатываем его и даем ответ. Выглядит слишком сложно для хорошо продуманной и высоконагруженной системы. Давайте обязуем пользователей присылать тип данных в теле запроса. Как же это сделать? Прямо в теле запроса, конечно же.


XML=
<Request>
...
</Request>

Вот таким простым способом мы всегда будем знать, что нам пришёл запрос в формате XML.


Спойлер

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


4. Дублирование данных без необходимости


Предположим, у нас в структуре XML ответа есть очень-очень важная информация. Как показать это пользователю? Самое очевидное – давайте покажем её несколько раз в рамках ответа, тогда он точно обратит на неё внимание.


<Response>
    <Obj Info="Important">
        <ObjSetting Info="Important"/>
        <Name>SomeName</Name>
        <Info>Important</Info>
    </Obj>
</Response>

После этого конечный пользователь точно обратит внимание на поле Info.


Спойлер

В данном случае я задумался и даже спросил у компании, предоставляющей API, о смысле поля Info и о том, будет ли отличаться информация в тегах различного уровня. Ответ мне был: нет, не будет – они дублируют друг друга. Зачем вводить пользователей в заблуждение и делать ответ более тяжёлым, если в этом нет необходимости?


5. Передача параметров одного типа по отдельности, а не массивом


Во одном из API, которые мы используем для поиска отелей, есть поля, обозначающие возраст гостей. Какой формат представления лучше всего использовать для передачи этой информации? Пусть формат будет таким: каждый возраст будет отдельным обязательным тегом XML, и значение 0 мы будем считать, как отсутствие этого параметра.


<Request>
<Age1>20</Age>
<Age2>20</Age>
<Age3>0</Age>
<Age4>0</Age>
</Request>

Спойлер

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


6. Пересылка информации из предыдущих запросов в рамках цепочки вызовов API


Самое время подумать о безопасности нашего API. Как мы поймём, что пользователь получает информацию из наших предыдущих ответов? Пусть он присылает нам наш ответ, конечно же!


<Request>
    <RequestInfo/>
    <PreviosResp>
    ...
    </PreviosResp>
</Request>

Спойлер

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


7. Неиспользование маркеров случившейся ошибки, таких как тег ошибки или http-код


Мы сделали наш прекрасный API и представили его миру. Но однажды что-то пошло не так, и мы не смогли сформировать ответ пользователю из-за внутренней ошибки. Что делать в таком случае? Просто дать шаблон ответа, без данных, без кодов ошибок или какой-либо другой информации. Никто не должен знать, что наш идеальный API может порой не работать!
Пример такого ответа:


<Response>
    <ImportantInfo/>
</Response>

– с кодом ответа 200 OK.


Спойлер

Замалчивание случившихся ошибок — очень плохая практика. Проблема в том, что всё выглядит так, как будто проблем в ответе нет: тега <Error> нет, http статус говорит, что всё в порядке. В этом случае нужно делать дополнительную валидацию полученной информации, чтобы не произошло непредвиденных последствий уже в нашей системе.


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

Теги:
Хабы:
+16
Комментарии8

Публикации

Информация

Сайт
www.ostrovok.ru
Дата регистрации
Дата основания
2010
Численность
501–1 000 человек
Местоположение
Россия