mirror of
https://github.com/twirl/The-API-Book.git
synced 2025-03-17 20:42:26 +02:00
even more fixes and clarifications to chapter 11
This commit is contained in:
parent
31629517e9
commit
57c6d5a7e8
@ -233,7 +233,7 @@ GET /v1/users/{id}/orders
|
||||
##### Avoid double negations
|
||||
|
||||
**Bad**: `"dont_call_me": false`
|
||||
— people are bad at perceiving double negation, making mistakes.
|
||||
— humans are bad at perceiving double negation; make mistakes.
|
||||
|
||||
**Better**: `"prohibit_calling": true` or `"avoid_calling": true`
|
||||
— it's easier to read, though you shouldn't deceive yourself. Avoid semantical double negations, even if you've found a ‘negative’ word without ‘negative’ prefix.
|
||||
@ -261,7 +261,7 @@ GET /coffee-machines/{id}/stocks
|
||||
|
||||
##### Avoid implicit type conversion
|
||||
|
||||
This advice is opposite to the previous one, ironically. When developing APIs you frequently need to add new optional field with non-empty default value. For example:
|
||||
This advice is opposite to the previous one, ironically. When developing APIs you frequently need to add a new optional field with non-empty default value. For example:
|
||||
|
||||
```
|
||||
POST /v1/orders
|
||||
@ -272,7 +272,7 @@ POST /v1/orders
|
||||
}
|
||||
```
|
||||
|
||||
New `contactless_delivery` options isn't required, but its default value is `true`. A question arises: how developers should discern explicit intention to abolish the option (`false`) from knowing not it exists (field isn't set). They have to write something like:
|
||||
New `contactless_delivery` option isn't required, but its default value is `true`. A question arises: how developers should discern explicit intention to abolish the option (`false`) from knowing not it exists (field isn't set). They have to write something like:
|
||||
|
||||
```
|
||||
if (Type(order.contactless_delivery) == 'Boolean' &&
|
||||
@ -303,16 +303,16 @@ POST /users
|
||||
{ … }
|
||||
→
|
||||
// Users are created with a monthly
|
||||
// spendings limit set by default
|
||||
// spending limit set by default
|
||||
{
|
||||
…
|
||||
"spendings_monthly_limit_usd": "100"
|
||||
"spending_monthly_limit_usd": "100"
|
||||
}
|
||||
// To cancel the limit null value is used
|
||||
POST /users
|
||||
{
|
||||
…
|
||||
"spendings_monthly_limit_usd": null
|
||||
"spending_monthly_limit_usd": null
|
||||
}
|
||||
```
|
||||
|
||||
@ -321,19 +321,19 @@ POST /users
|
||||
POST /users
|
||||
{
|
||||
// true — user explicitly cancels
|
||||
// monthly spendings limit
|
||||
// monthly spending limit
|
||||
// false — limit isn't canceled
|
||||
// (default value)
|
||||
"abolish_spendings_limit": false,
|
||||
"abolish_spending_limit": false,
|
||||
// Non-required field
|
||||
// Only present if the previous flag
|
||||
// is set to false
|
||||
"spendings_monthly_limit_usd": "100",
|
||||
"spending_monthly_limit_usd": "100",
|
||||
…
|
||||
}
|
||||
```
|
||||
|
||||
**NB**: the contradiction with the previous rule lies in the necessity of introducing ‘negative’ flags (the ‘no limit’ flag), which we had to rename to `abolish_spendings_limit`. Though it's a decent name for a negative flag, its semantics is still unobvious, and developers will have to read the docs. That's the way.
|
||||
**NB**: the contradiction with the previous rule lies in the necessity of introducing ‘negative’ flags (the ‘no limit’ flag), which we had to rename to `abolish_spending_limit`. Though it's a decent name for a negative flag, its semantics is still unobvious, and developers will have to read the docs. That's the way.
|
||||
|
||||
##### Avoid partial updates
|
||||
|
||||
@ -366,13 +366,13 @@ Excessive network traffic usually occurs if:
|
||||
* no limits on field values are set;
|
||||
* binary data is transmitted (graphics, audio, video, etc.)
|
||||
|
||||
Transferring only a subset of fields solves none of these cases, in the best case just masks it. More viable approach comprise:
|
||||
Transferring only a subset of fields solves none of these problems, in the best case just masks them. More viable approach comprise:
|
||||
|
||||
* making separate endpoints for ‘heavy’ data;
|
||||
* introducing pagination and field value length limits;
|
||||
* stopping saving bytes in all other cases.
|
||||
|
||||
**In second**, shortening response sizes will backfire exactly with implementing collaborative editing: one client won't see the changes the other client have made. Generally speaking, in 9 cases out of 10 it is better to return a full entity state from any modifying operation, sharing the format with read access endpoint. Actually, you should always do this unless response size affects performance.
|
||||
**In second**, shortening response sizes will backfire exactly with sploiling collaborative editing: one client won't see the changes the other client have made. Generally speaking, in 9 cases out of 10 it is better to return a full entity state from any modifying operation, sharing the format with read access endpoint. Actually, you should always do this unless response size affects performance.
|
||||
|
||||
**In third**, this approach might work if you need to rewrite a field's value. But how to unset the field, return its value to the default state? For example, how to *remove* `client_phone_number_ext`?
|
||||
|
||||
@ -406,7 +406,7 @@ PUT /v1/orders/123/client-details
|
||||
{ "phone_number" }
|
||||
```
|
||||
|
||||
Omitting `client_phone_number_ext` in `PUT client-details` request would be sufficient to remove it. This approach also helps to separate constant and calculated fields (`order_id` and `updated_at`) from editable ones, thus getting rid of ambiguous situations (what happens id a client tries to rewrite the `updated_at` field?). You may also return the entire `order` entity from `PUT` endpoints (however, there should be some naming convention for that).
|
||||
Omitting `client_phone_number_ext` in `PUT client-details` request would be sufficient to remove it. This approach also helps to separate constant and calculated fields (`order_id` and `updated_at`) from editable ones, thus getting rid of ambiguous situations (what happens if a client tries to rewrite the `updated_at` field?). You may also return the entire `order` entity from `PUT` endpoints (however, there should be some naming convention for that).
|
||||
|
||||
**Option 2**: design a format for atomic changes.
|
||||
|
||||
@ -556,7 +556,7 @@ GET /v1/recipes
|
||||
}]
|
||||
}
|
||||
```
|
||||
— there is no way how client might learn that failed operation was actually partially applied. Even if there is an indication of this fact in the answer, the client still cannot tell, whether lungo volume changed because of the request, or some other client changed it.
|
||||
— there is no way how client might learn that failed operation was actually partially applied. Even if there is an indication of this fact in the response, the client still cannot tell, whether lungo volume changed because of the request, or some other client changed it.
|
||||
|
||||
If you can't guarantee the atomicity of an operation, you should elaborate in details how to deal with it. There must be a separate status for each individual change.
|
||||
|
||||
@ -879,7 +879,7 @@ It is also a good practice to return all detectable errors at once to spare deve
|
||||
|
||||
##### Maintain a proper error sequence
|
||||
|
||||
**In first**, always return unresolvable errors before re resolvable once:
|
||||
**In first**, always return unresolvable errors before the resolvable ones:
|
||||
|
||||
```
|
||||
POST /v1/orders
|
||||
@ -934,7 +934,7 @@ POST /v1/orders
|
||||
```
|
||||
— what was the point of showing the price changed dialog, if the user still can't make an order, even if the price is right? When one of the concurrent orders finishes, and the user is able to commit another one, prices, items availability, and other order parameters will likely need another correction.
|
||||
|
||||
**In third**, draw a chart: which error resolution might lead to the emergence of another one. Otherwise you might eventually return the same error several time, or worse, make a cycle of errors.
|
||||
**In third**, draw a chart: which error resolution might lead to the emergence of another one. Otherwise you might eventually return the same error several times, or worse, make a cycle of errors.
|
||||
|
||||
```
|
||||
// Create an order
|
||||
@ -1009,7 +1009,7 @@ POST /search
|
||||
}
|
||||
```
|
||||
|
||||
This rule might be reduced to: if an array is the result of the operation, than emptiness of that array is not a mistake, but a correct response. (Of course, if empty array is acceptable semantically; empty coordinates array is a mistake, of course.)
|
||||
This rule might be reduced to: if an array is the result of the operation, than emptiness of that array is not a mistake, but a correct response. (Of course, if empty array is acceptable semantically; empty coordinates array is a mistake for sure.)
|
||||
|
||||
##### Localization and internationalization
|
||||
|
||||
|
@ -301,14 +301,14 @@ POST /users
|
||||
// с указанием лимита трат в месяц
|
||||
{
|
||||
…
|
||||
"spendings_monthly_limit_usd": "100"
|
||||
"spending_monthly_limit_usd": "100"
|
||||
}
|
||||
// Для отмены лимита требуется
|
||||
// указать значение null
|
||||
POST /users
|
||||
{
|
||||
…
|
||||
"spendings_monthly_limit_usd": null
|
||||
"spending_monthly_limit_usd": null
|
||||
}
|
||||
```
|
||||
|
||||
@ -320,16 +320,16 @@ POST /users
|
||||
// лимит трат в месяц
|
||||
// false — лимит не снят
|
||||
// (значение по умолчанию)
|
||||
"abolish_spendings_limit": false,
|
||||
"abolish_spending_limit": false,
|
||||
// Необязательное поле, имеет смысл
|
||||
// только если предыдущий флаг
|
||||
// имеет значение false
|
||||
"spendings_monthly_limit_usd": "100",
|
||||
"spending_monthly_limit_usd": "100",
|
||||
…
|
||||
}
|
||||
```
|
||||
|
||||
**NB**: противоречие с предыдущим советом состоит в том, что мы специально ввели отрицающий флаг («нет лимита»), который по правилу двойных отрицаний пришлось переименовать в `abolish_spendings_limit`. Хотя это и хорошее название для отрицательного флага, семантика его довольно неочевидна, разработчикам придётся как минимум покопаться в документации. Таков путь.
|
||||
**NB**: противоречие с предыдущим советом состоит в том, что мы специально ввели отрицающий флаг («нет лимита»), который по правилу двойных отрицаний пришлось переименовать в `abolish_spending_limit`. Хотя это и хорошее название для отрицательного флага, семантика его довольно неочевидна, разработчикам придётся как минимум покопаться в документации. Таков путь.
|
||||
|
||||
##### Избегайте частичных обновлений
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user