Комментарии 117
P.S. Топ моих болей в работе со сторонними апи:
- Непонятные возвращаемые ошибки
- Недокументированные ограничения
- Нелогичное изменения формата ответа при добавлении в запрос фильтра или флага
- Устаревшая документация при изменении поведения на 180 градусов
Дизайнер в принципе не себе техническую архитектуру клиент-серверного взаимодействия в большинстве случаев.
И, к сожалению, далеко не все и не всегда могут работать по ТЗ. Часто требования к проекту меняются быстрее, чем может быть написано ТЗ.
Получается менее формальный, но более «живой» документ
Последнее время архитекторы какие-то бесполезные пошли.
Равно и место экономить
«channelTitle»: «НТВ»,
«tags»: [
«NTV»,
«НТВ»,
«прямой эфир»,
«LIVE»,
«прямой эфир НТВ»,
«НТВ LIVE»
],
Про Go и оптимизацию буду ждать, подписался.
Я просто оставлю это здесь: grpc и grpc-gateway
Особенно первый пункт!
Там вообще мрак, бекенда ещё вообще нет а АПИ уже нарисовали.
Куча ненужных полей, нету нужных,
Единственный клиент данного АПИ — моё приложение.
Прошу сделать по человечески — не, мы так привыкли.
И это при том что бекенд уже 2 месяца как должен быть готов а его даже не начали.
Наболело, простите.
Ну и про изменение API не меняя версию методов, когда приложение в продакшене.
На самом деле всё делать 200-м кодом — зло. Нужно хотя бы отличать успешные ответы от неуспешных (чтобы и в браузерных DevTools сразу видеть, если что не так, и curl'ом тыкать было удобнее).
Ну т.е. 200 для успешных, 400 — если клиент портачит и 500 — если сервер. Хотя бы.
А лучше примерно так:
- 400 — ты дурак, см. тело ответа для ошибки
- 401 — сначала токен покажи, потом проходи
- 403 — куда прёшь?
- 404 — здесь рыбы нет
- 422 — ты прислал какую-то дичь, см. тело ответа для ошибки
Ну а в теле сообщения уже использовать специфичные для приложения коды ошибок.
422 — это зачастую ошибка валидации. Например "какого хрена у почты нет символа собаки" или "число, кажется, не может содержать буквы, но это не точно".
422 (Unprocessable Entity) — это нестандартный код ошибки, но популярный среди, например, разработчиков на Ruby on Rails. Смысл: синтаксически твой запрос правильный, но данные невалидные в нём или не хватает их.
А в 400 идут все некорректные запросы: синтаксически некорректный JSON, или вы отправили Form URL Encoded POST туда, где сервер ожидает JSON, или структура JSON'а кривая…
Возможно, я несколько отстал от жизни, но успешные ответы идут с тэгом Body, а неуспешные — с тэгом Fault. А еще у них разные Action. Зачем их еще как-то дополнительно различать?..
Как мобильному разработчику, в 80-90% проектах приходиться иметь дело с бекендом на стороне заказчика. И в основном это реальная боль. БООООЛь!
Был не так давно проект, над которым именно я работал, и там «северник» и в принципе, заказчик считали так же.
«Это, не ошибка сервера, это ошибка бизнес логики» говорили они мне. И вообще рвзговор сводился к разговору глухого и немого.
Да, не так сложно оказалось реализовать поддержку такого подхода, но только при условии, что ошибки единообразно структурированы (а тут мне пришлось попотеть). Дико раздражает, что например при запросе валидации данных пользователя на сервер, он отдает тебе 200OK. И приходится еще проверять, а 200 это точно все хорошо, или тут ошибок,
В общем, если этот коммент прочтет дядька, который пилит Бэк, пожалуйста, не будь безразличен к людям использующим твое АПИ. Потому что, как минимум, они тебя не будут уважать. У меня часто возникают вопросы, а люди которые пилят бек, это вообще люди? Где их берут таких криворуких, что приходит тебе строка, о потом, false, а иногда даже, массив!!! Зовите экзоциста! =)
Нет, ну серьезно, не нужно так!
Сам же код клиентской части легко может его игнорировать, обрабатывая только признак успешности, а затем собственно реальную ошибку уровня приложения.
Фактическое отсутствие стандарта классификации бизнес-ошибок по http статусам (да и откуда ему взяться) и соответствующая самопальная каша в кодерских головах — дополнительная причина полюбить 200 Ок.
Необходимость дергать распаковку ответа определяется заголовками Content-Encoding и Transfer-Encoding. Никакие другие соображения (код статуса, URL, погода на Марсе) не должны влиять на это.
{
success: false,
error: null
}
А данные обязательно передавать так:
data: {
rub: 101,
usd: "2,02"
}
Когда работаешь над сервером, то привычно, что все находится в едином scope запроса, где достаточно просто открыть транзакцию на запись и в нее последовательно протолкнуть данные. Все изолированно, предсказуемо и линейно.
Вы, похоже, никогда не занимались бэкендом.
Всегда проверяйте свое API
На кого рассчитана Ваша статья?
Типичный бэкенд — это, в том числе, и работа с другими API. Поэтому именно бэкендеры не узнают из Вашей статьи ничего нового.
Что же до ее технического уровня, то даже "Спасибо, Кэп" не за что сказать.
Один generic метод на любую запись в базу с сервера. Классно, правда?
Вообще-то это печально.
Если вы думаете, что если вы тестируете и документируете свой API, то и все другие делают так же, то у меня есть новости для вас!
Поддерживаемость? Нет, не слышали.
Как всегда круто написано! Надеюсь много похожих на героев этой статьи это прочтут и прислушаются.
А в чём проблема? И что вы предлагаете взамен?
Дизайнер же не должен задумываться об архитектуре, но думать о сложности реализации ему все таки надо. У нас макет проходит через команду разработчиков перед финальным согласованием, чтобы отмести какие-то совсем нереальные фичи.
2. Для бекенда «приложение» — это фронтенд, причём. И как именно будут показываться данные, кто с чем и по какому принципу будет объединяться — эти вопросы бекенд не волнуют вообще. API должно предоставлять набор простых «атомарных» методов, без объединений и прочей шелухи.
А вот «объединяя данные на сервере» мы получим bad design с бредовыми методами типа «дай нам список заказов, информацию когда закончится премиум аккаунт. И какая цветовая схема у пользователя выбрана еще». При этом если добавляется новый комонент, то дизайнер который «не должен задумываться об архитектуре»(ему всего-то надо добавить компонент на форму и послать 11-й запрос) внезапно начинает о ней задумываться и требовать, что бы к метод GetOrdersAndAccountExiredAndUserColourSchemaSettings возвращал еще и расписание автобусов(и, видимо, переименовался в GetOrdersAndAccountExiredAndUserColourSchemaSettingsAndPublicTransportSchedule). При этом бекенд намертво прибивается гвоздями к какой-то конкретной форме. Это при том, что бекенд не то, что о конкретной форме — он даже не должен знать, дёргает его «мобильное приложение», другой сервис или десктоп-клиент.
Эти все проблемы были окончательно решены лет 30 назад, в начале 90-х. Примерно тогда устаканилось такое семейство патернов, MV*** называется. Рекомендую ознакомиться, это сильно вам поможет.
2. Есть парадигма — backend для клиента, а не наоборот. Насчет названий — запрос в случае сложных форм называется как экран на клиенте (условно). Может быть это будет getUserSettings.
Что плохого, если есть методы для конкретной формы? Потребуется для не конкретной, будет еще один endpoint.
Что касается 'он не должен знать, дергает его приложение или что-то еще' — вот об этом и писал статье. В итоге получается API неудобное ни приложению, ни сервису, ни десктопу. И которое долбят в 10 раз больше, чем надо. Классно.
>>Что плохого, если есть методы для конкретной формы?
Тем, что из(форм) мношго, они могут меняться/создаваться вообще людьми, о которых вы не имеете представления.
«Дорогой Гугл, я тут пишу страничку, не мог бы метод, возвращающий по координатам информацию о трафике дополнительно сообщать историю погоды за 100 лет в NY, курс валют, адрес ближайшей пиццерии и пропушеные звонки в G+».
Но в данном конкретном обсуждении мы говорим вовсе не о публичных API. Вы же своих разработчиков не считаете незнакомыми?
И вообще, API — это view сервера, на минуточку. Если изменилось view продукта, то почему бы не измениться и view сервака? Вы же затачиваете свое решение для конкретного случая, а не сферических коней делаете.
Ага. Вот есть у нас Form1 и API для него — GetForm1Data.
Потом что-то добавили, что-то убрали. Серверное API обзоведётся в API набором функций GetForm1Data1, GetForm1Data2… GetForm1DataN — на каждое изменение ФОРМЫ.
Удачи.
>Когда многим надо, чтобы был запрос на историю погоды на 100 лет с курсом валют, то почему нет?
Потому что вы не сможете это поддерживать. Вообще достаточно взглянуть на любой распространённый API(от того же гугла или любой друго) что бы понять кто прав.
- не надо насиловать процессоры телефона и сеть передачи данных лишний раз.
- сервер может лечь от количества запросов с клиентов. Если у вас мобильных клиентов миллион, то сервер должен обработать 10 млн. запросов, только для того, чтобы показать экран. А если это все придет одновременно? :)
Передать 10М данных за один запрос и 10Х1М — оверхед минимален. В случае, если мы гоняем всё через tcp — я не уверен, что он будет вообще.
С «другой стороны» всё то же самое: принять 10М за раз и принять за 10 раз — разницы никакой.
По трафику все таки разница есть: всякие хидеры, мета инфа и тп на каждый запрос. Пусть и немного, но все же зависит от ситуации.
Разница может доходить до 2x, когда мета инфа занимает столько же, сколько сам полезный ответ.
Но еще нельзя забывать про время. Каждый запрос — это минимум 30мс дополнительного ожидания на установку соединения и передачу данных. А 10 запросов — это уже 0.3 секунды. Это же получается, что каждый третий экран создает задержку в секунду. Не круто ли?
Давайте сюда же запишем дополнительные действия сервера для обработки подключения: авторизация, фильтры различные, да что угодно. Все это будет выполняться в 10 раз больше, чем нужно.
И оно вам надо?
Извините, но это фейспалм. Потому что их будет 10 в любом случае.
>По трафику все таки разница есть: всякие хидеры, мета инфа и тп на каждый запрос.
«всякие хидеры, мета инфа и тп» — это сотни байт, ну единицы килобайт. Оверхед в «типичном» приложении — сотые доли процента, на уровне погрешности.
>Каждый запрос — это минимум 30мс дополнительного ожидания на установку соединения и передачу данных. А 10 запросов — это уже 0.3 секунды.
Ну вы же шутите, правда?
>Все это будет выполняться в 10 раз больше, чем нужно.
Выполняться в 10 раз дольше оно будет на идеальном канале в случае, если сервер не делает вообще ничего(шлём пустые запросы — получаем пустые ответы) причём сервер однопоточный и держит строго одно соединение.
Это, скажем так, несколько оторваный от практики случай.
> Извините, но это фейспалм. Потому что их будет 10 в любом случае.
Транзакции и материализация данных значительно облегчают нагрузку.
Как вы сделаете это одним запросом и при чём тут транзакции вообще?
«всякие хидеры, мета инфа и тп» — это сотни байт, ну единицы килобайт. Оверхед в «типичном» приложении — сотые доли процента, на уровне погрешности.
А тело ответа у вас значит не единицы килобайт? В «типичном» приложении ответы по 10 мегабайт и выше?)
Проблема, описанная в статье должна решаться, например, батчингом запросов. В итоге и запрос всего один и API остаётся красивым, логичным, выверенным. А не смесью говна, палок, котлет и мух.
* Достался же кому-то такой…
1. Вы не ответили на вопрос «в чём проблема». Какая вообще разница, какое количество запросов будет послано на сервер?
Самая очевидная проблема — ошибка несогласованных данных. Данные по одной карточке банка пришли до транзакции перевода, данные по другой перед. Итог — у пользователя впечатление, что у него исчезли деньги. Некоторые интернет-банки этим страдают. Этакое неповторяющееся чтение, несмотря на наличие нормальных транзакций на сервере. Чтобы этого избежать нужно чтобы у клиента были данные о связях между компонентами, что когда в одном окошке выполнили перевод нужно обновить две карточки счетов в другом, причем обязательно одновременно. Или можно просто прислать новые данные обеих карт в качестве ответа на перевод денег.
Достаточно же просто сделать один метод batch, который будет уметь принимать массив любых других методов и возвращать такой же массив результатов от них. Многие крупные API так и делают.
— Вот вам база данных, кладите всё туда.
Благодарен вам за труд, статься вышла отличной!
Прочитал, вынес некоторые нюансы для себя и в ближайшее время надеюсь избавиться от этих вредных привычек.
Хочу выразить благодарность и за две предыдущие статьи!
Но возникает вопрос, зачем пользоваться этим «модным» способом давать именно вредные статьи? Почему писать статью именно полезных советов с приведением примеров? Ведь это бы облегчило чтение, и можно было бы использовать как памятку для себя, или выдать как инструкцию для новичков?
Целью было не создать инструкцию, а поделиться наболевшим и дать пару советов.
> Ведь это бы облегчило чтение, и можно было бы использовать как памятку для себя, или выдать как инструкцию для новичков?
Задумаюсь над вашим предложением.
Можете пояснить механизм возникновения тормозов и лагов при таком подходе?
И про неподдерживаемость не забывайте.
БОльшая часть проблем, описанных в статье (если не все), вытекает из отсутствия взаимодействия в команде. API должно разрабатываться всей командой. backend и frontend ДОЛЖНЫ работать сообща, иначе у них получится говно будь даже каждый из них гением в своём деле.
Я слышал несколько докладов на разных конференциях, на разных языках, от Scala до Haskell, про то, как это сделать. Сам пока не смог повторить, но надежду не теряю.
С помощью Swagger (codepen) можно генерировать как клиенты так и серверы для API для множества языков. Например, я использую сгенерированный php-клиент для magento 2.
А для проверки работы API рекомендую великолепное приложение Insomnia.
Надеюсь, это поможет облегчить нелегкую работу с API :)
Не обращайте внимания на строение приложения, вам абсолютно не должно быть дело до того, как будет выглядеть продукт для пользователя. Ведь вы не хипстер, чтобы задумываться о таких вещах. Если данные для одного экрана надо получать через 10 разных запросов, это проблемы дизайнера, который рисовал интерфейс, не согласовывая его с вашим API.
Такой подход возможен, когда клиент всего один. Но сейчас это уже крайне редкий случай. Как правило клиентов несколько (веб + мобильные приложения), экраны у них разные, экранов гораздо больше, чем бизнес-сущностей, и к тому же экраны эти без конца меняются. Если у вас бэкенд будет подстраиваться под фронтенд, то вы огребаете следующие проблемы:
Огромный API. Тысячи похожих методов со своими параметрами и выдаваемыми данными. Вы не сможете быстро развивать бэкенд, так как нужно будет вносить синхронные изменения во все эти методы. В их реализации будет куча копипасты. А число багов пропорционально объёму кода. Чтобы разгребать это болото вам потребуется куча бэкендеров.
Медленная разработка. Вам приходится писать типовой код запроса и обработки ответа для каждого экрана. А на каждый чих нужно дождаться изменений бэкенда. Это от пары часов, до пары дней времени. Наличие нормализованного API позволяет изменять клиент очень быстро, не теребонькая бэкендеров по пустякам. Кроме того, это позволяет выделить всё общение с сервером в модель — попробуйте, это крайне удобно.
- Невозможность кеширования. Разные сущности обновляются с разной частотой. Степень актуальности разных сущностей нам нужна разная. Часть данных у нас может оставаться после предыдущих запросов и даже предыдущего запуска приложения. Правильная реализация Модели, позволит вам даже не запрашивать те данные, что у вас уже есть. Если же для каждого экрана у вас свой метод в API, который возвращает все данные для этого экрана, то вы тратите кучу ресурсов впустую (как на бэкенде, вытягивая их, так и на клиенте — обрабатывая их снова, ну и трафик, конечно же).
Для авторизации используйте только cookie. Вас так вас в институте учили, когда вы делали свой первый интернет магазин. Ведь Android и iOS — это просто еще один браузер, не нужно преувеличивать их сложность.
А в чём проблема мобильному приложению посылать заголовок Cookie? Или вы предпочитаете посылать токен в URL и светить им в логах сервера?
Запомните: никаких тестовых данных, пусть разработчики руками генерируют весь контент.
Что ж это за разработчики такие, что не могут автоматизировать столь простую задачу? :-) Но да, лучше не стирать базу, а тестировать на копии прода.
Будьте бунтарем: принимайте параметры в POST через URL, ведь это тот же GET, только другой. Дайте волю воображению. И игнорируйте любые мольбы коллег привести все к стандартному виду, они просто не могут мыслить нестандартно.
Нет никаких POST и GET параметров. Есть URL и есть Body. У GET запроса не может быть Body, у POST — может. Через что передавать тот или иной параметр зависит от сути параметра. Например, идентификатор сущности имеет смысл передавать через URl, а вот её новое состояние — через PATCH (а не POST). Для многих, к сожалению, является сюрпризом, что кроме POST есть более подходящие методы.
Самый простой и удобный вариант — это использовать Swagger.
Нет, самый простой — описать бизнес-сущности и универсальный протокол работы с ними. Клиент один раз реализует протокол и далее работает с бизнес-сущностями через модель. Красивая документашка по http-ручкам тут уже не нужна. А "Простого вордовского файла на дропбоксе будет достаточно".
skeddy_model OTriggered
created string \iso8601
changed string \iso8601
skeddy_person skeddy_model
- пользователь сервиса
id unique string \text
full_ame lucene string \text
description string \text
karma double \[-1,+1]
is_admin notunique boolean
avatar link-list \skeddy_image
mail link-set \skeddy_mail
phone link-set \skeddy_phone
token link-set \skeddy_token
meeting link-set \skeddy_meeting
profession link-set \skeddy_profession
refer_to link \skeddy_person
refer_from link \skeddy_person
manager link \skeddy_person
worker link-list \skeddy_person
service link-set \skeddy_service
salon_manage link-set \skeddy_salon
salon_work link-set \skeddy_salon
assessment_from link-set \skeddy_assessment
assessment_to link-set \skeddy_assessment
image link-list \skeddy_image
album link-set \skeddy_album
favorite link-set \skeddy_person
fan link-set \skeddy_person
notification link-set \skeddy_notification
place link-set \skeddy_place
social link-set \skeddy_social
schedule json \[{from,to,period}]
payment_to link-set \skeddy_payment
payment_from link-set \skeddy_payment
team link-set \skeddy_team
skeddy_mail skeddy_model
- электронная почта
id unique string \text
value unique string \text
confirmation string \text
person link \skeddy_person
skeddy_phone skeddy_model
- номер телефона
id unique string \text
value unique string
confirmation string \char[6]
person link \skeddy_person
salon link \skeddy_salon
skeddy_social skeddy_model
- информаця из социальной сети
id unique string \text
identity unique string \text
profile string \url
phone string \text
country string \text
city string \text
mail string \text
provider string \text
photo string \url
name_first string \text
name_last string \text
name_nick string \text
birthday string \iso8601
sex string \male|female
person link \skeddy_person
skeddy_token skeddy_model
- аутентификационный токен
id unique string \text
value unique string
description string
expires string
device_id string \char64
person link \skeddy_person
application link-set \skeddy_application
skeddy_application skeddy_model
- зарегистрированное клиентское приложение
id unique string \text
push_service string \apn|gcm|c2dm|wns
private_key string \text
certificate string \text
token link-set \skeddy_token
skeddy_profession skeddy_model
- основная профессия мастера
id unique string \text
title lucene string \text
master link-set \skeddy_person
skeddy_service skeddy_model
- предоставляемая мастером услуга
id unique string \text
title lucene string \text
description string \text
duration string \iso8601
period string \iso8601
price decimal
master link \skeddy_person
meeting link-set \skeddy_meeting
assessment link-set \skeddy_assessment
image link-list \skeddy_image
facet notunique link-list \skeddy_facet
skeddy_meeting skeddy_model
- встреча мастера и клиента
id unique string \text
from string \iso8601
to string \iso8601
price decimal
status string \suggested|booked|planned|completed|cancelled
payed boolean
is_suggested boolean
description string \text
service link \skeddy_service
master link \skeddy_person
customer link \skeddy_person
place link \skeddy_place
assessment link-set \skeddy_assessment
payment link-set \skeddy_payment
skeddy_assessment skeddy_model
- отзыв о прошедшей встрече
id unique string \text
comment string
strength double
from link \skeddy_person
to link \skeddy_person
meeting link \skeddy_meeting
service link \skeddy_service
skeddy_message skeddy_model
- сообщение одного пользователя другому
id unique string \text
value string \text
from link \skeddy_person
to link \skeddy_person
skeddy_album skeddy_model
- альбом с фотографиями
id unique string \text
title string \text
description string \text
person link \skeddy_person
image link-list \skeddy_image
skeddy_image skeddy_model
- мета информация о фотографии
id unique string \text
small string
big string
width integer
height integer
person link \skeddy_model
album link-set \skeddy_album
service link-set \skeddy_service
skeddy_notification skeddy_model
- уведомления с гарантированной доставкой
id unique string \text
status string \pending|sended|readed
type string \text
title string \text
message string \text
start string \iso8601
link string \uri
sender string \uri
recipient link \skeddy_person
skeddy_place skeddy_model
- адрес оказания услуг
id unique string \text
title string \text
description string \text
address string \text
longitude double
latitude double
master link-set \skeddy_person
skeddy_track skeddy_model
- собираемые координаты пользователя
id unique string \text
longitude double
latitude double
accuracy double
time string \iso8601
token link-set \skeddy_token
skeddy_payment skeddy_model
- входящий платёж
id unique string \text
amount decimal
status string \await|completed|rejected
reason string
acquire_id string
from link \skeddy_person
to link \skeddy_person
meeting link \skeddy_meeting
skeddy_article skeddy_model
- вики статья
id unique string \text
title lucene string \text
content string \text
image link-list \skeddy_image
skeddy_banner skeddy_model
- рекламный блок
id unique string \text
title string \text
description string \text
link string \uri
image link \skeddy_image
skeddy_team skeddy_model
- группа пользователей
id unique string \text
title string \text
member link-set \skeddy_person
skeddy_aspect skeddy_model
- критерий кластеризации услуг
id unique string \text
title lucene string \text
description string \text
image link-list \skeddy_image
facet_sub link-list \skeddy_facet
facet_super link-set \skeddy_facet
skeddy_facet skeddy_model
- значение критерия кластеризации
id unique string \text
title lucene string \text
description string \text
image link-list \skeddy_image
aspect_super link \skeddy_aspect
aspect_sub link-list \skeddy_aspect
service link-set \skeddy_service
skeddy_salon skeddy_model
- салон, где работают мастера
id unique string \text
title string \text
manager link-set \skeddy_person
worker link-set \skeddy_person
phone link-set \skeddy_phone
Особенно хорошо, если названия параметров в запросе и ответе идеально совпадают с полями соответствующих классов в мобильном приложении. Звучит странно, но это настолько упрощает жизнь разработчикам, что они будут вам за это шоколадки таскать из магазина.
Сквозное именование — классная штука, но важно, чтобы имена эти определялись бизнес-доменом, а не "в нашем фреймворке/языке принято именовать так" (в других фреймворках/языках по другому, а бизнес-домен — он один).
Тоже самое касается и изображений. Лучше всего, когда на картинку сразу приходит ссылка, которую не нужно 'доделывать'.
Немного доделывать её всё же стоит — резолвить относительно базового урла. И это касается не только и не столько ссылок на картинки, сколько вообще всех ссылок (HATEOAS и иже с ним). Если вы будете передавать абсолютную ссылку, то обретаете следующие проблемы:
- Для смены хоста вам придётся перезапросить все данные с сервера.
- Серверу надо знать по какому хосту вы обращались к апи (в общем случае это не известно — между клиентом и сервером могут быть разные интересные прокси).
- Раздувание объёма данных на ровном месте (имя хоста может быть довольно длинным). GZIP тут, конечно, поможет, но дебажить всё-равно не удобно.
- Нужны отдельные поля для картинок в разных размерах — усложнение API, увеличение объёма.
В мобильном приложении такого нет. Все крутится асинхронно, а если требуется соблюсти целостность данных из разных запросов, то это выливается в сложнейшие манипуляции с многопоточностью, злющими критическими секциями и распределением приоритетов, чтобы не было и намека на тормоза.
Вся эта сложность легко инкасулируется в библиотеку. Если таковой ещё нет (в чём я сильно сомневаюсь — поищите, наверняка найдёте), то не так уж сложно запилить её самому на основе сопрограмм. Изменять же API под кривой инструмент, который не умеет в "несколько запросов" — самое последнее, что стоит делать.
В качестве бонуса хочется добавить, что здорово, когда есть pretty print, хотя бы на время разработки. Бывает, что надо разобраться с тем, что пришло от сервера, не заглядывая в документацию.
Здорово, когда сам формат данных не допускает ugly print.
А если ваш бекенд не подстраивается под фронтенд, то вы огребаете другую проблему: при изменениях бизнес-процессов придется обновлять клиент. И если сервер можно обновить в любое время, то тысячи клиентов обновить совсем непросто. Уж по мне лучше гемороиться с огромным бекендом с кучей методов под разные клиенты, чем сидеть и думать, как нам скрыть вот эту кнопку только для пользователей Андроида на планшетах (пример утрированный).
Клиент в любом случае придётся обновлять. Если, конечно, у нас не тонкий терминальный клиент. Но такие клиенты для мобилок плохо подходят из-за нестабильного коннекта.
Вы это бизнесу обоснуйте) То ли дело 10 раз обновить, то ли 2-3 раза
У нас раньше так и было: клиенты дергали модельки из АПИ, потом по состоянию моделек показывали те или иные ЮИ-элементы. Тут были 2 большие проблемы: 1) Фронтенд команд как правило 3 (js, android, ios), трудно всем объяснить как выглядит бизнес процесс; 2) Клиент становится слишком умный, чтобы изменить поведение каких-то элементов надо либо перезаливать клиент, либо вставлять какие-то жуткие костыли в АПИ с подменой данных модели под конкретные версии. Затем мы стали делать так чтобы клиент спрашивал у сервера какие (значимые) элементы на экране показывать и в каком виде, а какие нет. Плюсы: вся логика с клиента переехала на сервер (легче изменять поведение клиента и объяснять бизнес процессы нужно только серверным программистам). Минусы: сервер стал толще, потому что писать АПИ надо под каждый экран.
У вас вся клиентская логика заключалась в том показывать или нет тот или иной элемент? :-) Ну, модели "Отчёт" и "Дашборд" — могут быть такими же бизнес сущностями, как и все остальные. Если у вас куча похожих экранов, то грех не реализовать их через один параметризируемый экран, конфиг для которого получать с сервера.
2. Каждый запрос, при достойной архитектуре, делается за 20 минут максимум. У нас бэкенд всегда идет впереди клиента по скорости работы, хотя людей там, как правило, в двое меньше, чем на каждой клиентской платформе. С учетом всех тестов, документирования и тп.
3. Не очень понял. Почему не кэшировать по максимуму? Ведь эта проблема будет так же, если не делать индивидуальные API под каждый чих.
> В чем проблема присылать cookie?
Плохая управляемость. Во-первых, система сама решает, цеплять этот куки к запросу или нет. Если решила, не цеплять, то счастливой отладки всем.
Во-вторых, кроме как удалить/прицепить — ничего больше не сделаешь. Понятия обновления токена нет. А дальше придется капчу приложению проходить?)
> Нет никаких POST и GET параметров. Есть URL и есть Body. У GET запроса не может быть Body, у POST — может.
Согласен, в статье упрощенное представление.
> Нет, самый простой — описать бизнес-сущности и универсальный протокол работы с ними.
Swagger не исключает бизнес-сущности, а требует их. Но не везде нужна полная сущность, например. Не присылать же целиком сущности же?
> Сквозное именование — классная штука, но важно, чтобы имена эти определялись бизнес-доменом.
Согласен, но это немного другой уровень.
Теперь про ссылки:
1. «Для смены хоста вам придётся перезапросить все данные с сервера.»
В принципе, при смене хоста придется все нафиг перевыпустить. Желательно, чтобы домен был статичным.
2. «Серверу надо знать по какому хосту вы обращались к апи „
Не вижу сложности. Все веб серверы умеют передавать Host.
3. “Раздувание объёма данных на ровном месте»
Ну не, как-то из пальца совсем уж. Сколько должно быть регулярно гуляющих ссылок, чтобы реально оказать влияние на объем.
4. «Нужны отдельные поля для картинок в разных размерах — усложнение API»
Согласен. Правда, не всегда применимо использовать композитные адреса. Представьте, что вконтакте можно было бы получить любые изображения любого человека, просто правильно составив ссылку? Но в целом, почему нет.
> то не так уж сложно запилить её самому на основе сопрограмм
Вы не разрабатывали приложения. Просто нечего сказать. Люблю такое, когда бэкенд разработчики говорят, что почему вы просто не сделаете ассемблерные вставки, например? Типа, могу хоть сейчас скинуть код.
На рынке многие разработчики не знают даже про простейший gcd, а вы говорите про инкапсуляции в библиотеку…
По опыту, проект на 5 клиентских платформ (веб, android, ios, macos, windows), не заметил, чтобы документация раздулась. Для регулярных случаев с вебом и двумя мобилками, все по дефолту хорошо.
Конкретных цифр не будет? Сколько уникальных серверных ручек в API? Сколько лет проекту?
Каждый запрос, при достойной архитектуре, делается за 20 минут максимум. У нас бэкенд всегда идет впереди клиента по скорости работы, хотя людей там, как правило, в двое меньше, чем на каждой клиентской платформе. С учетом всех тестов, документирования и тп.
Если 20 минут — это написание клиентского кода, то это слишком долго. С полноценной моделью (которая генерируется по общей с сервером схеме) вам потребуется 0 минут.
Не очень понял. Почему не кэшировать по максимуму? Ведь эта проблема будет так же, если не делать индивидуальные API под каждый чих.
Потому что к разным типам ресурсов разные требования по актуальности. От "балланс нельзя кешировать — он должен запрашиваться при каждом заходе на страницу", до "статистика обновляется по ночам, обновлять её в течении дня нет смысла".
Плохая управляемость. Во-первых, система сама решает, цеплять этот куки к запросу или нет. Если решила, не цеплять, то счастливой отладки всем.
Что значит "сама"? У вас там куки Шрёддингера? :-) Телепат во мне подсказывает, что она попросту протухает.
Во-вторых, кроме как удалить/прицепить — ничего больше не сделаешь. Понятия обновления токена нет.
В чём проблема установить новую куку?
Нет, самый простой — описать бизнес-сущности и универсальный протокол работы с ними.
Swagger не исключает бизнес-сущности, а требует их. Но не везде нужна полная сущность, например. Не присылать же целиком сущности же?
Стандартизированный fetch-plan позволяет указать какие поля и в каком объёме нужно возвращать. Зачем это описывать для каждого ресурса отдельно?
В принципе, при смене хоста придется все нафиг перевыпустить. Желательно, чтобы домен был статичным.
Зачем перевыпускать? В чём проблема серверу прислать событие "используй для картинок теперь такую-то базовую ссылку"? А клиенту резолвить урл в момент запроса, а не в момент получения ответа.
Не вижу сложности. Все веб серверы умеют передавать Host.
Это не тот хост, к которому обращался клиент, а тот, по которому обратились к вашему сервису. Одинаковыми они будут только если клиент обратился напрямую к вашему хосту, без посредников с другими именами.
Ну не, как-то из пальца совсем уж. Сколько должно быть регулярно гуляющих ссылок, чтобы реально оказать влияние на объем.
Согласен. Правда, не всегда применимо использовать композитные адреса. Представьте, что вконтакте можно было бы получить любые изображения любого человека, просто правильно составив ссылку? Но в целом, почему нет.
Из ВК и так кто угодно может получить ссылку на вашу аватарку :-)
https://api.vk.com/method/users.get?user_ids=1&fields=photo_max
Вы не разрабатывали приложения. Просто нечего сказать. Люблю такое, когда бэкенд разработчики говорят,
Я — преимущественно фронтенд-разработчик.
что почему вы просто не сделаете ассемблерные вставки, например? Типа, могу хоть сейчас скинуть код.
Тут не нужны ассемблерные вставки. Всё уже вставили за вас.
На рынке многие разработчики не знают даже про простейший gcd, а вы говорите про инкапсуляции в библиотеку…
Так пусть идут в школу и учатся.
Нет никаких POST и GET параметров.
Я думаю речь о пост-запросах на урл типа ***/api/items/1?id=123&user=sdfs — то есть когда пост-запрос требует передачи query strings. И это абсолютно идиотское использование REST. За такую реализацию надо как минимум штрафовать.
Или вы предпочитаете посылать токен в URL и светить им в логах сервера?
Для этого есть хедеры. А куки в restapi — это рудимент, который должен отвалиться как можно скорее.
Я думаю речь о пост-запросах на урл типа ***/api/items/1?id=123&user=sdfs — то есть когда пост-запрос требует передачи query strings. И это абсолютно идиотское использование REST. За такую реализацию надо как минимум штрафовать.
А обосновать сможете?
Для этого есть хедеры. А куки в restapi — это рудимент, который должен отвалиться как можно скорее.
А куки через астрал передаются или через что?
Поддержка кук предполагает, что кука может быть установлена сервером в ответ на любой запрос, после чего клиент обязан ее запомнить и передавать при последующих запросах. Частичная реализация этого механизма ("мы сохраняем только куку token и только полученную в ответ на запрос login") будет противоречить стандарту. Часть реализации, кончено же, возьмет на себя клиентская библиотека — но реализовывать хранение кук на диске придется самостоятельно.
В то же время, при передаче токена в заголовке Authorization или в нестандартном заголовке никто не мешает использовать самую простейшую схему управления токеном — он возвращается в ответ на запрос аутентификации и прикладывается ко всем остальным запросам.
Кроме того, если клиент работает в браузере, то отказ от использования кук автоматически устраняет XSRF-уязвимость без дополнительных телодвижений.
Токен, конечно же, никогда не протухает и никогда не отзывается?
Сам по себе отказ от кук — уже дополнительное телодвижение.
Ну пусть токен протухает и отзывается. И что с того? Это ничем не сложнее для реализации чем протухшая или отозванная кука.
Отказ от кук является дополнительным телодвижением только если клиент работает на платформе, которая из коробки обеспечивает хранение кук (= в браузере), для всех остальных видов клиентов это не так.
И даже в браузере обратиться к api, использующему токены авторизации, ничуть не сложнее чем обратиться к api, использующему анти-XSRF токены.
Ну пусть токен протухает и отзывается. И что с того? Это ничем не сложнее для реализации чем протухшая или отозванная кука.
О чём и речь.
Отказ от кук является дополнительным телодвижением только если клиент работает на платформе, которая из коробки обеспечивает хранение кук (= в браузере), для всех остальных видов клиентов это не так.
Для всех остальных — монописуально.
И что в итоге этот метод возвращает? Правильно, полную ссылку)
> Это не тот хост, к которому обращался клиент, а тот, по которому обратились к вашему сервису.
Он пробрасывается, как правило, чтобы иметь значение исходного хоста. По крайней мере везде, где я сталкивался с цепочкой проксирования.
> Так пусть идут в школу и учатся.
Дело говорите, но только больше годных спецов от этого не становится)
> Зачем перевыпускать? В чём проблема серверу прислать событие «используй для картинок теперь такую-то базовую ссылку»
Потому что сложно. Какое-то состояние дополнительное за которым надо следить. Везде, где я сталкивался с подобным поведением, было огромная куча багов из-за этого. Да, можно сказать, идите в школу учиться прогать, но лучше сейчас от этого не станет.
> Стандартизированный fetch-plan позволяет указать какие поля и в каком объёме нужно возвращать. Зачем это описывать для каждого ресурса отдельно?
Вы такого хорошего мнения о среднем уровне разработчиков на рынке) Там же не просто один запрос, которому надо передать список полей.
> В чём проблема установить новую куку?
Проблема следить за ней и в том, что она неуправляема на уровне приложения практически. Это действительно осложняет жизнь значительно.
> Конкретных цифр не будет? Сколько уникальных серверных ручек в API? Сколько лет проекту?
Полтора года, около 40.
В общем, все можно сделать офигенно по уму, с классной логикой, но когда у тебя штат состоит из сильнейших девов, которые никуда не разбегутся, платят им выше среднего и вообще у вас гугл. Но много ли где так?
И что в итоге этот метод возвращает? Правильно, полную ссылку)
С тем же успехом ссылка могла бы быть и не полная.
Он пробрасывается, как правило, чтобы иметь значение исходного хоста. По крайней мере везде, где я сталкивался с цепочкой проксирования.
Ок, прилетел вам Host: localhost:8080
. Какой сервис на вашем IP обработает этот запрос?
Дело говорите, но только больше годных спецов от этого не становится)
Достаточно иметь одного грамотного архитектора в команде, который научит этих негодников уму-разуму. :-)
Потому что сложно. Какое-то состояние дополнительное за которым надо следить. Везде, где я сталкивался с подобным поведением, было огромная куча багов из-за этого.
Не вижу тут сложности. Я же описал вам весь алгоритм действий: изменять значение одной глобальной переменной, когда приходит событие от сервера, и резолвить ссылки перед запросом относительно значения этой переменной. Всё.
Вы такого хорошего мнения о среднем уровне разработчиков на рынке) Там же не просто один запрос, которому надо передать список полей.
Если он разобрался в хоть каком-нибудь языке программирования, то разобраться в тривиальном ?fetch=first_name,last_name,age
и подавно сможет.
Проблема следить за ней и в том, что она неуправляема на уровне приложения практически. Это действительно осложняет жизнь значительно.
Под iOS приложение не может читать и устанавливать куки?
Полтора года, около 40.
У вас 40 экранов суммарно на 5 приложений? Что ж вы там полтора года делали? :-)
Как написать максимально хреновый бэкенд для мобильного приложения