You've already forked The-API-Book
mirror of
https://github.com/twirl/The-API-Book.git
synced 2025-12-11 22:17:03 +02:00
CRUD chapter refactoring
This commit is contained in:
@@ -5198,18 +5198,7 @@ Location: /v1/orders/{id}
|
||||
</code></pre>
|
||||
</li>
|
||||
</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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<h4>In Conclusion</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 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>
|
||||
<h4>Real-Life CRUD Operations</h4>
|
||||
<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>
|
||||
<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>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
|
||||
|
||||
Reference in New Issue
Block a user