diff --git a/docs/API.en.epub b/docs/API.en.epub index 499bfc5..1447812 100644 Binary files a/docs/API.en.epub and b/docs/API.en.epub differ diff --git a/docs/API.en.html b/docs/API.en.html index 669872b..29b3469 100644 --- a/docs/API.en.html +++ b/docs/API.en.html @@ -5198,18 +5198,7 @@ Location: /v1/orders/{id} -
Approach #2 is rarely used in modern systems as it requires trusting the client to generate identifiers properly. If we consider options #1 and #3, we must note that the latter conforms to HTTP semantics better as POST
requests are considered non-idempotent by default and should not be repeated in case of a timeout or server error. Therefore, repeating a request would appear as a mistake from an external observer's perspective, and it could indeed become one if the server changes the If-Match
header check policy to a more relaxed one. Conversely, repeating a PUT
request (assuming that getting a timeout or an error while performing a “heavy” order creation operation is much more probable than in the case of a “lightweight” draft creation) could be automated and would not result in order duplication even if the revision check is disabled. However, instead of two URLs and two operations (POST /v1/orders
/ GET /v1/orders/{id}
), we now have four URLs and five operations:
The draft creation URL (POST /v1/drafts
), which additionally requires a method of retrieving pending drafts through something like GET /v1/drafts/?user_id=<user_id>
.
The URL to confirm a draft, and perhaps the symmetrical operation of getting draft status (though the GET /drafts
resource mentioned above might serve this purpose as well).
The URL of an order (GET /v1/orders/{id}
).
Approach #2 is rarely used in modern systems as it requires trusting the client to generate identifiers properly. If we consider options #1 and #3, we must note that the latter conforms to HTTP semantics better as POST
requests are considered non-idempotent by default and should not be repeated in case of a timeout or server error. Therefore, repeating a request would appear as a mistake from an external observer's perspective, and it could indeed become one if the server changes the If-Match
header check policy to a more relaxed one. Conversely, repeating a PUT
request (assuming that getting a timeout or an error while performing a “heavy” order creation operation is much more probable than in the case of a “lightweight” draft creation) could be automated and would not result in order duplication even if the revision check is disabled.
Let's continue. The reading operation is at first glance straightforward:
If media data is attached to an entity, we will additionally require more endpoints to amend this metadata.
Finally, with deleting resources the situation is simple: in modern services, data is never deleted, only archived or marked as deleted. Therefore, instead of a DELETE /v1/orders/{id}
endpoint there should be PUT /v1/orders/{id}/archive
or PUT /v1/archive?order=<order_id>
.
The idea of CRUD as a methodology of describing typical operations applied to resources with a small set of uniform verbs quickly evolves towards a bucket of different endpoints, each of them covering some specific aspect of working with the entity during its lifecycle.
-This discourse is not to be perceived as criticizing the idea of CRUD itself. We just point out that in complex subject areas cutting edges and sticking to some mnemonic rules rarely plays out. It is much better to design entity manipulation URLs based on specific use cases. And if you do want to have a uniform interface to manipulate typical entities, you would rather initially design it much more detailed and extensible than just a set of four HTTP-CRUD methods.
This discourse is not intended to be perceived as criticizing the idea of CRUD operations itself. We simply point out that in complex subject areas, cutting edges and sticking to some mnemonic rules rarely play out. We started with the idea of having two URLs and four or five methods to apply to them:
+/v1/orders/
to be acted upon with POST
/v1/orders/{id}
to be acted upon with GET
/ PUT
/ DELETE
/ optionally PATCH
.However, if we add the following requirements:
+GET /v1/orders/?user_id=<user_id>
to retrieve the ongoing orders, perhaps with additional simple filters/v1/orders/drafts/?user_id=<user_id>
to be acted upon with POST
to create an order draft and with GET
to retrieve existing drafts and the revisionPUT /v1/orders/drafts/{id}/commit
to commit the draftGET /v1/orders/{id}
to retrieve the newly created orderPOST /v1/orders/{id}/drafts
to create a draft for applying partial changesPUT /v1/orders/{id}/drafts/{id}/commit
to apply the drafted changes/v1/orders/search?user_id=<user_id>
to be acted upon with either GET
(for simple cases) or POST
(in more complex scenarios) to search for ordersPUT /v1/orders/{id}/archive
to archive the orderplus presumably a set of operations like POST /v1/orders/{id}/cancel
for executing atomic actions on entities. This is what is likely to happen in real life: the idea of CRUD as a methodology for describing typical operations applied to resources with a small set of uniform verbs quickly evolves towards a bucket of different endpoints, each covering a specific aspect of working with the entity during its lifecycle. This only proves that mnemonics are just helpful starting points; each situation requires a thorough understanding of the subject area and designing an API that fits it. However, if your task is to develop a “universal” interface that fits every kind of entity, we would strongly suggest starting with something like the ten-method nomenclature described above.
The examples of organizing HTTP APIs discussed in the previous chapters were mostly about “happy paths,” i.e., the direct path of working with an API in the absence of obstacles. It's now time to talk about the opposite case: how HTTP APIs should work with errors and how the standard and the REST architectural principles can help us.
Imagine that some actor (a client or a gateway) tries to create a new order:
POST /v1/orders?user_id=<user_id> HTTP/1.1
diff --git a/docs/API.en.pdf b/docs/API.en.pdf
index 61809aa..7b1088b 100644
Binary files a/docs/API.en.pdf and b/docs/API.en.pdf differ
diff --git a/docs/API.ru.epub b/docs/API.ru.epub
index 13447c9..d279188 100644
Binary files a/docs/API.ru.epub and b/docs/API.ru.epub differ
diff --git a/docs/API.ru.html b/docs/API.ru.html
index d4d2718..a7c69d9 100644
--- a/docs/API.ru.html
+++ b/docs/API.ru.html
@@ -5185,12 +5185,7 @@ Location: /v1/orders/{id}
-Метод (2) в современных системах используется редко, так как вынуждает доверять правильности генерации идентификатора заказа клиентом. Если же рассматривать варианты (1) и (3), то необходимо отметить, что семантике протокола вариант (3) соответствует лучше, так как POST
-запросы по умолчанию считаются неидемпотентными, и их автоматический повтор в случае получения сетевого таймаута или ошибки сервера будет выглядеть для постороннего наблюдателя опасной операцией (которой запрос и правда может стать, если сервер изменит политику проверки заголовка If-Match
на более мягкую). Повтор PUT
-запроса (а мы предполагаем, что таймауты и серверные ошибки на «тяжёлой» операции создания заказа намного более вероятны, чем на «лёгкой» операции создания черновика) вполне может быть автоматизирован, и не будет создавать дубликаты заказа, даже если проверка ревизии будет отключена вообще. Однако теперь вместо двух URL и двух операций (POST /v1/orders
— GET /v1/orders/{id}
) мы имеем четыре URL и пять операций:
POST /v1/drafts
), который дополнительно потребует существования URL последнего черновика и/или списка черновиков пользователя (GET /v1/drafts/?user_id=<user_id>
или что-то аналогичное).PUT /v1/drafts/{id}/status
) и, может быть, симметричную операцию чтения статуса черновика для получения актуальной ревизии (хотя эндпойнт GET /v1/drafts
, описанный выше, для этого подходит лучше).GET /v1/orders/{id}
).Метод (2) в современных системах используется редко, так как вынуждает доверять правильности генерации идентификатора заказа клиентом. Если же рассматривать варианты (1) и (3), то необходимо отметить, что семантике протокола вариант (3) соответствует лучше, так как POST
-запросы по умолчанию считаются неидемпотентными, и их автоматический повтор в случае получения сетевого таймаута или ошибки сервера будет выглядеть для постороннего наблюдателя опасной операцией (которой запрос и правда может стать, если сервер изменит политику проверки заголовка If-Match
на более мягкую). Повтор PUT
-запроса (а мы предполагаем, что таймауты и серверные ошибки на «тяжёлой» операции создания заказа намного более вероятны, чем на «лёгкой» операции создания черновика) вполне может быть автоматизирован, и не будет создавать дубликаты заказа, даже если проверка ревизии будет отключена вообще.
Идём дальше. Операция чтения на первый взгляд не вызывает сомнений:
Если к сущности прилагаются медиаданные, для их редактирования также придётся разработать отдельные эндпойнты.
С удалением ситуация проще всего: никакие данные в современных сервисах не удаляются моментально, а лишь архивируются или помечаются удалёнными. Таким образом, вместо DELETE /v1/orders/{id}
необходимо разработать эндпойнт типа PUT /v1/orders/{id}/archive
или PUT /v1/archive?order=<order_id>
.
Идея CRUD как способ минимальным набором операций описать типовые действия над ресурсом в при столкновении с реальностью быстро эволюционирует в сторону семейства эндпойнтов, каждый из которых описывает отдельный аспект взаимодействия с сущностью в течение её жизненного цикла.
-Изложенные выше соображения следует считать не критикой концепции CRUD как таковой, а скорее призывом не лениться и разрабатывать номенклатуру ресурсов и операций над ними исходя из конкретной предметной области, а не абстрактных мнемонических правил, к которым является эта концепция. Если вы всё же хотите разработать типовой API для манипуляции типовыми сущностями, стоит изначально разработать его гораздо более гибким, чем предлагает CRUD-HTTP методология.
Изложенные выше соображения не следует считать критикой концепции CRUD как таковой. Мы лишь указываем, что что в сложных предметных областях «срезание углов» и следование мнемоническим правилам редко работает. Мы начали с двух URL и четырёх-пяти методов манипуляции ими:
+/v1/orders/
для вызова с глаголом POST
;/v1/orders/{id}
для вызова с глаголами GET
/ PUT
/ DELETE
/ опционально PATCH
.Однако, если мы выдвинем требования наличия возможностей:
+то мы быстро придём к номенклатуре из 8 URL и 9-10 методов:
+GET /v1/orders/?user_id=<user_id>
для получения списка текущих заказов, возможно с какими-то простыми фильтрами;/v1/orders/drafts/?user_id=<user_id>
для создания черновика заказа через метод POST
и получения списка текущих черновиков и актуальной ревизии через метод GET
;PUT /v1/orders/drafts/{id}/commit
для подтверждения черновиков и создания заказов;GET /v1/orders/{id}
для получения вновь созданного заказа;POST /v1/orders/{id}/drafts
для создания черновика внесения списка изменений;PUT /v1/orders/{id}/drafts/{id}/commit
для подтверждения черновика со списком изменений;/v1/orders/search?user_id=<user_id>
для поиска заказов через метод GET
(простые случаи) или POST
(если необходимы сложные фильтры);PUT /v1/orders/{id}/archive
для архивирования заказа.плюс, вероятно, потребуются частные операции типа POST /v1/orders/{id}/cancel
для проведения атомарных изменений. Именно это произойдёт в реальной жизни: идея CRUD как методологии описания типичных операций над ресурсом с помощью небольшого набора HTTP-глаголов быстро превратится в семейство эндпойнтов, каждый из которых покрывает какой-то аспект жизненного цикла сущности. Это всего лишь означает, что CRUD-мнемоника даёт только стартовый набор гипотез; любая конкретная предметная область требует вдумчивого подхода и дизайна подходящего API. Если же перед вами стоит задача разработать «универсальный» интерфейс, который подходит для работы с любыми сущностями, лучше сразу начинайте с номенклатуры в 10 методов типа описанной выше.
Рассмотренные в предыдущих главах примеры организации API согласно стандарту HTTP и принципам REST покрывают т.н. «happy path», т.е. стандартный процесс работы с API в отсутствие ошибок. Конечно, нам не менее интересен и обратный кейс — каким образом HTTP API следует работать с ошибками, и чем стандарт и архитектурные принципы могут нам в этом помочь. Пусть какой-то агент в системе (неважно, клиент или гейтвей) пытается создать новый заказ:
POST /v1/orders?user_id=<user_id> HTTP/1.1
Authorization: Bearer <token>
diff --git a/docs/API.ru.pdf b/docs/API.ru.pdf
index 1251846..08779d9 100644
Binary files a/docs/API.ru.pdf and b/docs/API.ru.pdf differ
diff --git a/src/en/clean-copy/05-Section IV. HTTP APIs & the REST Architectural Principles/06.md b/src/en/clean-copy/05-Section IV. HTTP APIs & the REST Architectural Principles/06.md
index eee3d51..8545e06 100644
--- a/src/en/clean-copy/05-Section IV. HTTP APIs & the REST Architectural Principles/06.md
+++ b/src/en/clean-copy/05-Section IV. HTTP APIs & the REST Architectural Principles/06.md
@@ -118,13 +118,7 @@ Let's start with the resource creation operation. As we remember from the “[Sy
Location: /v1/orders/{id}
```
-Approach \#2 is rarely used in modern systems as it requires trusting the client to generate identifiers properly. If we consider options \#1 and \#3, we must note that the latter conforms to HTTP semantics better as `POST` requests are considered non-idempotent by default and should not be repeated in case of a timeout or server error. Therefore, repeating a request would appear as a mistake from an external observer's perspective, and it could indeed become one if the server changes the `If-Match` header check policy to a more relaxed one. Conversely, repeating a `PUT` request (assuming that getting a timeout or an error while performing a “heavy” order creation operation is much more probable than in the case of a “lightweight” draft creation) could be automated and would not result in order duplication even if the revision check is disabled. However, instead of two URLs and two operations (`POST /v1/orders` / `GET /v1/orders/{id}`), we now have four URLs and five operations:
-
- 1. The draft creation URL (`POST /v1/drafts`), which additionally requires a method of retrieving pending drafts through something like `GET /v1/drafts/?user_id=`.
-
- 2. The URL to confirm a draft, and perhaps the symmetrical operation of getting draft status (though the `GET /drafts` resource mentioned above might serve this purpose as well).
-
- 3. The URL of an order (`GET /v1/orders/{id}`).
+Approach \#2 is rarely used in modern systems as it requires trusting the client to generate identifiers properly. If we consider options \#1 and \#3, we must note that the latter conforms to HTTP semantics better as `POST` requests are considered non-idempotent by default and should not be repeated in case of a timeout or server error. Therefore, repeating a request would appear as a mistake from an external observer's perspective, and it could indeed become one if the server changes the `If-Match` header check policy to a more relaxed one. Conversely, repeating a `PUT` request (assuming that getting a timeout or an error while performing a “heavy” order creation operation is much more probable than in the case of a “lightweight” draft creation) could be automated and would not result in order duplication even if the revision check is disabled.
##### Reading
@@ -161,8 +155,25 @@ If media data is attached to an entity, we will additionally require more endpoi
Finally, with deleting resources the situation is simple: in modern services, data is never deleted, only archived or marked as deleted. Therefore, instead of a `DELETE /v1/orders/{id}` endpoint there should be `PUT /v1/orders/{id}/archive` or `PUT /v1/archive?order=`.
-#### In Conclusion
+#### Real-Life CRUD Operations
-The idea of CRUD as a methodology of describing typical operations applied to resources with a small set of uniform verbs quickly evolves towards a bucket of different endpoints, each of them covering some specific aspect of working with the entity during its lifecycle.
+This discourse is not intended to be perceived as criticizing the idea of CRUD operations itself. We simply point out that in complex subject areas, cutting edges and sticking to some mnemonic rules rarely play out. We started with the idea of having two URLs and four or five methods to apply to them:
+ * `/v1/orders/` to be acted upon with `POST`
+ * `/v1/orders/{id}` to be acted upon with `GET` / `PUT` / `DELETE` / optionally `PATCH`.
-This discourse is not to be perceived as criticizing the idea of CRUD itself. We just point out that in complex subject areas cutting edges and sticking to some mnemonic rules rarely plays out. It is much better to design entity manipulation URLs based on specific use cases. And if you do want to have a uniform interface to manipulate typical entities, you would rather initially design it much more detailed and extensible than just a set of four HTTP-CRUD methods.
\ No newline at end of file
+However, if we add the following requirements:
+ * Concurrency control in entity creation
+ * Collaborative editing
+ * Archiving entities
+ * Searching entities with filters
+then we end up with the following nomenclature of 8 URLs and 9-10 methods:
+ * `GET /v1/orders/?user_id=` to retrieve the ongoing orders, perhaps with additional simple filters
+ * `/v1/orders/drafts/?user_id=` to be acted upon with `POST` to create an order draft and with `GET` to retrieve existing drafts and the revision
+ * `PUT /v1/orders/drafts/{id}/commit` to commit the draft
+ * `GET /v1/orders/{id}` to retrieve the newly created order
+ * `POST /v1/orders/{id}/drafts` to create a draft for applying partial changes
+ * `PUT /v1/orders/{id}/drafts/{id}/commit` to apply the drafted changes
+ * `/v1/orders/search?user_id=` to be acted upon with either `GET` (for simple cases) or `POST` (in more complex scenarios) to search for orders
+ * `PUT /v1/orders/{id}/archive` to archive the order
+
+plus presumably a set of operations like `POST /v1/orders/{id}/cancel` for executing atomic actions on entities. This is what is likely to happen in real life: the idea of CRUD as a methodology for describing typical operations applied to resources with a small set of uniform verbs quickly evolves towards a bucket of different endpoints, each covering a specific aspect of working with the entity during its lifecycle. This only proves that mnemonics are just helpful starting points; each situation requires a thorough understanding of the subject area and designing an API that fits it. However, if your task is to develop a “universal” interface that fits every kind of entity, we would strongly suggest starting with something like the ten-method nomenclature described above.
\ No newline at end of file
diff --git a/src/ru/clean-copy/05-Раздел IV. HTTP API и архитектурные принципы REST/06.md b/src/ru/clean-copy/05-Раздел IV. HTTP API и архитектурные принципы REST/06.md
index 2131225..32d3fdc 100644
--- a/src/ru/clean-copy/05-Раздел IV. HTTP API и архитектурные принципы REST/06.md
+++ b/src/ru/clean-copy/05-Раздел IV. HTTP API и архитектурные принципы REST/06.md
@@ -117,11 +117,7 @@
Location: /v1/orders/{id}
```
-Метод (2) в современных системах используется редко, так как вынуждает доверять правильности генерации идентификатора заказа клиентом. Если же рассматривать варианты (1) и (3), то необходимо отметить, что семантике протокола вариант (3) соответствует лучше, так как `POST`-запросы по умолчанию считаются неидемпотентными, и их автоматический повтор в случае получения сетевого таймаута или ошибки сервера будет выглядеть для постороннего наблюдателя опасной операцией (которой запрос и правда может стать, если сервер изменит политику проверки заголовка `If-Match` на более мягкую). Повтор `PUT`-запроса (а мы предполагаем, что таймауты и серверные ошибки на «тяжёлой» операции создания заказа намного более вероятны, чем на «лёгкой» операции создания черновика) вполне может быть автоматизирован, и не будет создавать дубликаты заказа, даже если проверка ревизии будет отключена вообще. Однако теперь вместо двух URL и двух операций (`POST /v1/orders` — `GET /v1/orders/{id}`) мы имеем четыре URL и пять операций:
-
- 1. URL создания черновика (`POST /v1/drafts`), который дополнительно потребует существования URL последнего черновика и/или списка черновиков пользователя (`GET /v1/drafts/?user_id=` или что-то аналогичное).
- 2. URL подтверждения черновика (`PUT /v1/drafts/{id}/status`) и, может быть, симметричную операцию чтения статуса черновика для получения актуальной ревизии (хотя эндпойнт `GET /v1/drafts`, описанный выше, для этого подходит лучше).
- 3. URL заказа (`GET /v1/orders/{id}`).
+Метод (2) в современных системах используется редко, так как вынуждает доверять правильности генерации идентификатора заказа клиентом. Если же рассматривать варианты (1) и (3), то необходимо отметить, что семантике протокола вариант (3) соответствует лучше, так как `POST`-запросы по умолчанию считаются неидемпотентными, и их автоматический повтор в случае получения сетевого таймаута или ошибки сервера будет выглядеть для постороннего наблюдателя опасной операцией (которой запрос и правда может стать, если сервер изменит политику проверки заголовка `If-Match` на более мягкую). Повтор `PUT`-запроса (а мы предполагаем, что таймауты и серверные ошибки на «тяжёлой» операции создания заказа намного более вероятны, чем на «лёгкой» операции создания черновика) вполне может быть автоматизирован, и не будет создавать дубликаты заказа, даже если проверка ревизии будет отключена вообще.
##### Чтение
@@ -155,8 +151,26 @@
С удалением ситуация проще всего: никакие данные в современных сервисах не удаляются моментально, а лишь архивируются или помечаются удалёнными. Таким образом, вместо `DELETE /v1/orders/{id}` необходимо разработать эндпойнт типа `PUT /v1/orders/{id}/archive` или `PUT /v1/archive?order=`.
-#### В качестве заключения
+#### CRUD-операции в реальной жизни
-Идея CRUD как способ минимальным набором операций описать типовые действия над ресурсом в при столкновении с реальностью быстро эволюционирует в сторону семейства эндпойнтов, каждый из которых описывает отдельный аспект взаимодействия с сущностью в течение её жизненного цикла.
+Изложенные выше соображения не следует считать критикой концепции CRUD как таковой. Мы лишь указываем, что что в сложных предметных областях «срезание углов» и следование мнемоническим правилам редко работает. Мы начали с двух URL и четырёх-пяти методов манипуляции ими:
+ * `/v1/orders/` для вызова с глаголом `POST`;
+ * `/v1/orders/{id}` для вызова с глаголами `GET` / `PUT` / `DELETE` / опционально `PATCH`.
-Изложенные выше соображения следует считать не критикой концепции CRUD как таковой, а скорее призывом не лениться и разрабатывать номенклатуру ресурсов и операций над ними исходя из конкретной предметной области, а не абстрактных мнемонических правил, к которым является эта концепция. Если вы всё же хотите разработать типовой API для манипуляции типовыми сущностями, стоит изначально разработать его гораздо более гибким, чем предлагает CRUD-HTTP методология.
\ No newline at end of file
+Однако, если мы выдвинем требования наличия возможностей:
+ * контроля параллелизма при создании сущностей;
+ * совместного редактирования;
+ * архивирования;
+ * поиска с фильтрацией;
+
+то мы быстро придём к номенклатуре из 8 URL и 9-10 методов:
+ * `GET /v1/orders/?user_id=` для получения списка текущих заказов, возможно с какими-то простыми фильтрами;
+ * `/v1/orders/drafts/?user_id=` для создания черновика заказа через метод `POST` и получения списка текущих черновиков и актуальной ревизии через метод `GET`;
+ * `PUT /v1/orders/drafts/{id}/commit` для подтверждения черновиков и создания заказов;
+ * `GET /v1/orders/{id}` для получения вновь созданного заказа;
+ * `POST /v1/orders/{id}/drafts` для создания черновика внесения списка изменений;
+ * `PUT /v1/orders/{id}/drafts/{id}/commit` для подтверждения черновика со списком изменений;
+ * `/v1/orders/search?user_id=` для поиска заказов через метод `GET` (простые случаи) или `POST` (если необходимы сложные фильтры);
+ * `PUT /v1/orders/{id}/archive` для архивирования заказа.
+
+плюс, вероятно, потребуются частные операции типа `POST /v1/orders/{id}/cancel` для проведения атомарных изменений. Именно это произойдёт в реальной жизни: идея CRUD как методологии описания типичных операций над ресурсом с помощью небольшого набора HTTP-глаголов быстро превратится в семейство эндпойнтов, каждый из которых покрывает какой-то аспект жизненного цикла сущности. Это всего лишь означает, что CRUD-мнемоника даёт только стартовый набор гипотез; любая конкретная предметная область требует вдумчивого подхода и дизайна подходящего API. Если же перед вами стоит задача разработать «универсальный» интерфейс, который подходит для работы с любыми сущностями, лучше сразу начинайте с номенклатуры в 10 методов типа описанной выше.