You've already forked The-API-Book
mirror of
https://github.com/twirl/The-API-Book.git
synced 2025-08-10 21:51:42 +02:00
CRUD chapter refactoring
This commit is contained in:
BIN
docs/API.en.epub
BIN
docs/API.en.epub
Binary file not shown.
@@ -5198,18 +5198,7 @@ Location: /v1/orders/{id}
|
|||||||
</code></pre>
|
</code></pre>
|
||||||
</li>
|
</li>
|
||||||
</ol>
|
</ol>
|
||||||
<p>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 <code>POST</code> 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 <code>If-Match</code> header check policy to a more relaxed one. Conversely, repeating a <code>PUT</code> 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 (<code>POST /v1/orders</code> / <code>GET /v1/orders/{id}</code>), we now have four URLs and five operations:</p>
|
<p>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 <code>POST</code> 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 <code>If-Match</code> header check policy to a more relaxed one. Conversely, repeating a <code>PUT</code> 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.</p>
|
||||||
<ol>
|
|
||||||
<li>
|
|
||||||
<p>The draft creation URL (<code>POST /v1/drafts</code>), which additionally requires a method of retrieving pending drafts through something like <code>GET /v1/drafts/?user_id=<user_id></code>.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>The URL to confirm a draft, and perhaps the symmetrical operation of getting draft status (though the <code>GET /drafts</code> resource mentioned above might serve this purpose as well).</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>The URL of an order (<code>GET /v1/orders/{id}</code>).</p>
|
|
||||||
</li>
|
|
||||||
</ol>
|
|
||||||
<h5><a href="#chapter-38-paragraph-2" id="chapter-38-paragraph-2" class="anchor">2. Reading</a></h5>
|
<h5><a href="#chapter-38-paragraph-2" id="chapter-38-paragraph-2" class="anchor">2. Reading</a></h5>
|
||||||
<p>Let's continue. The reading operation is at first glance straightforward:</p>
|
<p>Let's continue. The reading operation is at first glance straightforward:</p>
|
||||||
<ul>
|
<ul>
|
||||||
@@ -5246,9 +5235,29 @@ Location: /v1/orders/{id}
|
|||||||
<p>If media data is attached to an entity, we will additionally require more endpoints to amend this metadata.</p>
|
<p>If media data is attached to an entity, we will additionally require more endpoints to amend this metadata.</p>
|
||||||
<h5><a href="#chapter-38-paragraph-4" id="chapter-38-paragraph-4" class="anchor">4. Deleting</a></h5>
|
<h5><a href="#chapter-38-paragraph-4" id="chapter-38-paragraph-4" class="anchor">4. Deleting</a></h5>
|
||||||
<p>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 <code>DELETE /v1/orders/{id}</code> endpoint there should be <code>PUT /v1/orders/{id}/archive</code> or <code>PUT /v1/archive?order=<order_id></code>.</p>
|
<p>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 <code>DELETE /v1/orders/{id}</code> endpoint there should be <code>PUT /v1/orders/{id}/archive</code> or <code>PUT /v1/archive?order=<order_id></code>.</p>
|
||||||
<h4>In Conclusion</h4>
|
<h4>Real-Life CRUD Operations</h4>
|
||||||
<p>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.</p>
|
<p>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:</p>
|
||||||
<p>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.</p><div class="page-break"></div><h3><a href="#http-api-errors" class="anchor" id="http-api-errors">Chapter 39. Working with HTTP API Errors</a><a href="#chapter-39" class="secondary-anchor" id="chapter-39"> </a></h3>
|
<ul>
|
||||||
|
<li><code>/v1/orders/</code> to be acted upon with <code>POST</code></li>
|
||||||
|
<li><code>/v1/orders/{id}</code> to be acted upon with <code>GET</code> / <code>PUT</code> / <code>DELETE</code> / optionally <code>PATCH</code>.</li>
|
||||||
|
</ul>
|
||||||
|
<p>However, if we add the following requirements:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Concurrency control in entity creation</li>
|
||||||
|
<li>Collaborative editing</li>
|
||||||
|
<li>Archiving entities</li>
|
||||||
|
<li>Searching entities with filters
|
||||||
|
then we end up with the following nomenclature of 8 URLs and 9-10 methods:</li>
|
||||||
|
<li><code>GET /v1/orders/?user_id=<user_id></code> to retrieve the ongoing orders, perhaps with additional simple filters</li>
|
||||||
|
<li><code>/v1/orders/drafts/?user_id=<user_id></code> to be acted upon with <code>POST</code> to create an order draft and with <code>GET</code> to retrieve existing drafts and the revision</li>
|
||||||
|
<li><code>PUT /v1/orders/drafts/{id}/commit</code> to commit the draft</li>
|
||||||
|
<li><code>GET /v1/orders/{id}</code> to retrieve the newly created order</li>
|
||||||
|
<li><code>POST /v1/orders/{id}/drafts</code> to create a draft for applying partial changes</li>
|
||||||
|
<li><code>PUT /v1/orders/{id}/drafts/{id}/commit</code> to apply the drafted changes</li>
|
||||||
|
<li><code>/v1/orders/search?user_id=<user_id></code> to be acted upon with either <code>GET</code> (for simple cases) or <code>POST</code> (in more complex scenarios) to search for orders</li>
|
||||||
|
<li><code>PUT /v1/orders/{id}/archive</code> to archive the order</li>
|
||||||
|
</ul>
|
||||||
|
<p>plus presumably a set of operations like <code>POST /v1/orders/{id}/cancel</code> 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.</p><div class="page-break"></div><h3><a href="#http-api-errors" class="anchor" id="http-api-errors">Chapter 39. Working with HTTP API Errors</a><a href="#chapter-39" class="secondary-anchor" id="chapter-39"> </a></h3>
|
||||||
<p>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.</p>
|
<p>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.</p>
|
||||||
<p>Imagine that some actor (a client or a gateway) tries to create a new order:</p>
|
<p>Imagine that some actor (a client or a gateway) tries to create a new order:</p>
|
||||||
<pre><code>POST /v1/orders?user_id=<user_id> HTTP/1.1
|
<pre><code>POST /v1/orders?user_id=<user_id> HTTP/1.1
|
||||||
|
BIN
docs/API.en.pdf
BIN
docs/API.en.pdf
Binary file not shown.
BIN
docs/API.ru.epub
BIN
docs/API.ru.epub
Binary file not shown.
@@ -5185,12 +5185,7 @@ Location: /v1/orders/{id}
|
|||||||
</code></pre>
|
</code></pre>
|
||||||
</li>
|
</li>
|
||||||
</ol>
|
</ol>
|
||||||
<p>Метод (2) в современных системах используется редко, так как вынуждает доверять правильности генерации идентификатора заказа клиентом. Если же рассматривать варианты (1) и (3), то необходимо отметить, что семантике протокола вариант (3) соответствует лучше, так как <code>POST</code>-запросы по умолчанию считаются неидемпотентными, и их автоматический повтор в случае получения сетевого таймаута или ошибки сервера будет выглядеть для постороннего наблюдателя опасной операцией (которой запрос и правда может стать, если сервер изменит политику проверки заголовка <code>If-Match</code> на более мягкую). Повтор <code>PUT</code>-запроса (а мы предполагаем, что таймауты и серверные ошибки на «тяжёлой» операции создания заказа намного более вероятны, чем на «лёгкой» операции создания черновика) вполне может быть автоматизирован, и не будет создавать дубликаты заказа, даже если проверка ревизии будет отключена вообще. Однако теперь вместо двух URL и двух операций (<code>POST /v1/orders</code> — <code>GET /v1/orders/{id}</code>) мы имеем четыре URL и пять операций:</p>
|
<p>Метод (2) в современных системах используется редко, так как вынуждает доверять правильности генерации идентификатора заказа клиентом. Если же рассматривать варианты (1) и (3), то необходимо отметить, что семантике протокола вариант (3) соответствует лучше, так как <code>POST</code>-запросы по умолчанию считаются неидемпотентными, и их автоматический повтор в случае получения сетевого таймаута или ошибки сервера будет выглядеть для постороннего наблюдателя опасной операцией (которой запрос и правда может стать, если сервер изменит политику проверки заголовка <code>If-Match</code> на более мягкую). Повтор <code>PUT</code>-запроса (а мы предполагаем, что таймауты и серверные ошибки на «тяжёлой» операции создания заказа намного более вероятны, чем на «лёгкой» операции создания черновика) вполне может быть автоматизирован, и не будет создавать дубликаты заказа, даже если проверка ревизии будет отключена вообще.</p>
|
||||||
<ol>
|
|
||||||
<li>URL создания черновика (<code>POST /v1/drafts</code>), который дополнительно потребует существования URL последнего черновика и/или списка черновиков пользователя (<code>GET /v1/drafts/?user_id=<user_id></code> или что-то аналогичное).</li>
|
|
||||||
<li>URL подтверждения черновика (<code>PUT /v1/drafts/{id}/status</code>) и, может быть, симметричную операцию чтения статуса черновика для получения актуальной ревизии (хотя эндпойнт <code>GET /v1/drafts</code>, описанный выше, для этого подходит лучше).</li>
|
|
||||||
<li>URL заказа (<code>GET /v1/orders/{id}</code>).</li>
|
|
||||||
</ol>
|
|
||||||
<h5><a href="#chapter-38-paragraph-2" id="chapter-38-paragraph-2" class="anchor">2. Чтение</a></h5>
|
<h5><a href="#chapter-38-paragraph-2" id="chapter-38-paragraph-2" class="anchor">2. Чтение</a></h5>
|
||||||
<p>Идём дальше. Операция чтения на первый взгляд не вызывает сомнений:</p>
|
<p>Идём дальше. Операция чтения на первый взгляд не вызывает сомнений:</p>
|
||||||
<ul>
|
<ul>
|
||||||
@@ -5225,9 +5220,31 @@ Location: /v1/orders/{id}
|
|||||||
<p>Если к сущности прилагаются медиаданные, для их редактирования также придётся разработать отдельные эндпойнты.</p>
|
<p>Если к сущности прилагаются медиаданные, для их редактирования также придётся разработать отдельные эндпойнты.</p>
|
||||||
<h5><a href="#chapter-38-paragraph-4" id="chapter-38-paragraph-4" class="anchor">4. Удаление</a></h5>
|
<h5><a href="#chapter-38-paragraph-4" id="chapter-38-paragraph-4" class="anchor">4. Удаление</a></h5>
|
||||||
<p>С удалением ситуация проще всего: никакие данные в современных сервисах не удаляются моментально, а лишь архивируются или помечаются удалёнными. Таким образом, вместо <code>DELETE /v1/orders/{id}</code> необходимо разработать эндпойнт типа <code>PUT /v1/orders/{id}/archive</code> или <code>PUT /v1/archive?order=<order_id></code>.</p>
|
<p>С удалением ситуация проще всего: никакие данные в современных сервисах не удаляются моментально, а лишь архивируются или помечаются удалёнными. Таким образом, вместо <code>DELETE /v1/orders/{id}</code> необходимо разработать эндпойнт типа <code>PUT /v1/orders/{id}/archive</code> или <code>PUT /v1/archive?order=<order_id></code>.</p>
|
||||||
<h4>В качестве заключения</h4>
|
<h4>CRUD-операции в реальной жизни</h4>
|
||||||
<p>Идея CRUD как способ минимальным набором операций описать типовые действия над ресурсом в при столкновении с реальностью быстро эволюционирует в сторону семейства эндпойнтов, каждый из которых описывает отдельный аспект взаимодействия с сущностью в течение её жизненного цикла.</p>
|
<p>Изложенные выше соображения не следует считать критикой концепции CRUD как таковой. Мы лишь указываем, что что в сложных предметных областях «срезание углов» и следование мнемоническим правилам редко работает. Мы начали с двух URL и четырёх-пяти методов манипуляции ими:</p>
|
||||||
<p>Изложенные выше соображения следует считать не критикой концепции CRUD как таковой, а скорее призывом не лениться и разрабатывать номенклатуру ресурсов и операций над ними исходя из конкретной предметной области, а не абстрактных мнемонических правил, к которым является эта концепция. Если вы всё же хотите разработать типовой API для манипуляции типовыми сущностями, стоит изначально разработать его гораздо более гибким, чем предлагает CRUD-HTTP методология.</p><div class="page-break"></div><h3><a href="#http-api-errors" class="anchor" id="http-api-errors">Глава 39. Работа с ошибками в HTTP API</a><a href="#chapter-39" class="secondary-anchor" id="chapter-39"> </a></h3>
|
<ul>
|
||||||
|
<li><code>/v1/orders/</code> для вызова с глаголом <code>POST</code>;</li>
|
||||||
|
<li><code>/v1/orders/{id}</code> для вызова с глаголами <code>GET</code> / <code>PUT</code> / <code>DELETE</code> / опционально <code>PATCH</code>.</li>
|
||||||
|
</ul>
|
||||||
|
<p>Однако, если мы выдвинем требования наличия возможностей:</p>
|
||||||
|
<ul>
|
||||||
|
<li>контроля параллелизма при создании сущностей;</li>
|
||||||
|
<li>совместного редактирования;</li>
|
||||||
|
<li>архивирования;</li>
|
||||||
|
<li>поиска с фильтрацией;</li>
|
||||||
|
</ul>
|
||||||
|
<p>то мы быстро придём к номенклатуре из 8 URL и 9-10 методов:</p>
|
||||||
|
<ul>
|
||||||
|
<li><code>GET /v1/orders/?user_id=<user_id></code> для получения списка текущих заказов, возможно с какими-то простыми фильтрами;</li>
|
||||||
|
<li><code>/v1/orders/drafts/?user_id=<user_id></code> для создания черновика заказа через метод <code>POST</code> и получения списка текущих черновиков и актуальной ревизии через метод <code>GET</code>;</li>
|
||||||
|
<li><code>PUT /v1/orders/drafts/{id}/commit</code> для подтверждения черновиков и создания заказов;</li>
|
||||||
|
<li><code>GET /v1/orders/{id}</code> для получения вновь созданного заказа;</li>
|
||||||
|
<li><code>POST /v1/orders/{id}/drafts</code> для создания черновика внесения списка изменений;</li>
|
||||||
|
<li><code>PUT /v1/orders/{id}/drafts/{id}/commit</code> для подтверждения черновика со списком изменений;</li>
|
||||||
|
<li><code>/v1/orders/search?user_id=<user_id></code> для поиска заказов через метод <code>GET</code> (простые случаи) или <code>POST</code> (если необходимы сложные фильтры);</li>
|
||||||
|
<li><code>PUT /v1/orders/{id}/archive</code> для архивирования заказа.</li>
|
||||||
|
</ul>
|
||||||
|
<p>плюс, вероятно, потребуются частные операции типа <code>POST /v1/orders/{id}/cancel</code> для проведения атомарных изменений. Именно это произойдёт в реальной жизни: идея CRUD как методологии описания типичных операций над ресурсом с помощью небольшого набора HTTP-глаголов быстро превратится в семейство эндпойнтов, каждый из которых покрывает какой-то аспект жизненного цикла сущности. Это всего лишь означает, что CRUD-мнемоника даёт только стартовый набор гипотез; любая конкретная предметная область требует вдумчивого подхода и дизайна подходящего API. Если же перед вами стоит задача разработать «универсальный» интерфейс, который подходит для работы с любыми сущностями, лучше сразу начинайте с номенклатуры в 10 методов типа описанной выше.</p><div class="page-break"></div><h3><a href="#http-api-errors" class="anchor" id="http-api-errors">Глава 39. Работа с ошибками в HTTP API</a><a href="#chapter-39" class="secondary-anchor" id="chapter-39"> </a></h3>
|
||||||
<p>Рассмотренные в предыдущих главах примеры организации API согласно стандарту HTTP и принципам REST покрывают т.н. «happy path», т.е. стандартный процесс работы с API в отсутствие ошибок. Конечно, нам не менее интересен и обратный кейс — каким образом HTTP API следует работать с ошибками, и чем стандарт и архитектурные принципы могут нам в этом помочь. Пусть какой-то агент в системе (неважно, клиент или гейтвей) пытается создать новый заказ:</p>
|
<p>Рассмотренные в предыдущих главах примеры организации API согласно стандарту HTTP и принципам REST покрывают т.н. «happy path», т.е. стандартный процесс работы с API в отсутствие ошибок. Конечно, нам не менее интересен и обратный кейс — каким образом HTTP API следует работать с ошибками, и чем стандарт и архитектурные принципы могут нам в этом помочь. Пусть какой-то агент в системе (неважно, клиент или гейтвей) пытается создать новый заказ:</p>
|
||||||
<pre><code>POST /v1/orders?user_id=<user_id> HTTP/1.1
|
<pre><code>POST /v1/orders?user_id=<user_id> HTTP/1.1
|
||||||
Authorization: Bearer <token>
|
Authorization: Bearer <token>
|
||||||
|
BIN
docs/API.ru.pdf
BIN
docs/API.ru.pdf
Binary file not shown.
@@ -118,13 +118,7 @@ Let's start with the resource creation operation. As we remember from the “[Sy
|
|||||||
Location: /v1/orders/{id}
|
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:
|
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.
|
||||||
|
|
||||||
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=<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}`).
|
|
||||||
|
|
||||||
##### Reading
|
##### 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=<order_id>`.
|
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>`.
|
||||||
|
|
||||||
#### 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.
|
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=<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 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=<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.
|
@@ -117,11 +117,7 @@
|
|||||||
Location: /v1/orders/{id}
|
Location: /v1/orders/{id}
|
||||||
```
|
```
|
||||||
|
|
||||||
Метод (2) в современных системах используется редко, так как вынуждает доверять правильности генерации идентификатора заказа клиентом. Если же рассматривать варианты (1) и (3), то необходимо отметить, что семантике протокола вариант (3) соответствует лучше, так как `POST`-запросы по умолчанию считаются неидемпотентными, и их автоматический повтор в случае получения сетевого таймаута или ошибки сервера будет выглядеть для постороннего наблюдателя опасной операцией (которой запрос и правда может стать, если сервер изменит политику проверки заголовка `If-Match` на более мягкую). Повтор `PUT`-запроса (а мы предполагаем, что таймауты и серверные ошибки на «тяжёлой» операции создания заказа намного более вероятны, чем на «лёгкой» операции создания черновика) вполне может быть автоматизирован, и не будет создавать дубликаты заказа, даже если проверка ревизии будет отключена вообще. Однако теперь вместо двух URL и двух операций (`POST /v1/orders` — `GET /v1/orders/{id}`) мы имеем четыре URL и пять операций:
|
Метод (2) в современных системах используется редко, так как вынуждает доверять правильности генерации идентификатора заказа клиентом. Если же рассматривать варианты (1) и (3), то необходимо отметить, что семантике протокола вариант (3) соответствует лучше, так как `POST`-запросы по умолчанию считаются неидемпотентными, и их автоматический повтор в случае получения сетевого таймаута или ошибки сервера будет выглядеть для постороннего наблюдателя опасной операцией (которой запрос и правда может стать, если сервер изменит политику проверки заголовка `If-Match` на более мягкую). Повтор `PUT`-запроса (а мы предполагаем, что таймауты и серверные ошибки на «тяжёлой» операции создания заказа намного более вероятны, чем на «лёгкой» операции создания черновика) вполне может быть автоматизирован, и не будет создавать дубликаты заказа, даже если проверка ревизии будет отключена вообще.
|
||||||
|
|
||||||
1. URL создания черновика (`POST /v1/drafts`), который дополнительно потребует существования URL последнего черновика и/или списка черновиков пользователя (`GET /v1/drafts/?user_id=<user_id>` или что-то аналогичное).
|
|
||||||
2. URL подтверждения черновика (`PUT /v1/drafts/{id}/status`) и, может быть, симметричную операцию чтения статуса черновика для получения актуальной ревизии (хотя эндпойнт `GET /v1/drafts`, описанный выше, для этого подходит лучше).
|
|
||||||
3. URL заказа (`GET /v1/orders/{id}`).
|
|
||||||
|
|
||||||
##### Чтение
|
##### Чтение
|
||||||
|
|
||||||
@@ -155,8 +151,26 @@
|
|||||||
|
|
||||||
С удалением ситуация проще всего: никакие данные в современных сервисах не удаляются моментально, а лишь архивируются или помечаются удалёнными. Таким образом, вместо `DELETE /v1/orders/{id}` необходимо разработать эндпойнт типа `PUT /v1/orders/{id}/archive` или `PUT /v1/archive?order=<order_id>`.
|
С удалением ситуация проще всего: никакие данные в современных сервисах не удаляются моментально, а лишь архивируются или помечаются удалёнными. Таким образом, вместо `DELETE /v1/orders/{id}` необходимо разработать эндпойнт типа `PUT /v1/orders/{id}/archive` или `PUT /v1/archive?order=<order_id>`.
|
||||||
|
|
||||||
#### В качестве заключения
|
#### CRUD-операции в реальной жизни
|
||||||
|
|
||||||
Идея CRUD как способ минимальным набором операций описать типовые действия над ресурсом в при столкновении с реальностью быстро эволюционирует в сторону семейства эндпойнтов, каждый из которых описывает отдельный аспект взаимодействия с сущностью в течение её жизненного цикла.
|
Изложенные выше соображения не следует считать критикой концепции CRUD как таковой. Мы лишь указываем, что что в сложных предметных областях «срезание углов» и следование мнемоническим правилам редко работает. Мы начали с двух URL и четырёх-пяти методов манипуляции ими:
|
||||||
|
* `/v1/orders/` для вызова с глаголом `POST`;
|
||||||
|
* `/v1/orders/{id}` для вызова с глаголами `GET` / `PUT` / `DELETE` / опционально `PATCH`.
|
||||||
|
|
||||||
Изложенные выше соображения следует считать не критикой концепции CRUD как таковой, а скорее призывом не лениться и разрабатывать номенклатуру ресурсов и операций над ними исходя из конкретной предметной области, а не абстрактных мнемонических правил, к которым является эта концепция. Если вы всё же хотите разработать типовой API для манипуляции типовыми сущностями, стоит изначально разработать его гораздо более гибким, чем предлагает CRUD-HTTP методология.
|
Однако, если мы выдвинем требования наличия возможностей:
|
||||||
|
* контроля параллелизма при создании сущностей;
|
||||||
|
* совместного редактирования;
|
||||||
|
* архивирования;
|
||||||
|
* поиска с фильтрацией;
|
||||||
|
|
||||||
|
то мы быстро придём к номенклатуре из 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 методов типа описанной выше.
|
||||||
|
Reference in New Issue
Block a user