1
0
mirror of https://github.com/twirl/The-API-Book.git synced 2025-03-17 20:42:26 +02:00

style fix

This commit is contained in:
Sergey Konstantinov 2021-05-30 23:17:48 +03:00
parent b1d99a8d1c
commit 3fb6c00047

View File

@ -33,7 +33,7 @@
* разработка REST API должна фокусироваться на описании медиатипов, представляющих ресурсы; при этом клиент вообще ничего про эти медиатипы знать не должен;
* в REST API не должно быть фиксированных имён ресурсов и операций над ними, клиент должен извлекать эту информацию из ответов сервера.
Короче говоря, REST по Филдингу подразумевает, что клиент, получив каким-то образом ссылку на точку входа REST API, далее должен быть в состоянии полностью выстроить взаимодействие с этим API, не обладая вообще никаким априорным знанием о нём, и уж тем более не должен содержать никакого специально написанного кода, чтобы с этим API взаимодействовать.
Короче говоря, REST по Филдингу подразумевает, что клиент, получив каким-то образом ссылку на точку входа REST API, далее должен быть в состоянии полностью выстроить взаимодействие с API, не обладая вообще никаким априорным знанием о нём, и уж тем более не должен содержать никакого специально написанного кода для работы с этим API.
Оставляя за скобками тот факт, что Филдинг весьма вольно истолковал свою же диссертацию, просто отметим, что ни одна существующая система в мире не удовлетворяет описанию REST по Филдингу-2008.
@ -47,7 +47,7 @@
Что же «правильного» в REST-подходе к дизайну API (таком, как он сформировался в коллективном сознании широких масс программистов)? То, что такой дизайн позволяет добиться более эффективного использования времени — времени программистов и компьютеров.
Если коротко обобщить все сломанные в попытках определить REST копья, то мы получим следующий принцип: *лучше бы агентам распределённой системы понимать мета-информацию запросов и ответов, проходящих через эту систему* (готовая заповедь для RESTful-пастафарианства практически!).
Если коротко обобщить все сломанные в попытках определить REST копья, то мы получим следующий принцип: *лучше бы ты разрабатывал распределённую систему так, чтобы промежуточные агенты умели читать метаданные запросов и ответов, проходящих через эту систему* (готовая заповедь для RESTful-пастафарианства практически!).
У протокола HTTP есть очень важное достоинство: он предоставляет стороннему наблюдателю довольно подробную информацию о том, что произошло с запросом и ответом, даже если этот наблюдатель ничего не знает о семантике операции:
@ -105,9 +105,9 @@ DELETE /user/{user_id}
Authorization: Bearer <token>
```
Теперь URL запроса в точности идентифицирует ресурс, к которому обращаются; теперь можно, в частности, организовать маршрутизацию запроса в зависимости от идентификатора пользователя. Префетчер мессенджера не пройдёт по DELETE-ссылке; а если он это и сделает, то без заголовка Authorization операция выполнена не будет.
Теперь URL запроса в точности идентифицирует ресурс, к которому обращаются, поэтому можно организовать кэш и даже заранее наполнить его; можно организовать маршрутизацию запроса в зависимости от идентификатора пользователя, т.е. появляется возможность шардирования. Префетчер мессенджера не пройдёт по DELETE-ссылке; а если он это и сделает, то без заголовка Authorization операция выполнена не будет.
Наконец, неочевидная польза такого решения заключается в следующем: промежуточный сервер-гейтвей, обрабатывающий запрос, может проверить заголовок Authorization и переслать запрос далее без него (желательно, конечно, по безопасному соединению или хотя бы подписав запрос). Тогда во внутренней среде можно будет свободно организовывать кэширование данных в любых промежуточных узлах. Более того, *агент может легко модифицировать операцию*: например, для авторизованных пользователей пересылать запрос дальше как есть, а для неавторизованным показывать публичный профиль, пересылая запрос на специальный URL, ну, скажем, `GET /user/{user_id}/public-profile`. Для современных микросервисных архитектур *возможность корректно и дёшево модифицировать запрос при маршрутизации и является самым ценным преимуществом в концепции REST*.
Наконец, неочевидная польза такого решения заключается в следующем: промежуточный сервер-гейтвей, обрабатывающий запрос, может проверить заголовок Authorization и переслать запрос далее без него (желательно, конечно, по безопасному соединению или хотя бы подписав запрос). Тогда во внутренней среде можно будет свободно организовывать кэширование данных в любых промежуточных узлах. Более того, *агент может легко модифицировать операцию*: например, для авторизованных пользователей пересылать запрос дальше как есть, а для неавторизованным показывать публичный профиль, пересылая запрос на специальный URL, ну, скажем, `GET /user/{user_id}/public-profile` — для этого достаточно всего лишь дописать `/public-profile` к URL, не изменяя все остальные части запроса. Для современных микросервисных архитектур *возможность корректно и дёшево модифицировать запрос при маршрутизации является самым ценным преимуществом в концепции REST*.
Шагнём ещё чуть вперёд. Предположим, что гейтвей спроксировал запрос `DELETE /user/{user_id}` в нужный микросервис и не дождался ответа. Какие дальше возможны варианты?
@ -123,15 +123,15 @@ Authorization: Bearer <token>
В итоге, более сложная архитектура оказался разделена по уровням ответственности, и каждый разработчик занимается своим делом. Разработчик гейтвея гарантирует наиболее оптимальный роутинг внутри дата-центра, разработчик фреймворка предоставляет функциональность по реализации политики таймаутов и перезапросов, а разработчик клиента пишет *бизнес-логику* обработки ошибок, а не код восстановления из низкоуровневых состояний неопределённости.
**Разумеется** все подобные оптимизации можно выполнить и без опоры на стандартную номенклатуру методов / статусов / заголовков HTTP, или даже вовсе поверх другого протокола. Достаточно разработать одинаковый формат данных, содержащий нужную мета-информацию, и научить промежуточные агенты и фреймворки его читать. В общем-то, именно это Филдинг и утверждает в своей диссертации. Но, конечно, очень желательно, чтобы этот код уже был кем-то написан за вас.
**Разумеется** все подобные оптимизации можно выполнить и без опоры на стандартную номенклатуру методов / статусов / заголовков HTTP, или даже вовсе поверх другого протокола. Достаточно разработать одинаковый формат данных, содержащий нужную мета-информацию, и научить промежуточные агенты и фреймворки его читать. В общем-то, именно это Филдинг и утверждает в своей диссертации. Но, конечно, очень желательно, чтобы этот код уже был кем-то написан за нас.
Заметим, что вышесказанное не имеет абсолютно ничего общего с многочисленными советами «как правильно разрабатывать REST API», которые можно найти в интернете:
Заметим, что многочисленные советы «как правильно разрабатывать REST API», которые можно найти в интернете, никак не связаны с изложенными выше принципами, а зачастую и противоречат им:
1. «Не используйте в URL глаголы, только существительные» — этот совет является всего лишь костылём для того, чтобы добиться правильной организации мета-информации об операции. В контексте работы с URL важно добиться двух моментов:
* чтобы URL был ключом кэширования для кэшируемых операций и ключом идемпотентности для идемпотентных;
* чтобы при маршрутизации запроса в многослойной системе можно было легко понять, куда будет маршрутизироваться эта операция по её URL — как машине, так и человеку.
2. «Используйте HTTP-глаголы для описания действий того, что происходит с ресурсом» — это правило попросту ставит телегу впереди лошади. Глагол указывает всем промежуточным агентам, является ли операция (не)модифицирующей, (не)кэшируемой, (не)идемпотентной; вместо того, чтобы выбирать строго по этим трём критериям, предлагается воспользоваться какой-то мнемоникой — если глагол подходит к смыслу операции, то и ок. Это в некоторых случаях просто опасно: вам может показаться, что `DELETE /list?element_index=3` прекрасно описывает ваше намерение удалить третий элемент списка, но т.к. эта операция неидемпотентна, использовать метод `DELETE` здесь нельзя;
2. «Используйте HTTP-глаголы для описания действий того, что происходит с ресурсом» — это правило попросту ставит телегу впереди лошади. Глагол указывает всем промежуточным агентам, является ли операция (не)модифицирующей, (не)кэшируемой, (не)идемпотентной и есть ли у запроса тело; вместо того, чтобы выбирать строго по этим четырём критериям, предлагается воспользоваться какой-то мнемоникой — если глагол подходит к смыслу операции, то и ок. Это в некоторых случаях просто опасно: вам может показаться, что `DELETE /list?element_index=3` прекрасно описывает ваше намерение удалить третий элемент списка, но т.к. эта операция неидемпотентна, использовать метод `DELETE` здесь нельзя;
3. «Используйте `POST` для создания сущностей, `GET` для доступа к ним, `PUT` для полной перезаписи, `PATCH` для частичной и `DELETE` для удаления» — вновь мнемоника, позволяющая «на пальцах» прикинуть, какие побочные эффекты возможны у какого из методов. Если попытаться разобраться в вопросе глубже, то получится, что вообще-то этот совет находится где-то между «бесполезен» и «вреден»:
* использовать метод `GET` в API имеет смысл тогда и только тогда, когда вы можете указать заголовки кэширования; если выставить `Cache-Control` в `no-cache` — то получится просто неявный `POST`; если их не указать совсем, то какой-то промежуточный агент может взять и додумать их за вас;
@ -143,25 +143,25 @@ Authorization: Bearer <token>
5. «Используйте множественное число для сущностей», «приписывайте слэш в конце URL» и тому подобные советы по стилистике кода, не имеющие никакого отношения к REST.
Осмелимся в конце этого раздела сформулировать четыре правила, которые позволят вам написать хорошее REST API:
Осмелимся в конце этого раздела сформулировать четыре правила, которые действительно позволят вам написать хорошее REST API:
1. Соблюдайте стандарт HTTP, *особенно* в части семантики методов, статусов и заголовков.
2. Рассматривайте URL как ключ кэша и ключ идемпотентности.
3. Проектируйте систему так, что она использует части URL (хост, путь, query-параметры), статусы и заголовки как метаинформацию, предназначенную для маршрутизации запроса внутри многослойной системы, возможно, с частичной перезаписью на каждом слое.
4. Читайте сигнатуры вызовов HTTP-методов вашего API как код, и применяйте к нему те же стилистические правила, что и к обычному коду: сигнатуры должны быть семантичными, консистентными и читабельными.
2. Используйте URL как ключ кэша и ключ идемпотентности.
3. Проектируйте архитектуру так, чтобы для организации маршрутизации запросов внутри многослойной системы было достаточно манипулировать частями URL (хост, путь, query-параметры), статусами и заголовками.
4. Рассматривайте сигнатуры вызовов HTTP-методов вашего API как код, и применяйте к нему те же стилистические правила, что и к коду: сигнатуры должны быть семантичными, консистентными и читабельными.
#### Преимущества и недостатки REST
Главное преимущество, которое вам предоставляет REST — возможность положиться на то, что промежуточные агенты, от клиентских фреймворков до API-гейтвеев, умеют читать мета-информацию запроса и выполнять какие-то действия с её использованием — настраивать политику перезапросов и таймауты, логировать, кэшировать, шардировать и так далее — без необходимости писать какой-то дополнительный код. Немаловажно уточнить, что, если вы этими преимуществами не пользуетесь, никакой REST вам не нужен.
Главное преимущество, которое вам предоставляет REST — возможность положиться на то, что промежуточные агенты, от клиентских фреймворков до API-гейтвеев, умеют читать метаданные запроса и выполнять какие-то действия с их использованием — настраивать политику перезапросов и таймауты, логировать, кэшировать, шардировать, проксировать и так далее — без необходимости писать какой-то дополнительный код. Немаловажно уточнить, что, если вы этими преимуществами не пользуетесь, никакой REST вам не нужен.
Главным недостатком REST является то, что промежуточные агенты, от клиентских фреймворков до API-гейтвеев, умеют читать мета-информацию запроса и выполнять какие-то действия с её использованием — настраивать политику перезапросов и таймауты, логировать, кэшировать, шардировать и так далее — даже если вы их об этом не просили. Более того, так как стандарты HTTP являются сложными, концепция REST — непонятной, а разработчики программного обеспечения — неидеальными, то промежуточные агенты могут трактовать метаданные запроса *неправильно*. Особенно это касается каких-то экзотических и сложных в имплементации стандартов.
Главным недостатком REST является то, что промежуточные агенты, от клиентских фреймворков до API-гейтвеев, умеют читать метаданные запроса и выполнять какие-то действия с их использованием — настраивать политику перезапросов и таймауты, логировать, кэшировать, шардировать, проксировать и так далее — даже если вы их об этом не просили. Более того, так как стандарты HTTP являются сложными, концепция REST — непонятной, а разработчики программного обеспечения — неидеальными, то промежуточные агенты могут трактовать метаданные запроса *неправильно*. Особенно это касается каких-то экзотических и сложных в имплементации стандартов.
Разработка распределённых систем в парадигме REST — это всегда некоторый торг: какую функциональность вы готовы отдать на откуп чужому коду, а какую — нет. Увы, нащупывать баланс приходится методом проб и ошибок.
#### О метапрограммировании и REST по Филдингу
Отдельно всё-таки выскажемся о трактовке REST по Филдингу-2008, которая, на самом деле, уходит корнями в распространённую концепцию [HATEOAS](https://en.wikipedia.org/wiki/HATEOAS). С одной стороны, она является довольно логичным продолжением концепций, изложенных выше: если машиночитаемыми будут не только метаданные текущей исполняемой операции, но и всех возможных операций над ресурсом, это, конечно, позволит построить гораздо более функциональные сетевые агенты. Вообще сама идея метапрограммирования, когда клиент является настолько сложной вычислительной машиной, что способен расширять сам себя без необходимости привлечь разработчика, который прочитает документацию API и напишет код работы с ним, конечно, выглядит весьма привлекательной для любого технократа.
Отдельно всё-таки выскажемся о трактовке REST по Филдингу-2008, которая, на самом деле, уходит корнями в распространённую концепцию [HATEOAS](https://en.wikipedia.org/wiki/HATEOAS). С одной стороны, она является довольно логичным продолжением принципов, изложенных выше: если машиночитаемыми будут не только метаданные текущей исполняемой операции, но и всех возможных операций над ресурсом, это, конечно, позволит построить гораздо более функциональные сетевые агенты. Вообще сама идея метапрограммирования, когда клиент является настолько сложной вычислительной машиной, что способен расширять сам себя без необходимости привлечь разработчика, который прочитает документацию API и напишет код работы с ним, конечно, выглядит весьма привлекательной для любого технократа.
Недостатком этой идеи является тот факт, что клиент будет расширять сам себя без привлечения разработчика, который прочитает документацию API и напишет код работы с ним. Возможно, в идеальном мире так работает; в реальном — нет. Любое большое API неидеально, в нём всегда есть концепции, для понимания которых (пока что) требуется живой человек. А поскольку, повторимся, API работает мультипликатором и ваших возможностей, и ваших ошибок, автоматизированное метапрограммирование роботом поверх API чревато очень-очень большими ошибками.
Недостатком этой идеи является тот факт, что клиент будет расширять сам себя без привлечения разработчика, который прочитает документацию API и напишет код работы с ним. Возможно, в идеальном мире так работает; в реальном — нет. Любое большое API неидеально, в нём всегда есть концепции, для понимания которых (пока что) требуется живой человек. А поскольку, повторимся, API работает мультипликатором и ваших возможностей, и ваших ошибок, автоматизированное метапрограммирование поверх API чревато очень-очень дорогими ошибками.
Пока сильный ИИ не разработан, мы всё-таки настаиваем на том, что код работы с API должен писать живой человек, который опирается на подробную документацию, а не догадки о смысле гиперссылок в ответе сервера.