1
0
mirror of https://github.com/twirl/The-API-Book.git synced 2025-10-30 23:37:47 +02:00

fresh build

This commit is contained in:
Sergey Konstantinov
2025-10-11 22:00:55 +03:00
parent 95215afb09
commit a4687657a0
4 changed files with 17 additions and 17 deletions

Binary file not shown.

View File

@@ -2991,7 +2991,7 @@ X-Idempotency-Token: <span class="hljs-substitution">&#x3C;token></span>
<li>The capability of the client to calculate these diffs doesn't relieve the server developers of the duty to do the same as client developers might make mistakes or overlook certain aspects.</li>
</ul>
</li>
<li>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.
<li>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 not order-dependent. In our case, they are: the result of simultaneously removing the first element in the list and editing the second one depends on the execution order.
<ul>
<li>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.</li>
</ul>
@@ -3027,10 +3027,10 @@ X-Idempotency-Token: <span class="hljs-substitution">&#x3C;token></span>
<p>While this approach may appear more robust, it doesn't fundamentally address the problems:</p>
<ul>
<li>“Magical” values are replaced with “magical” prefixes</li>
<li>The fragmentation of algorithms and the non-transitivity of operations persist.</li>
<li>The fragmentation of algorithms and the result’s dependence on the order of operations persist.</li>
</ul>
<p>Given that the format becomes more complex and less intuitively understandable, we consider this enhancement dubious.</p>
<p>A <strong>more consistent solution</strong> 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 “<a href="#api-design-isolating-responsibility">Isolating Responsibility Areas</a>” chapter.</p>
<p>A <strong>more consistent solution</strong> 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 order-independence of different types of operations). This approach aligns well with the decomposition principle we discussed in the “<a href="#api-design-isolating-responsibility">Isolating Responsibility Areas</a>” chapter.</p>
<pre><code data-language="json"><span class="hljs-comment">// Creates an order</span>
<span class="hljs-comment">// comprising two beverages</span>
<span class="hljs-keyword">POST</span> /v1/orders/
@@ -3080,7 +3080,7 @@ X-Idempotency-Token: <span class="hljs-substitution">&#x3C;token></span>
<pre><code data-language="json"><span class="hljs-comment">// Deletes one of the beverages</span>
<span class="hljs-keyword">DELETE</span> /v1/orders/<span class="hljs-substitution">{id}</span>/items/<span class="hljs-substitution">{item_id}</span>
</code></pre>
<p>Now to reset the <code>volume</code> field it is enough <em>not</em> to pass it in the <code>PUT items/{item_id}</code>. Also note that the operations of removing one beverage and editing another one became transitive.</p>
<p>Now to reset the <code>volume</code> field it is enough <em>not</em> to pass it in the <code>PUT items/{item_id}</code>. Also note that the operations of removing one beverage and editing another one became order-independent.</p>
<p>This approach also allows for separating read-only and calculated fields (such as <code>created_at</code> and <code>status</code>) from the editable ones without creating ambivalent situations (such as what should happen if the client tries to modify the <code>created_at</code> field).</p>
<p>Applying this pattern is typically sufficient for most APIs that manipulate composite entities. However, it comes with a price as it sets high standards for designing the decomposed interfaces (otherwise a once neat API will crumble with further API expansion) and the necessity to make many requests to replace a significant subset of the entity's fields (which implies exposing the functionality of applying bulk changes, the undesirability of which we discussed in the previous chapter).</p>
<p><strong>NB</strong>: While decomposing endpoints, it's tempting to split editable and read-only data. Then the latter might be cached for a long time and there will be no need for sophisticated list iteration techniques. The plan looks great on paper; however, with API expansion, immutable data often ceases to be immutable which is only solvable by creating new versions of the interfaces. We recommend explicitly pronouncing some data non-modifiable in one of the following two cases: either (1) it really cannot become editable without breaking backward compatibility or (2) the reference to the resource (such as, let's say, a link to an image) is fetched via the API itself and you can make these links persistent (i.e., if the image is updated, a new link is generated instead of overwriting the content the old one points to).</p>
@@ -3112,7 +3112,7 @@ X-Idempotency-Token: <span class="hljs-substitution">&#x3C;token></span>
}
</code></pre>
<p>This approach is much more complex to implement, but it is the only viable technique for realizing collaborative editing as it explicitly reflects the exact actions the client applied to an entity. Having the changes in this format also allows for organizing offline editing with accumulating changes on the client side for the server to resolve the conflict later based on the revision history.</p>
<p><strong>NB</strong>: One approach to this task is developing a set of operations in which all actions are transitive (i.e., the final state of the entity does not change regardless of the order in which the changes were applied). One example of such a nomenclature is a conflict-free replicated data type (<em>CRDT</em>).<sup class="in-place-reference"><a id="api-patterns-partial-updates-ref-2-back" href="#api-patterns-partial-updates-ref-2">2</a></sup> However, we consider this approach viable only in some subject areas, as in real life, non-transitive changes are always possible. If one user entered new text in the document and another user removed the document completely, there is no way to automatically resolve this conflict that would satisfy both users. The only correct way of resolving this conflict is explicitly asking users which option for mitigating the issue they prefer.</p><h4>References</h4><ul class="references"><li><p><a class="back-anchor" href="#api-patterns-partial-updates-ref-1-back" id="api-patterns-partial-updates-ref-1"><sup>1</sup> </a><span>Protocol Buffers. Field Masks in Update Operations<br /><a target="_blank" class="external" href="https://protobuf.dev/reference/protobuf/google.protobuf/#field-masks-updates">protobuf.dev/reference/protobuf/google.protobuf/#field-masks-updates</a></span></p></li><li><p><a class="back-anchor" href="#api-patterns-partial-updates-ref-2-back" id="api-patterns-partial-updates-ref-2"><sup>2</sup> </a><span>See “Conflict-Free Replicated Data Type” · <a target="_blank" class="reference-inline-link" href="https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type">en.wikipedia.org/wiki/Conflict-free_replicated_data_type</a> or refer to <a class="ref-to-bibliography" href="#bibliography-shapiro-et-al-crdt">Shapiro, M., Preguiça, N., Baquero, C., Zawirski, M. (2011)</a>, Conflict-Free Replicated Data Types</span></p></li></ul><div class="page-break"></div><h3><a class="anchor" id="api-patterns-degrading" href="#api-patterns-degrading">Chapter 24. Degradation and Predictability</a></h3>
<p><strong>NB</strong>: One approach to this task is developing a set of operations in which the state of the system remains consistent regardless of the order in which the operations are applied. One example of such a nomenclature is Conflict-Free Replicated Data Type (<em>CRDT</em>).<sup class="in-place-reference"><a id="api-patterns-partial-updates-ref-2-back" href="#api-patterns-partial-updates-ref-2">2</a></sup> However, we consider this approach viable only in some subject areas, as in real life, the order of operation <em>is</em> typically important. If one user entered new text in the document and another user removed the document completely, there is no way to automatically resolve this conflict that would satisfy both users. The only correct way of resolving this conflict is explicitly asking users which option for mitigating the issue they prefer.</p><h4>References</h4><ul class="references"><li><p><a class="back-anchor" href="#api-patterns-partial-updates-ref-1-back" id="api-patterns-partial-updates-ref-1"><sup>1</sup> </a><span>Protocol Buffers. Field Masks in Update Operations<br /><a target="_blank" class="external" href="https://protobuf.dev/reference/protobuf/google.protobuf/#field-masks-updates">protobuf.dev/reference/protobuf/google.protobuf/#field-masks-updates</a></span></p></li><li><p><a class="back-anchor" href="#api-patterns-partial-updates-ref-2-back" id="api-patterns-partial-updates-ref-2"><sup>2</sup> </a><span>See “Conflict-Free Replicated Data Type” · <a target="_blank" class="reference-inline-link" href="https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type">en.wikipedia.org/wiki/Conflict-free_replicated_data_type</a> or refer to <a class="ref-to-bibliography" href="#bibliography-shapiro-et-al-crdt">Shapiro, M., Preguiça, N., Baquero, C., Zawirski, M. (2011)</a>, Conflict-Free Replicated Data Types</span></p></li></ul><div class="page-break"></div><h3><a class="anchor" id="api-patterns-degrading" href="#api-patterns-degrading">Chapter 24. Degradation and Predictability</a></h3>
<p>In the previous chapters, we repeatedly discussed that the background level of errors is not just unavoidable, but in many cases, APIs are deliberately designed to tolerate errors to make the system more scalable and predictable.</p>
<p>But let's ask ourselves a question: what does a “more predictable system” mean? For an API vendor, the answer is simple: the distribution and number of errors are both indicators of technical problems (if the numbers are growing unexpectedly) and KPIs for technical refactoring (if the numbers are decreasing after the release).</p>
<p>However, for partner developers, the concept of “API predictability” means something completely different: how solidly they can cover the API use cases (both happy and unhappy paths) in their code. In other words, how well one can understand based on the documentation and the nomenclature of API methods what errors might arise during the API work cycle and how to handle them.</p>
@@ -4667,7 +4667,7 @@ Location: /v1/orders/<span class="hljs-substitution">{id}</span>
<p>The problem of partial updates was discussed in detail in the <a href="#api-patterns-partial-updates">corresponding chapter</a> of “The API Patterns” section. To quickly recap:</p>
<ul>
<li>The concept of fully overwriting resources with <code>PUT</code> is viable but soon faces problems when working with calculated or immutable fields and organizing collaborative editing. It is also suboptimal in terms of traffic consumption.</li>
<li>Partially updating a resource using the <code>PATCH</code> method is potentially non-idempotent (and likely non-transitive), and the aforementioned concerns regarding automatic retries are applicable to it as well.</li>
<li>Partially updating a resource using the <code>PATCH</code> method is potentially non-idempotent (and likely order-dependent), and the aforementioned concerns regarding automatic retries are applicable to it as well.</li>
</ul>
<p>If we need to update a complex entity, especially if collaborative editing is needed, we will soon find ourselves leaning towards one of the following two approaches:</p>
<ul>
@@ -6147,7 +6147,7 @@ button.<span class="hljs-property">view</span>.<span class="hljs-property">compu
</ul>
<p>Additionally, we might talk about forming a community, i.e., a network of developers (or customers, or business owners) who are loyal to the product. The benefits of having such a community might be substantial: lowering the technical support costs, getting a convenient channel for publishing announcements regarding new services and new releases, and obtaining beta users for upcoming products.</p>
<h5><a class="anchor" id="api-product-business-models-para-7" href="#api-product-business-models-para-7">7. API = a Feedback and UGC Tool</a></h5>
<p>If a company possesses some big data, it might be useful to provide a public API for users to make corrections in the data or otherwise get involved in working with it. For example, cartographical API providers usually allow the audience to post feedback or correct mistakes right on partners' websites and applications. [In the case of our coffee API, we might be collecting feedback to improve the service, both passively through building coffee shop ratings or actively through contacting business owners to convey users' requests or finding new coffee shops that are still not integrated with the platform.]</p>
<p>If a company possesses some big data, fully or partially authored by external contributors (so-called “user-generated content,” or “UGC”), it might be useful to provide a public API for users to make corrections in the data or otherwise get involved in working with it. For example, cartographical API providers usually allow the audience to post feedback or correct mistakes right on partners' websites and applications. [In the case of our coffee API, we might be collecting feedback to improve the service, both passively through building coffee shop ratings or actively through contacting business owners to convey users' requests or finding new coffee shops that are still not integrated with the platform.]</p>
<h5><a class="anchor" id="api-product-business-models-para-8" href="#api-product-business-models-para-8">8. Terraforming</a></h5>
<p>Finally, the most altruistic approach to API product development is providing it free of charge (or as an open-source or open-data project) just to change the landscape. If today nobody is willing to pay for the API, we might invest in popularizing the functionality hoping to find commercial niches later (in any of the aforementioned formats) or to increase the significance and usefulness of the API integrations for end users (and therefore the readiness of the partners to pay for the API). [In the case of our coffee example, imagine a coffee machine maker that starts providing APIs for free aiming to make having an API a “must” for every coffee machine vendor thus allowing for the development of commercial API-based services in the future.]</p>
<h5><a class="anchor" id="api-product-business-models-para-9" href="#api-product-business-models-para-9">9. Gray Zones</a></h5>

