mirror of
https://github.com/twirl/The-API-Book.git
synced 2025-04-17 11:06:25 +02:00
additions & clarifications
This commit is contained in:
parent
8247917f32
commit
dec82d4a7d
@ -60,7 +60,7 @@ A full example of an API implementing the naïve approach would look like this:
|
||||
|
||||
```
|
||||
// Partially rewrites the order:
|
||||
// * resets delivery address
|
||||
// * resets the delivery address
|
||||
// to the default values
|
||||
// * leaves the first beverage
|
||||
// intact
|
||||
@ -75,7 +75,7 @@ PATCH /v1/orders/{id}
|
||||
// do nothing to the entity
|
||||
{},
|
||||
// “Special” value #3:
|
||||
// delete an entity
|
||||
// delete the entity
|
||||
false
|
||||
]
|
||||
}
|
||||
@ -94,6 +94,45 @@ However, upon closer examination all these conclusions seem less viable:
|
||||
* Finally, the naïve approach of organizing collaborative editing by allowing conflicting operations to be carried out if they don't touch the same fields works only if the changes are transitive. In our case, they are not: the result of simultaneously removing the first element in the list and editing the second one depends on the execution order.
|
||||
* Often, developers try to reduce the outgoing traffic volume as well by returning an empty server response for modifying operations. Therefore, two clients editing the same entity do not see the changes made by each other until they explicitly refresh the state, which further increases the chance of yielding highly unexpected results.
|
||||
|
||||
The solution could be enhanced by introducing explicit control sequences instead of relying on “magical” values and adding meta settings for the operation (such as a field name filter as it's [implemented in gRPC](https://protobuf.dev/reference/protobuf/google.protobuf/#field-masks-updates)). Here's an example:
|
||||
|
||||
```
|
||||
// Partially rewrites the order:
|
||||
// * resets the delivery address
|
||||
// to the default values
|
||||
// * leaves the first beverage
|
||||
// intact
|
||||
// * removes the second beverage
|
||||
PATCH /v1/orders/{id}?⮠
|
||||
// A meta filter: which fields
|
||||
// are allowed to be modified
|
||||
field_mask=delivery_address,items
|
||||
{
|
||||
// “Special” value #1:
|
||||
// reset the field
|
||||
"delivery_address": {
|
||||
// The `__` prefix is needed
|
||||
// to avoid collisions
|
||||
// with real field names
|
||||
"__operation": "reset"
|
||||
},
|
||||
"items": [
|
||||
// “Special” value #2:
|
||||
// do nothing to the entity
|
||||
{ "__operation": "skip" },
|
||||
// “Special” value #3:
|
||||
// delete the entity
|
||||
{ "__operation": "delete" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
While this approach may appear more robust, it doesn't fundamentally address the problems:
|
||||
* “Magical” values are replaced with “magical” prefixes
|
||||
* The fragmentation of algorithms and the non-transitivity of operations persist.
|
||||
|
||||
Given that the format becomes more complex and less intuitively understandable, we consider this enhancement dubious.
|
||||
|
||||
A **more consistent solution** is to split an endpoint into several idempotent sub-endpoints, each having its own independent identifier and/or address (which is usually enough to ensure the transitivity of independent operations). This approach aligns well with the decomposition principle we discussed in the “[Isolating Responsibility Areas](#api-design-isolating-responsibility)” chapter.
|
||||
|
||||
```
|
||||
|
@ -91,6 +91,45 @@ PATCH /v1/orders/{id}
|
||||
* наконец, подобная наивная концепция организации совместного доступа работает ровно до того момента, пока изменения транзитивны, т.е. результат не зависит от порядка выполнения операций (в нашим примере это уже не так — операции удаления первого элемента и редактирования первого элемента нетранзитивны);
|
||||
* кроме того, часто в рамках той же концепции экономят и на исходящем трафике, возвращая пустой ответ сервера для модифицирующих операций; таким образом, два клиента, редактирующих одну и ту же сущность, не видят изменения друг друга, что ещё больше повышает вероятность получить совершенно неожиданные результаты.
|
||||
|
||||
Это решение можно улучшить путём ввода явных управляющих конструкций вместо «магических значений» и введением мета-опций операции (скажем, фильтра по именам полей, как это [принято в gRPC](https://protobuf.dev/reference/protobuf/google.protobuf/#field-masks-updates)), например, так:
|
||||
|
||||
```
|
||||
// Частично перезаписывает заказ:
|
||||
// * сбрасывает адрес доставки
|
||||
// в значение по умолчанию
|
||||
// * не изменяет первый напиток
|
||||
// * удаляет второй напиток
|
||||
PATCH /v1/orders/{id}?⮠
|
||||
// мета-фильтр: какие поля
|
||||
// переопределяются
|
||||
field_mask=delivery_address,items
|
||||
{
|
||||
// Специальное значение №1:
|
||||
// обнулить поле
|
||||
"delivery_address": {
|
||||
// Префикс `__` нужен, чтобы
|
||||
// избежать коллизий
|
||||
// с реальными именами полей
|
||||
"__operation": "reset"
|
||||
},
|
||||
"items": [
|
||||
// Специальное значение №2:
|
||||
// не выполнять никаких
|
||||
// операций с объектом
|
||||
{ "__operation": "skip" },
|
||||
// Специальное значение №3:
|
||||
// удалить объект
|
||||
{ "__operation": "delete" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Такой подход выглядит более надёжным, но в реальности мало что меняет в постановке проблемы:
|
||||
* «магические значения» заменены «магическими» префиксами;
|
||||
* фрагментация алгоритмов и нетранзитивность операций сохраняется.
|
||||
|
||||
При этом формат перестаёт быть простым и интуитивно понятным, что с нашей точки зрения делает такое улучшение спорным.
|
||||
|
||||
**Более консистентное решение**: разделить эндпойнт на несколько идемпотентных суб-эндпойнтов, имеющих независимые идентификаторы и/или адреса (чего обычно достаточно для обеспечения транзитивности независимых операций). Этот подход также хорошо согласуется с принципом декомпозиции, который мы рассматривали в предыдущем главе [«Разграничение областей ответственности»](#api-design-isolating-responsibility).
|
||||
|
||||
```
|
||||
|
Loading…
x
Reference in New Issue
Block a user