Комментарии 37
SSL повсюду — самое важное в вашем сервисе, т. к. без SSL авторизация и аутентификация бессмысленны.
Из последних 7 API, с которыми я работал, 3 обходились без SSL, ибо шифровать данные было незачем. Аутентификация была, ломалась "на раз", но никто не парился — слишком малые суммы крутились. Так что, называть SSL важным для произвольного сервиса несколько категорично.
Методы POST и PUT должны возвращать обратно объект, который они изменили или создали, — это позволит сократить время обращения к сервису вдвое.
В некоторых сценариях достаточно вернуть ID созданного юзера\продукта. Формирование и\или сериализация только что созданного объекта может занимать немалое время (кстати, результат надо еще десериализовать на клиенте).
Поддержка фильтрации, сортировки и постраничного вывода — очень желательно, чтобы это было стандартно и работало «из коробки».
Золотые слова (с точки зрения клиента).
НЛО прилетело и опубликовало эту надпись здесь
Мир на Swagger'e не заканчивается — у него тоже недостатков хватает.
Думаю было бы не плохо упомянуть про HATEOAS подход и довольно плачевную ситуацию с существующими протоколами на этом поприще.
Сейчас разрабатывается пару довольно интересных подходов к разработке реактивных приложений, без использования вышеупомянутых boilerplate'ов и без кодогенерации.
Думаю было бы не плохо упомянуть про HATEOAS подход и довольно плачевную ситуацию с существующими протоколами на этом поприще.
Сейчас разрабатывается пару довольно интересных подходов к разработке реактивных приложений, без использования вышеупомянутых boilerplate'ов и без кодогенерации.
У меня такое ощущение, и это сугубо личное, что на HATEOAS все забили по причине того, что это на самом деле кардинально другой подход который требует, как бы это сказать, намного более динамичных систем, которые адаптируются к наличию или отсутствию того или иного API и блужданию по нем, как по блужданию по диалогам в ролевой игре. Оно прекрасно в теории, спору нет, но нюанс в том кто, и за какие деньги, способен грамотно реализовать подобное поведение.
Самое интересное не затронули. Как работать с тем, что не укладывается в понятие ресурса или в стандартные глаголы HTTP? Например, поиск, логин, восстановление пароля.
Также спорно использование суб-ресурсов вроде
Также спорно использование суб-ресурсов вроде
stations/1/arrivals
. Почему предпочтительней так, а не использовать отдельный ресурс arrivals
?то, что не угладывается в понятие ресурса, заворачивается в "задачу", которая является ресурсом.
логин не обязательно должен быть на webapi. даже лучше делать свой oauth.
На счет спорности суб-ресурсов согласен.
логин не обязательно должен быть на webapi. даже лучше делать свой oauth.
На счет спорности суб-ресурсов согласен.
Про «задачи» — в точку.
Можно рассматривать логин в качестве операции создания объекта «Сессия»:
Можно рассматривать логин в качестве операции создания объекта «Сессия»:
POST /sessions
Хорошо. Как переложить на задачу поиск и восстановление пароля?
Восстановление пароля — это процесс. процесс можно создать. можно проверить его статус (новый, отправили письмо, восстановили, отменили). можно удалить.
Поиск — это запрос коллекции данных. Тут просто идеально подходит OData.
Например: /api/ticket?$filter=… — поиск билетов по заданным критериям.
Поиск — это запрос коллекции данных. Тут просто идеально подходит OData.
Например: /api/ticket?$filter=… — поиск билетов по заданным критериям.
Если поиск чего-то конкретного, то да. А если поиск делается сразу и по всему? Например,
GET /search?query=beer
и на выходе мы получаем статьи, заметки, юзеров, комментарии?Ответ — коллекция обьектов "элемент результата поиска". Ну банально объект с одним полем "ссылка".
Вот у нас прилетели 50 результатов. Делать 50 запросов чтобы получить эти объекты?
Не обязательно 50, можно сгруппировать по типу объектов. Ножно объекты пихать в поле объекта результат-поиска. А можно в объекте результатов поиска иметь 2 поля: текст и ссылка на страницу.
У вас будет 50 моделей с описанием результатов поиска.
В модели может лежать кусок найденного текста. Дополнительная картинка. Ссылка на "статью, заметку, юзера". Тип найденного объекта. И т.д.
Тут главное что модель не обязательно сама статья, заметка, юзер. И, как написано в статье, эта модель может вообще не мапиться на базу.
В модели может лежать кусок найденного текста. Дополнительная картинка. Ссылка на "статью, заметку, юзера". Тип найденного объекта. И т.д.
Тут главное что модель не обязательно сама статья, заметка, юзер. И, как написано в статье, эта модель может вообще не мапиться на базу.
Зависит от задачи. Иногда достаточно коллекции из 50 ссылок, иногда её надо расширить текстовым полем, иногда более сложные объекты типа "элемент результата поиска" с метаданными, сниппетами и т. п., иногда отдавать коллекцию объектов, иногда совмещать некоторые или даже все подходы.
В данном случае при определении пути stations/1/arrivals учитывается что у вас OneToMany отношение со сложенным ключем.
Если у таблички Arrivals PK состоит из ID и FK station_id, то целесообразно использование
stations/{station_id}/arrivals/{arrival_id}
Если у таблички Arrivals PK состоит только из ID, то можно и
/arrivals/{arrival_id}
которые при поиске по станции будут выглядеть так
/arrivals/station/{station_id}
Сложенные ключи позволяют упростить жизнь если таблички будут расти до непонятных размеров (200Гб — 2Тб), в последствии шардиться и партицироваться, дампиться во внешнее хранилище (cassandra / hbase etc) или архивные таблички (mysql engine archive).
Так что это скорее вопрос разработки и нормализации доменной модели, чем интерфейсов…
p.s. нормальных форм нынче уже шесть
Если у таблички Arrivals PK состоит из ID и FK station_id, то целесообразно использование
stations/{station_id}/arrivals/{arrival_id}
Если у таблички Arrivals PK состоит только из ID, то можно и
/arrivals/{arrival_id}
которые при поиске по станции будут выглядеть так
/arrivals/station/{station_id}
Сложенные ключи позволяют упростить жизнь если таблички будут расти до непонятных размеров (200Гб — 2Тб), в последствии шардиться и партицироваться, дампиться во внешнее хранилище (cassandra / hbase etc) или архивные таблички (mysql engine archive).
Так что это скорее вопрос разработки и нормализации доменной модели, чем интерфейсов…
p.s. нормальных форм нынче уже шесть
Не обязательно делать модель API в строгом соответствии с хранилищем данных. Для проектов со стажем это скорее всего будет не так.
Для проектов «со стажем» это будет общепринятой практикой и соответствующее соглашение будет утверждено документально, что бы упростить поддержку и внедрение нового функционала. Не будет ситуаций: кто что захотел, пришёл и наворотил — потому что захотел, а не потому что надо.
REST в чистом виде использовать далеко не всегда удобно.
Например, необходимо зарегистрировать пользователя в системе страховой компании.
Регистрация происходит с мобильного устройства, где вводить кучу полей (имя, фамилия, отчество, телефон, адрес электропочты и пр.) — мягко говоря, не самое лучшее решение.
В качестве оптимизации UX регистрацию можно разбить на два шага. На первом шаге пользователь вводит номер страхового полиса, и дальше на серверной стороне система уже сама может подтянуть имя, фамилию и пр., скомпоновав их в конечную «Учётную запись».
Если у пользователя нет полиса, или системе введенный номер полиса по какой-то причине неизвестен — инициируется второй шаг, где уже нужно вбивать все личные данные.
Для большинства клиентов страховой компании второй шаг никогда не придётся проходить, т.к. каждого из них можно однозначно идентифицировать по номеру полиса. Соответственно, количество полей ввода для регистрации на мобильном устройстве сокращается до одного.
С подобными предпосылками, возникает вопрос: как рассматривать процесс регистрации с точки зрения API?
Необходимо создать задачу/операцию «Зарегистрировать пользователя по номеру полиса»?
Хорошо. А что должно возвращаться в ответ на запрос ниже?
Будет возвращаться объект «Операция» с идентификатором?
И потом мобильное устройство должно отправлять ещё один запрос с этим идентификатором?
Это — не вариант, количество запросов с мобильного устройства нужно минимизировать.
Или в ответ на запрос будет возвращаться сразу объект «Учётная запись»? Это — неконсистентно.
Мы в компании остановились на том, что подход REST в чистом виде использовать для некоторых целей неудобно, и в качестве компромисса можно задействовать запросы «с глаголами». В ответ может прийти учётная запись:
Что вы думаете по этому поводу?
Например, необходимо зарегистрировать пользователя в системе страховой компании.
Регистрация происходит с мобильного устройства, где вводить кучу полей (имя, фамилия, отчество, телефон, адрес электропочты и пр.) — мягко говоря, не самое лучшее решение.
В качестве оптимизации UX регистрацию можно разбить на два шага. На первом шаге пользователь вводит номер страхового полиса, и дальше на серверной стороне система уже сама может подтянуть имя, фамилию и пр., скомпоновав их в конечную «Учётную запись».
Если у пользователя нет полиса, или системе введенный номер полиса по какой-то причине неизвестен — инициируется второй шаг, где уже нужно вбивать все личные данные.
Для большинства клиентов страховой компании второй шаг никогда не придётся проходить, т.к. каждого из них можно однозначно идентифицировать по номеру полиса. Соответственно, количество полей ввода для регистрации на мобильном устройстве сокращается до одного.
С подобными предпосылками, возникает вопрос: как рассматривать процесс регистрации с точки зрения API?
Необходимо создать задачу/операцию «Зарегистрировать пользователя по номеру полиса»?
Хорошо. А что должно возвращаться в ответ на запрос ниже?
POST /users/register_with_insurance_number_operations
Будет возвращаться объект «Операция» с идентификатором?
И потом мобильное устройство должно отправлять ещё один запрос с этим идентификатором?
Это — не вариант, количество запросов с мобильного устройства нужно минимизировать.
Или в ответ на запрос будет возвращаться сразу объект «Учётная запись»? Это — неконсистентно.
Мы в компании остановились на том, что подход REST в чистом виде использовать для некоторых целей неудобно, и в качестве компромисса можно задействовать запросы «с глаголами». В ответ может прийти учётная запись:
POST /users/register_with_insurance_number
Что вы думаете по этому поводу?
У нас была подобная задача. Форма регистрации делилась на отдельные экраны по одному-два поля на экран:
Чтобы не гонять туда-сюда тучу раз полный набор данных, сделали вот такое:
- Password
- Опционально first name, last name
Чтобы не гонять туда-сюда тучу раз полный набор данных, сделали вот такое:
// валидируем почту POST /user Action=Validate Email=me@example.com // валидируем имя-фамилию POST /user Action=Validate FirstName=Alex LastName=Makarov // создаём юзера POST /user Email=me@example.com Password=XXX FirstName=Alex LastName=Makarov
плохо что у вас есть Action=Validate
Предложите альтернативу.
Альтернатива в данном случае — не использовать REST. Он в данном случае не нужен и не стоит его притягивать.
Создание чего-то должно вернуть:
Создание чего-то должно вернуть:
- ссылку на созданный ресурс
- сам ресурс.
Валедировать чернз API не входит в принцыпы REST. Я считаю что без этого можно обойтись. Это как в Web. Есть валидация на стороне клиента и на стороне сервера. На клиенте мы проверяем заполненность полей и формат данных, а наличие совпадений в бд уже после отпрвки формы на сервере.
Да. Проверить наличие совпадений до отправки может быть удобно, но без этого можно обойтись
Да. Проверить наличие совпадений до отправки может быть удобно, но без этого можно обойтись
Это сильно ударит по удобству формы регистрации, сократит конверсию. Компания потеряет доход.
Костылей всегда можно наплодить. Но в любом случае Action=Validate делать нельзя. Нельзя отправлять семантически разные запросы на один адрес. Нужно создавать дополнительные адреса на подобии таких:
`
POST /user/isValidEmail
POST /user/isValidUsername
`
И да. Наличие такого метода это серьёзная дыра в безопастности позволяющая собрать базу email ваших пользователей. На месте бизнеса я бы ещё подумал что хуже — уменьшение конверсии или утечка персональных данных пользователей.
`
POST /user/isValidEmail
POST /user/isValidUsername
`
И да. Наличие такого метода это серьёзная дыра в безопастности позволяющая собрать базу email ваших пользователей. На месте бизнеса я бы ещё подумал что хуже — уменьшение конверсии или утечка персональных данных пользователей.
Наличие валидации почты при регистрации — точно такая же утечка. Не знаю, как её избежать кроме как не спрашивать почту.
Да, но в случае регистрации, если этот емайл не занят будет создан новый пользователь, а хакерам это не надо.
Уязвимость через регистрацию будет если форма регистрации состоит предположим из полей email и password.
Есть проверки которые выполняются в указанном порядке:
То хакер может указать корректный email и не корректный пароль и тогда если пользователя в бд нет регистрация свалится после 3-его шага. Пользователь зарегистрирован не будет, а email будет проверен.
Но решается эта проблема очень просто. Проверка корректности данных должна выполнятся до запроса в бд
Есть проверки которые выполняются в указанном порядке:
- указан email
- формат email-а
- наличие email в бд
- указан пароль
- длинна пароля
- сложность пароля
То хакер может указать корректный email и не корректный пароль и тогда если пользователя в бд нет регистрация свалится после 3-его шага. Пользователь зарегистрирован не будет, а email будет проверен.
Но решается эта проблема очень просто. Проверка корректности данных должна выполнятся до запроса в бд
Дополнительные адреса — это можно. Будет совсем не REST, потому как адрес должен быть ресурсом, но, возможно, немного лучше, чем мой Action.
С моей точки зрение изначальное использование POST некорректно, т.к. Ваша операция проверки индпотентна и безопасна, а POST не обладает ни тем ни другим свойством.
Если бы сейчас у меня стояла задача ввода сложной формы, то я бы делал так (опять же сильно зависит от юзкейса):
Если бы сейчас у меня стояла задача ввода сложной формы, то я бы делал так (опять же сильно зависит от юзкейса):
- Сервер предоставляет функции валидации уникальности полей, например GET /users/validation/EmailAvailability/ddd@dd.dd
- Мы признаем, что на время заполнения формы данный e-mail может быть кем-то занят, но вероятно этого не велика
- Делает обычный пост с повторной валидацией всех полей.
Выглядит это действительно сложно.
Первое что пришло в голову. На первом шаге мы отправляем запрос на адрес:
code — соответственно номер страховки
В случае ошибки переходим к шагу 2 и отправляем запрос:
Принципы REST не нарушены. Интерфейс вполне логичен
Первое что пришло в голову. На первом шаге мы отправляем запрос на адрес:
POST /insurance/{code}/user
code — соответственно номер страховки
В случае ошибки переходим к шагу 2 и отправляем запрос:
POST /user/
Принципы REST не нарушены. Интерфейс вполне логичен
По вашему примеру, чем плох следующий подход (в рамках REST)
Дано, мобильное устройство на котором проходит указанный этап регистрации.
Т.е. если клиент хранит состояние между шагами представления, то ничего додумывать не придется.
Дано, мобильное устройство на котором проходит указанный этап регистрации.
- Создаем объект хранящий состояние (т.е. все указанные вами поля — (имя, фамилия, отчество, телефон, адрес электропочты и пр.)
- Отображается первый шаг, где пользователь вводит номер страхового полиса, жмет далее
- Клиент отправляет GET запрос. типа GET api/v10/users/exists?ssn="123-456-4567"
- Если клиент есть, то следующим шагом ввод пароля (иначе можно ввести чужой ssn и узнать личные данные), и вход, после чего подтянутся GET'ом данные — GET api/v10/users?ssn="123-456-4567"
- Если клиента нет, отображается форма для заполнения остальных полей
5.1 Поля заполнили, отправляем POST с данными для регистрации
Т.е. если клиент хранит состояние между шагами представления, то ничего додумывать не придется.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий
Подходы к проектированию RESTful API