Binary file not shown.

View File

@@ -2934,7 +2934,7 @@ X-Idempotency-Token: &#x3C;токен>
<li>существование клиентского алгоритма построения diff-ов не отменяет обязанность сервера уметь делать то же самое — поскольку клиентские разработчики могли ошибиться или просто полениться правильно вычислить изменившиеся поля;</li>
</ul>
</li>
<li>наконец, подобная наивная концепция организации совместного доступа работает ровно до того момента, пока изменения транзитивны, т.е. результат не зависит от порядка выполнения операций (в нашим примере это уже не так — операции удаления первого элемента и редактирования первого элемента нетранзитивны);
<li>наконец, подобная наивная концепция организации совместного доступа работает ровно до того момента, пока результат не зависит от порядка выполнения операций (в нашем примере это уже не так — операции удаления первого элемента и редактирования первого элемента нельзя просто так менять местами);
<ul>
<li>кроме того, часто в рамках той же концепции экономят и на исходящем трафике, возвращая пустой ответ сервера для модифицирующих операций; таким образом, два клиента, редактирующих одну и ту же сущность, не видят изменения друг друга, что ещё больше повышает вероятность получить совершенно неожиданные результаты.</li>
</ul>
@@ -2973,10 +2973,10 @@ X-Idempotency-Token: &#x3C;токен>
<p>Такой подход выглядит более надёжным, но в реальности мало что меняет в постановке проблемы:</p>
<ul>
<li>«магические значения» заменены «магическими» префиксами;</li>
<li>фрагментация алгоритмов и нетранзитивность операций сохраняется.</li>
<li>фрагментация алгоритмов и зависимость результата от порядка операций сохраняется.</li>
</ul>
<p>При этом формат перестаёт быть простым и интуитивно понятным, что с нашей точки зрения делает такое улучшение спорным.</p>
<p><strong>Более консистентное решение</strong>: разделить эндпойнт на несколько идемпотентных суб-эндпойнтов, имеющих независимые идентификаторы и/или адреса (чего обычно достаточно для обеспечения транзитивности независимых операций). Этот подход также хорошо согласуется с принципом декомпозиции, который мы рассматривали в предыдущем главе <a href="#api-design-isolating-responsibility">«Разграничение областей ответственности»</a>.</p>
<p><strong>Более консистентное решение</strong>: разделить эндпойнт на несколько идемпотентных суб-эндпойнтов, имеющих независимые идентификаторы и/или адреса (чего обычно достаточно для обеспечения независимости результата от порядка выполнения операций). Этот подход также хорошо согласуется с принципом декомпозиции, который мы рассматривали в предыдущем главе <a href="#api-design-isolating-responsibility">«Разграничение областей ответственности»</a>.</p>
<pre><code data-language="json"><span class="hljs-comment">// Создаёт заказ из двух напитков</span>
<span class="hljs-keyword">POST</span> /v1/orders/
{
@@ -3024,7 +3024,7 @@ X-Idempotency-Token: &#x3C;токен>
<pre><code data-language="json"><span class="hljs-comment">// Удаляет один из напитков в заказе</span>
<span class="hljs-keyword">DELETE</span> /v1/orders/<span class="hljs-substitution">{id}</span>/items/<span class="hljs-substitution">{item_id}</span>
</code></pre>
<p>Теперь для удаления <code>volume</code> достаточно <em>не</em> передавать его в <code>PUT items/{item_id}</code>. Кроме того, обратите внимание, что операции удаления одного напитка и модификации другого теперь стали транзитивными.</p>
<p>Теперь для удаления <code>volume</code> достаточно <em>не</em> передавать его в <code>PUT items/{item_id}</code>. Кроме того, обратите внимание, что результат выполнения операций удаления одного напитка и модификации другого теперь не зависит от порядка применения.</p>
<p>Этот подход также позволяет отделить неизменяемые и вычисляемые поля (<code>created_at</code> и <code>status</code>) от изменяемых, не создавая двусмысленных ситуаций (что произойдёт, если клиент попытается изменить <code>created_at</code>?).</p>
<p>Применения этого паттерна, как правило, достаточно для большинства API, манипулирующих сложносоставленными сущностями, однако и недостатки у него тоже есть: высокие требования к качеству проектирования декомпозиции (иначе велик шанс, что стройное API развалится при дальнейшем расширении функциональности) и необходимость совершать множество запросов для изменения всей сущности целиком (из чего вытекает необходимость создания функциональности для внесения массовых изменений, нежелательность которой мы обсуждали в предыдущей главе).</p>
<p><strong>NB</strong>: при декомпозиции эндпойнтов велик соблазн провести границу так, чтобы разделить изменяемые и неизменяемые данные. Тогда последние можно объявить кэшируемыми условно вечно и вообще не думать над проблемами пагинации и формата обновления. На бумаге план выглядит отлично, однако с ростом API неизменяемые данные частенько перестают быть таковыми, и тогда потребуется выпускать новые интерфейсы работы с данными. Мы скорее рекомендуем объявлять данные иммутабельными в одном из двух случаев: либо (1) они действительно не могут стать изменяемыми без слома обратной совместимости, либо (2) ссылка на ресурс (например, на изображение) поступает через API же, и вы обладаете возможностью сделать эти ссылки персистентными (т.е. при необходимости обновить изображение будете генерировать новую ссылку, а не перезаписывать контент по старой ссылке).</p>
@@ -3056,7 +3056,7 @@ X-Idempotency-Token: &#x3C;токен>
}
</code></pre>
<p>Этот подход существенно сложнее в имплементации, но является единственным возможным вариантом реализации совместного редактирования, поскольку он явно отражает, что в действительности делал пользовать с представлением объекта. Имея данные в таком формате возможно организовать и оффлайн-редактирование, когда пользовательские изменения накапливаются и сервер впоследствии автоматически разрешает возможные конфликты, основываясь на истории ревизий.</p>
<p><strong>NB</strong>: один из подходов к этой задаче — разработка такой номенклатуры операций над данными (например, conflict-free replicated data type (<em>CRDT</em>)<sup class="in-place-reference"><a id="api-patterns-partial-updates-ref-2-back" href="#api-patterns-partial-updates-ref-2">2</a></sup>), в которой любые действия транзитивны (т.е. конечное состояние системы не зависит от того, в каком порядке они были применены). Мы, однако, склонны считать такой подход применимым только к весьма ограниченным предметным областям — поскольку в реальной жизни нетранзитивные действия находятся почти всегда. Если один пользователь ввёл в документ новый текст, а другой пользователь удалил документ — никакого разумного (т.е. удовлетворительного с точки зрения обоих акторов) способа автоматического разрешения конфликта здесь нет, необходимо явно спросить пользователей, что бы они хотели сделать с возникшим конфликтом.</p><h4>Примечания</h4><ul class="references"><li><p><a class="back-anchor" href="#api-patterns-partial-updates-ref-1-back" id="api-patterns-partial-updates-ref-1"><sup>1</sup> </a><span>Protocol Buffers. Field Masks in Update Operations<br /><a target="_blank" class="external" href="https://protobuf.dev/reference/protobuf/google.protobuf/#field-masks-updates">protobuf.dev/reference/protobuf/google.protobuf/#field-masks-updates</a></span></p></li><li><p><a class="back-anchor" href="#api-patterns-partial-updates-ref-2-back" id="api-patterns-partial-updates-ref-2"><sup>2</sup> </a><span>Conflict-Free Replicated Data Type<br /><a target="_blank" class="external" href="https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type">en.wikipedia.org/wiki/Conflict-free_replicated_data_type</a></span></p></li></ul><div class="page-break"></div><h3><a class="anchor" id="api-patterns-degrading" href="#api-patterns-degrading">Глава 24. Деградация и предсказуемость</a></h3>
<p><strong>NB</strong>: один из подходов к этой задаче — разработка такой номенклатуры операций над данными (например, conflict-free replicated data type (<em>CRDT</em>)<sup class="in-place-reference"><a id="api-patterns-partial-updates-ref-2-back" href="#api-patterns-partial-updates-ref-2">2</a></sup>), в которой операции выполнимы в любом порядке (изменение порядка выполнения операций никогда не приводит к неразрешимому конфликту). Мы, однако, склонны считать такой подход применимым только к весьма ограниченным предметным областям — поскольку в реальной жизни порядок исполнения, как правило, важен. Если один пользователь ввёл в документ новый текст, а другой пользователь удалил документ — никакого разумного (т.е. удовлетворительного с точки зрения обоих акторов) способа автоматического разрешения конфликта здесь нет, необходимо явно спросить пользователей, что бы они хотели сделать с возникшим конфликтом.</p><h4>Примечания</h4><ul class="references"><li><p><a class="back-anchor" href="#api-patterns-partial-updates-ref-1-back" id="api-patterns-partial-updates-ref-1"><sup>1</sup> </a><span>Protocol Buffers. Field Masks in Update Operations<br /><a target="_blank" class="external" href="https://protobuf.dev/reference/protobuf/google.protobuf/#field-masks-updates">protobuf.dev/reference/protobuf/google.protobuf/#field-masks-updates</a></span></p></li><li><p><a class="back-anchor" href="#api-patterns-partial-updates-ref-2-back" id="api-patterns-partial-updates-ref-2"><sup>2</sup> </a><span>Conflict-Free Replicated Data Type<br /><a target="_blank" class="external" href="https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type">en.wikipedia.org/wiki/Conflict-free_replicated_data_type</a></span></p></li></ul><div class="page-break"></div><h3><a class="anchor" id="api-patterns-degrading" href="#api-patterns-degrading">Глава 24. Деградация и предсказуемость</a></h3>
<p>В предыдущих главах мы много говорили о том, что фон ошибок — не только неизбежное зло в любой достаточно большой системе, но и, зачастую, осознанное решение, которое позволяет сделать систему более масштабируемой и предсказуемой.</p>
<p>Зададим себе, однако, вопрос: а что значит «более предсказуемая» система? Для нас как для вендора API это достаточно просто: процент ошибок (в разбивке по типам) достаточно стабилен, и им можно пользоваться как индикатором возникающих технических проблем (если он растёт) и как KPI для технических улучшений и рефакторингов (если он падает).</p>
<p>Но вот для разработчиков-партнёров понятие «предсказуемость поведения API» имеет совершенно другой смысл: насколько хорошо и полно они в своём коде могут покрыть различные сценарии использования API и потенциальные проблемы — или, иными словами, насколько явно из документации и номенклатуры методов и ошибок API становится ясно, какие типовые ошибки могут возникнуть и как с ними работать.</p>
@@ -3325,7 +3325,7 @@ X-Idempotency-Token: &#x3C;токен>
<p>Эти пункты мы выписали с одной целью: нам нужно понять, каким конкретно образом мы будем переводить неявные договорённости в явные, если нам это потребуется. Например, если разные кофемашины предоставляют разный объём функциональности — допустим, в каких-то кофейнях объём кофе фиксирован — что должно измениться в нашем API?</p>
<p>Универсальный паттерн внесения подобных изменений таков: мы должны рассмотреть существующий интерфейс как частный случай некоторого более общего, в котором значения некоторых параметров приняты известными по умолчанию, а потому опущены. Таким образом, внесение изменений всегда происходит в три шага.</p>
<ol>
<li>Явная фиксация программного контракта <em>в том объёме, в котором она действует на текущий момент</em>.</li>
<li>Явная фиксация программного контракта <em>в том объёме, в котором он действует на текущий момент</em>.</li>
<li>Расширение функциональности: добавление нового метода, который позволяет обойти ограничение, зафиксированное в п. 1.</li>
<li>Объявление существующих вызовов (из п. 1) "хелперами" к новому формату (из п. 2), в которых значение новых опций считается равным значению по умолчанию.</li>
</ol>
@@ -4635,7 +4635,7 @@ Location: /v1/orders/<span class="hljs-substitution">{id}</span>
<li><code>GET /v1/orders/{order_id}/attachements/{id}</code></li>
</ul>
<h5><a class="anchor" id="http-api-urls-crud-para-3" href="#http-api-urls-crud-para-3">3. Редактирование</a></h5>
<p>Проблемы частичного обновления ресурсов мы подробно разбирали в <a href="#api-patterns-partial-updates">соответствующей главе</a> раздела «Паттерны дизайна API». Напомним, что полная перезапись ресурса методом <code>PUT</code> возможна, но быстро разбивается о необходимость работать с вычисляемыми и неизменяемыми полями, необходимость совместного редактирования и/или большой объём передаваемых данных. Работа через метод <code>PATCH</code> возможна, но, так как этот метод по умолчанию считается неидемпотентным (и часто нетранзитивным), для него справедливо всё то же соображение об опасности автоматических перезапросов. Достаточно быстро мы придём к одному из двух вариантов:</p>
<p>Проблемы частичного обновления ресурсов мы подробно разбирали в <a href="#api-patterns-partial-updates">соответствующей главе</a> раздела «Паттерны дизайна API». Напомним, что полная перезапись ресурса методом <code>PUT</code> возможна, но быстро разбивается о необходимость работать с вычисляемыми и неизменяемыми полями, необходимость совместного редактирования и/или большой объём передаваемых данных. Работа через метод <code>PATCH</code> возможна, но, так как этот метод по умолчанию считается неидемпотентным (и часто зависящим от порядка выполнения операций), для него справедливо всё то же соображение об опасности автоматических перезапросов. Достаточно быстро мы придём к одному из двух вариантов:</p>
<ul>
<li>либо <code>PUT</code> декомпозирован на множество составных <code>PUT /v1/orders/{id}/address</code>, <code>PUT /v1/orders/{id}/volume</code> и т.д. — по ресурсу для каждой частной операции;</li>
<li>либо существует отдельный ресурс, принимающий список изменений, причём, вероятнее всего, через схему черновик-подтверждение в виде пары методов <code>POST</code> + <code>PUT</code>.</li>
@@ -5075,7 +5075,7 @@ api.<span class="hljs-title function_">subscribe</span>(
<p>Наконец, ещё одна важная функция, которая может быть доверена SDK — это изоляция нижележащего API и смена парадигмы версионирования. Доступ к функциональности API может быть скрыт (т.е. разработчики не будут иметь доступ к низкоуровневой работой с API), тем самым обеспечивая определённую свободу работы с API изнутри SDK, вплоть до бесшовного перехода на новые мажорные версии API. Этот подход, несомненно, предоставляет вендору API намного больше контроля над приложениями клиентов, но требует и намного больше ресурсов на разработку, и, что важнее, грамотного проектирования SDK — такого, чтобы у разработчиков не было необходимости обращаться к API напрямую в обход SDK по причине отсутствия в нём необходимых функций или их плохой реализации, и при этом SDK могу пережить смену мажорной версии низкоуровневого API.</p>
</li>
</ol>
<p>Суммируя написанное выше, хорошо спроектированный SDK служит, помимо поддержания консистентности платформе и предоставления «синтаксического сахара», трём важным целям:</p>
<p>Суммируя написанное выше, хорошо спроектированный SDK служит, помимо поддержания консистентности платформы и предоставления «синтаксического сахара», трём важным целям:</p>
<ul>
<li>снижение количества ошибок в клиентском коде путём имплементации хелперов, покрывающих неочевидные и слабоформализуемые аспекты работы с API;</li>
<li>избавление клиентских разработчиков от необходимости писать код, который им совершенно не нужен;</li>
@@ -6100,7 +6100,7 @@ button.<span class="hljs-property">view</span>.<span class="hljs-property">compu
</ul>
<p>Дополнительно в этом разделе можно говорить о формировании комьюнити, т.е. сообщества пользователей или разработчиков, лояльных к продукту. Выгоды от существования таких комьюнити бывают довольно существенны: это и снижение расходов на техническую поддержку, и удобный канал информирования о новых сервисах и релизах, и возможность получить бета-тестеров разрабатываемых продуктов.</p>
<h5><a class="anchor" id="api-product-business-models-para-7" href="#api-product-business-models-para-7">7. API = инструмент получения обратной связи и UGC</a></h5>
<p>Если компания располагает какими-то большими данными, то оправданной может быть стратегия выпуска публичного API для того, чтобы конечные пользователи вносили исправления в данные или иным образом вовлекались в их разметку. Например, провайдеры картографических API часто разрешают сообщить об ошибке или исправить неточность прямо в стороннем приложении. [А в случае нашего кофейного API мы могли бы собирать обратную связь, как пассивно — строить рейтинги заведений, например, — так и активно — контактировать с владельцами заведений чтобы помочь им исправить недостатки; находить через UGC ещё не подключенные к API кофейни и проактивно работать с ними.]</p>
<p>Если компания располагает какими-то большими данными, которые полностью или частично созданы сообществом (т.н. «User-Generated Content», UGC), то оправданной может быть стратегия выпуска публичного API для того, чтобы конечные пользователи вносили исправления в данные или иным образом вовлекались в их разметку. Например, провайдеры картографических API часто разрешают сообщить об ошибке или исправить неточность прямо в стороннем приложении. [А в случае нашего кофейного API мы могли бы собирать обратную связь, как пассивно — строить рейтинги заведений, например, — так и активно — контактировать с владельцами заведений чтобы помочь им исправить недостатки; находить через UGC ещё не подключенные к API кофейни и проактивно работать с ними.]</p>
<h5><a class="anchor" id="api-product-business-models-para-8" href="#api-product-business-models-para-8">8. Терраформирование</a></h5>
<p>Наконец, наиболее альтруистический подход к разработке API — предоставление его либо полностью бесплатно, либо в формате открытого кода и открытых данных просто с целью изменения ландшафта: если сегодня за API никто не готов платить, то можно вложиться в популяризацию и продвижение функциональности, рассчитывая найти коммерческие ниши (в любом из перечисленных форматов) в будущем или повысить значимость и полезность API в глазах конечных пользователей (а значит и готовность потребителей платить за использование API). [В случае нашего кофейного примера — если компания-производитель умных кофе-машин предоставляет API полностью бесплатно в расчёте на то, что со временем наличие у кофе-машин API станет нормой, и появится возможность разработать новые монетизируемые сервисы за счёт этого.]</p>
<h5><a class="anchor" id="api-product-business-models-para-9" href="#api-product-business-models-para-9">9. Серая зона</a></h5>
@@ -6447,7 +6447,7 @@ button.<span class="hljs-property">view</span>.<span class="hljs-property">compu
<li>поддержка разработчиков по возникающим техническим вопросам.</li>
</ul>
<p>Первый раздел, несомненно, критически важен для любого здорового продукта (включая API), но вновь не содержит в себе какой-то особенной специфики. С точки зрения содержания настоящей книги нас гораздо больше интересует второй раздел.</p>
<p>Поскольку API — программный продукт, разработчики фактически будут задавать вопросы о работе того или иного фрагмента кода, который они пишут. Этот факт сам по себе задирает планку требований к качеству специалистов поддержки до очень высокого уровня, поскольку прочитать код и понять причину проблемы может только разработчик же. Но это полбеды: другая сторона проблемы заключается в том, что, как мы упоминали в предыдущих главах, львиная доля этих запросов будет задана неопытными или непрофессиональными разработчиками, что в случае любого сколько-нибудь популярного API приводит к тому, что 9 из 10 запросов <em>будут вовсе не про работу API</em>. Начинающие разработчики плохо владеют языком, обладают фрагментарными знаниями об устройстве платформы и не умеют правильно формулировать свои проблемы (и как следствие не могут поискать ответ на свой вопрос в Интернете перед тем; хотя, будем честны, в большинстве случаев даже и не пробуют).</p>
<p>Поскольку API — программный продукт, разработчики фактически будут задавать вопросы о работе того или иного фрагмента кода, который они пишут. Этот факт сам по себе задирает планку требований к качеству специалистов поддержки до очень высокого уровня, поскольку прочитать код и понять причину проблемы может только разработчик. Но это полбеды: другая сторона проблемы заключается в том, что, как мы упоминали в предыдущих главах, львиная доля этих запросов будет задана неопытными или непрофессиональными разработчиками, что в случае любого сколько-нибудь популярного API приводит к тому, что 9 из 10 запросов <em>будут вовсе не про работу API</em>. Начинающие разработчики плохо владеют языком, обладают фрагментарными знаниями об устройстве платформы и не умеют правильно формулировать свои проблемы (и как следствие не могут поискать ответ на свой вопрос в Интернете перед тем; хотя, будем честны, в большинстве случаев даже и не пробуют).</p>
<p>Вариантов работы с такими обращениями может быть несколько.</p>
<ol>
<li>