1
0
mirror of https://github.com/twirl/The-API-Book.git synced 2024-11-24 08:02:25 +02:00

various style improvements

This commit is contained in:
Sergey Konstantinov 2023-09-03 23:25:01 +03:00
parent 84a7aa02f8
commit 8ce1001207
18 changed files with 181 additions and 157 deletions

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

View File

@ -1798,15 +1798,15 @@ strpbrk (str1, str2)
<p>— то разработчику потребуется вычислить флаг <code>!no_beans &#x26;&#x26; !no_cup</code>, что эквивалентно <code>!(no_beans || no_cup)</code>, а вот в этом переходе ошибиться очень легко, и избегание двойных отрицаний помогает слабо. Здесь, к сожалению, есть только общий совет «избегайте ситуаций, когда разработчику нужно вычислять такие флаги».</p>
<h5><a href="#chapter-13-paragraph-9" id="chapter-13-paragraph-9" class="anchor">9. Избегайте неявного приведения типов</a></h5>
<p>Этот совет парадоксально противоположен предыдущему. Часто при разработке API возникает ситуация, когда добавляется новое необязательное поле с непустым значением по умолчанию. Например:</p>
<pre><code class="language-typescript"><span class="hljs-keyword">const</span> orderParams = {
<pre><code class="language-typescript"><span class="hljs-keyword">let</span> orderParams = {
<span class="hljs-attr">contactless_delivery</span>: <span class="hljs-literal">false</span>
};
<span class="hljs-keyword">const</span> order = api.<span class="hljs-title function_">createOrder</span>(
<span class="hljs-keyword">let</span> order = api.<span class="hljs-title function_">createOrder</span>(
orderParams
);
</code></pre>
<p>Новая опция <code>contactless_delivery</code> является необязательной, однако её значение по умолчанию — <code>true</code>. Возникает вопрос, каким образом разработчик должен отличить явное <em>нежелание</em> пользоваться опцией (<code>false</code>) от незнания о её существовании (поле не задано). Приходится писать что-то типа такого:</p>
<pre><code class="language-typescript"><span class="hljs-keyword">const</span> value = orderParams.<span class="hljs-property">contactless_delivery</span>;
<pre><code class="language-typescript"><span class="hljs-keyword">let</span> value = orderParams.<span class="hljs-property">contactless_delivery</span>;
<span class="hljs-keyword">if</span> (<span class="hljs-title class_">Type</span>(value) == <span class="hljs-string">'Boolean'</span> &#x26;&#x26; value == <span class="hljs-literal">false</span>) {
}
@ -1814,10 +1814,10 @@ strpbrk (str1, str2)
<p>Эта практика ведёт к усложнению кода, который пишут разработчики, и в этом коде легко допустить ошибку, которая по сути меняет значение поля на противоположное. То же самое произойдёт, если для индикации отсутствия значения поля использовать специальное значение типа <code>null</code> или <code>-1</code>.</p>
<p>Если протоколом не предусмотрена нативная поддержка таких кейсов (т.е. разработчик не может допустить ошибку, спутав отсутствие поля с пустым значением), универсальное правило — все новые необязательные булевы флаги должны иметь значение по умолчанию false.</p>
<p><strong>Хорошо</strong></p>
<pre><code class="language-typescript"><span class="hljs-keyword">const</span> orderParams = {
<pre><code class="language-typescript"><span class="hljs-keyword">let</span> orderParams = {
<span class="hljs-attr">force_contact_delivery</span>: <span class="hljs-literal">true</span>
};
<span class="hljs-keyword">const</span> order = api.<span class="hljs-title function_">createOrder</span>(
<span class="hljs-keyword">let</span> order = api.<span class="hljs-title function_">createOrder</span>(
orderParams
);
</code></pre>
@ -2635,7 +2635,7 @@ X-Idempotency-Token: &#x3C;токен>
</li>
<li>
<p>Клиент запрашивает текущее состояние системы и получает пустой ответ, поскольку таймаут случился раньше, чем запрос на создание заказа дошёл до сервера:</p>
<pre><code class="language-typescript"><span class="hljs-keyword">const</span> pendingOrders = <span class="hljs-keyword">await</span>
<pre><code class="language-typescript"><span class="hljs-keyword">let</span> pendingOrders = <span class="hljs-keyword">await</span>
api.<span class="hljs-title function_">getOngoingOrders</span>(); <span class="hljs-comment">// → []</span>
</code></pre>
</li>
@ -2659,12 +2659,12 @@ X-Idempotency-Token: &#x3C;токен>
lock = <span class="hljs-keyword">await</span> api.<span class="hljs-title function_">acquireLock</span>(<span class="hljs-variable constant_">ORDER_CREATION</span>);
<span class="hljs-comment">// Получаем текущий список</span>
<span class="hljs-comment">// заказов, известных системе</span>
<span class="hljs-keyword">const</span> pendingOrders = <span class="hljs-keyword">await</span>
<span class="hljs-keyword">let</span> pendingOrders = <span class="hljs-keyword">await</span>
api.<span class="hljs-title function_">getPendingOrders</span>();
<span class="hljs-comment">// Если нашего заказа ещё нет,</span>
<span class="hljs-comment">// создаём его</span>
<span class="hljs-keyword">if</span> (pendingOrders.<span class="hljs-property">length</span> == <span class="hljs-number">0</span>) {
<span class="hljs-keyword">const</span> order = <span class="hljs-keyword">await</span> api.<span class="hljs-title function_">createOrder</span>(…)
<span class="hljs-keyword">let</span> order = <span class="hljs-keyword">await</span> api.<span class="hljs-title function_">createOrder</span>(…)
}
} <span class="hljs-keyword">catch</span> (e) {
<span class="hljs-comment">// Обработка ошибок</span>
@ -2683,17 +2683,17 @@ X-Idempotency-Token: &#x3C;токен>
<h4>Оптимистичное управление параллелизмом</h4>
<p>Более щадящий с точки зрения сложности имплементации вариант — это реализовать оптимистичное управление параллелизмом<a href="#ref-chapter-17-no-30" id="ref-chapter-17-no-30-back" class="ref"><sup>2</sup></a> и потребовать от клиента передавать признак того, что он располагает актуальным состоянием разделяемого ресурса.</p>
<pre><code class="language-typescript"><span class="hljs-comment">// Получаем состояние</span>
<span class="hljs-keyword">const</span> orderState =
<span class="hljs-keyword">let</span> orderState =
<span class="hljs-keyword">await</span> api.<span class="hljs-title function_">getOrderState</span>();
<span class="hljs-comment">// Частью состояния является</span>
<span class="hljs-comment">// версия ресурса</span>
<span class="hljs-keyword">const</span> version =
<span class="hljs-keyword">let</span> version =
orderState.<span class="hljs-property">latestVersion</span>;
<span class="hljs-comment">// Заказ можно создать,</span>
<span class="hljs-comment">// только если версия состояния</span>
<span class="hljs-comment">// не изменилась с момента чтения</span>
<span class="hljs-keyword">try</span> {
<span class="hljs-keyword">const</span> task = <span class="hljs-keyword">await</span> api
<span class="hljs-keyword">let</span> task = <span class="hljs-keyword">await</span> api
.<span class="hljs-title function_">createOrder</span>(version, …);
} <span class="hljs-keyword">catch</span> (e) {
<span class="hljs-comment">// Если версия неверна, т.е. состояние</span>
@ -2713,16 +2713,16 @@ X-Idempotency-Token: &#x3C;токен>
<p>Описанный в предыдущей главе подход фактически представляет собой размен производительности API на «нормальный» (т.е. ожидаемый) фон ошибок при работе с ним путём изоляции компонента, отвечающего за строгую консистентность и управление параллелизмом внутри системы. Тем не менее, его пропускная способность всё равно ограничена, и увеличить её мы можем единственным образом — убрав строгую консистентность из внешнего API, что даст возможность осуществлять чтение состояния системы из реплик:</p>
<pre><code class="language-typescript"><span class="hljs-comment">// Получаем состояние,</span>
<span class="hljs-comment">// возможно, из реплики</span>
<span class="hljs-keyword">const</span> orderState =
<span class="hljs-keyword">let</span> orderState =
<span class="hljs-keyword">await</span> api.<span class="hljs-title function_">getOrderState</span>();
<span class="hljs-keyword">const</span> version =
<span class="hljs-keyword">let</span> version =
orderState.<span class="hljs-property">latestVersion</span>;
<span class="hljs-keyword">try</span> {
<span class="hljs-comment">// Обработчик запроса на </span>
<span class="hljs-comment">// создание заказа прочитает</span>
<span class="hljs-comment">// актуальную версию </span>
<span class="hljs-comment">// из мастер-данных</span>
<span class="hljs-keyword">const</span> task = <span class="hljs-keyword">await</span> api
<span class="hljs-keyword">let</span> task = <span class="hljs-keyword">await</span> api
.<span class="hljs-title function_">createOrder</span>(version, …);
} <span class="hljs-keyword">catch</span> (e) {
@ -2732,18 +2732,18 @@ X-Idempotency-Token: &#x3C;токен>
<p><strong>NB</strong>: на всякий случай уточним, что выбирать подходящий подход вы можете только в случае разработки новых API. Если вы уже предоставляете эндпойнт, реализующий какую-то модель консистентности, вы не можете понизить её уровень (в частности, сменить строгую консистентность на слабую), даже если вы никогда не документировали текущее поведение явно (мы обсудим это требование детальнее в главе <a href="#back-compat-iceberg-waterline">«О ватерлинии айсберга»</a> раздела «Обратная совместимость»).</p>
<p>Однако, выбор слабой консистентности вместо сильной влечёт за собой и другие проблемы. Да, мы можем потребовать от партнёров дождаться получения последнего актуального состояния ресурса перед внесением изменений. Но очень неочевидно (и в самом деле неудобно) требовать от партнёров быть готовыми к тому, что они должны дождаться появления в том числе и тех изменений, которые сами же внесли.</p>
<pre><code class="language-typescript"><span class="hljs-comment">// Создаёт заказ</span>
<span class="hljs-keyword">const</span> api = <span class="hljs-keyword">await</span> api
<span class="hljs-keyword">let</span> api = <span class="hljs-keyword">await</span> api
.<span class="hljs-title function_">createOrder</span>(…)
<span class="hljs-comment">// Возвращает список заказов</span>
<span class="hljs-keyword">const</span> pendingOrders = <span class="hljs-keyword">await</span> api.
<span class="hljs-keyword">let</span> pendingOrders = <span class="hljs-keyword">await</span> api.
<span class="hljs-title function_">getOngoingOrders</span>(); <span class="hljs-comment">// → []</span>
<span class="hljs-comment">// список пуст</span>
</code></pre>
<p>Если мы не гарантируем сильную консистентность, то второй вызов может запросто вернуть пустой результат, ведь при чтении из реплики новый заказ мог просто до неё ещё не дойти.</p>
<p>Важный паттерн, который поможет в этой ситуации — это имплементация модели «read-your-writes»<a href="#ref-chapter-18-no-33" id="ref-chapter-18-no-33-back" class="ref"><sup>2</sup></a>, а именно гарантии, что клиент всегда «видит» те изменения, которые сам же и внёс. Поднять уровень слабой консистентности до read-your-writes можно, если предложить клиенту самому передать токен, описывающий его последние изменения.</p>
<pre><code class="language-typescript"><span class="hljs-keyword">const</span> order = <span class="hljs-keyword">await</span> api
<pre><code class="language-typescript"><span class="hljs-keyword">let</span> order = <span class="hljs-keyword">await</span> api
.<span class="hljs-title function_">createOrder</span>(…);
<span class="hljs-keyword">const</span> pendingOrders = <span class="hljs-keyword">await</span> api.
<span class="hljs-keyword">let</span> pendingOrders = <span class="hljs-keyword">await</span> api.
<span class="hljs-title function_">getOngoingOrders</span>({
…,
<span class="hljs-comment">// Передаём идентификатор</span>
@ -2806,19 +2806,19 @@ X-Idempotency-Token: &#x3C;токен>
<p>Продолжим рассматривать предыдущий пример. Пусть на старте приложение получает <em>какое-то</em> состояние системы, возможно, не самое актуальное. От чего ещё зависит вероятность коллизий и как мы можем её снизить?</p>
<p>Напомним, что вероятность эта равна отношению периода времени, требуемого для получения актуального состояния к типичному периоду времени, за который пользователь перезапускает приложение и повторяет заказ. Повлиять на знаменатель этой дроби мы практически не можем (если только не будем преднамеренно вносить задержку инициализации API, что мы всё же считаем крайней мерой). Обратимся теперь к числителю.</p>
<p>Наш сценарий использования, напомним, выглядит так:</p>
<pre><code class="language-typescript"><span class="hljs-keyword">const</span> pendingOrders = <span class="hljs-keyword">await</span> api.
<pre><code class="language-typescript"><span class="hljs-keyword">let</span> pendingOrders = <span class="hljs-keyword">await</span> api.
<span class="hljs-title function_">getOngoingOrders</span>();
<span class="hljs-keyword">if</span> (pendingOrder.<span class="hljs-property">length</span> == <span class="hljs-number">0</span>) {
<span class="hljs-keyword">const</span> order = <span class="hljs-keyword">await</span> api
<span class="hljs-keyword">let</span> order = <span class="hljs-keyword">await</span> api
.<span class="hljs-title function_">createOrder</span>(…);
}
<span class="hljs-comment">// Здесь происходит крэш приложения,</span>
<span class="hljs-comment">// и те же операции выполняются</span>
<span class="hljs-comment">// повторно</span>
<span class="hljs-keyword">const</span> pendingOrders = <span class="hljs-keyword">await</span> api.
<span class="hljs-keyword">let</span> pendingOrders = <span class="hljs-keyword">await</span> api.
<span class="hljs-title function_">getOngoingOrders</span>(); <span class="hljs-comment">// → []</span>
<span class="hljs-keyword">if</span> (pendingOrder.<span class="hljs-property">length</span> == <span class="hljs-number">0</span>) {
<span class="hljs-keyword">const</span> order = <span class="hljs-keyword">await</span> api
<span class="hljs-keyword">let</span> order = <span class="hljs-keyword">await</span> api
.<span class="hljs-title function_">createOrder</span>(…);
}
</code></pre>
@ -2826,18 +2826,18 @@ X-Idempotency-Token: &#x3C;токен>
<p>Мы видим, что, если создание заказа само по себе происходит очень долго (здесь «очень долго» = «сопоставимо со временем запуска приложения»), то все наши усилия практически бесполезны. Пользователь может устать ждать исполнения вызова <code>createOrder</code>, выгрузить приложение и послать второй (и более) <code>createOrder</code>. В наших интересах сделать так, чтобы этого не происходило.</p>
<p>Но каким образом мы реально можем улучшить это время? Ведь создание заказа <em>действительно</em> может быть длительным — нам нужно выполнить множество проверок и дождаться ответа платёжного шлюза, подтверждения приёма заказа кофейней и т.д.</p>
<p>Здесь нам на помощь приходят асинхронные вызовы. Если наша цель — уменьшить число коллизий, то нам нет никакой нужды дожидаться, когда заказ будет <em>действительно</em> создан; наша цель — максимально быстро распространить по репликам знание о том, что заказ <em>принят к созданию</em>. Мы можем поступить следующим образом: создавать не заказ, а задание на создание заказа, и возвращать его идентификатор.</p>
<pre><code class="language-typescript"><span class="hljs-keyword">const</span> pendingOrders = <span class="hljs-keyword">await</span> api.
<pre><code class="language-typescript"><span class="hljs-keyword">let</span> pendingOrders = <span class="hljs-keyword">await</span> api.
<span class="hljs-title function_">getOngoingOrders</span>();
<span class="hljs-keyword">if</span> (pendingOrder.<span class="hljs-property">length</span> == <span class="hljs-number">0</span>) {
<span class="hljs-comment">// Вместо создания заказа</span>
<span class="hljs-comment">// размещаем задание на создание</span>
<span class="hljs-keyword">const</span> task = <span class="hljs-keyword">await</span> api
<span class="hljs-keyword">let</span> task = <span class="hljs-keyword">await</span> api
.<span class="hljs-title function_">putOrderCreationTask</span>(…);
}
<span class="hljs-comment">// Здесь происходит крэш приложения,</span>
<span class="hljs-comment">// и те же операции выполняются</span>
<span class="hljs-comment">// повторно</span>
<span class="hljs-keyword">const</span> pendingOrders = <span class="hljs-keyword">await</span> api.
<span class="hljs-keyword">let</span> pendingOrders = <span class="hljs-keyword">await</span> api.
<span class="hljs-title function_">getOngoingOrders</span>();
<span class="hljs-comment">// → { tasks: [task] }</span>
</code></pre>
@ -2875,18 +2875,18 @@ X-Idempotency-Token: &#x3C;токен>
</li>
</ol>
<p>Поэтому, при всей привлекательности идеи, мы всё же склонны рекомендовать ограничиться асинхронными интерфейсами только там, где они действительно критически важны (как в примере выше, где они снижают вероятность коллизий), и при этом иметь отдельные очереди для каждого кейса. Идеальное решение с очередями — то, которое вписано в бизнес-логику и вообще не выглядит очередью. Например, ничто не мешает нам объявить состояние «задание на создание заказа принято и ожидает исполнения» просто отдельным статусом заказа, а его идентификатор сделать идентификатором будущего заказа:</p>
<pre><code class="language-typescript"><span class="hljs-keyword">const</span> pendingOrders = <span class="hljs-keyword">await</span> api.
<pre><code class="language-typescript"><span class="hljs-keyword">let</span> pendingOrders = <span class="hljs-keyword">await</span> api.
<span class="hljs-title function_">getOngoingOrders</span>();
<span class="hljs-keyword">if</span> (pendingOrder.<span class="hljs-property">length</span> == <span class="hljs-number">0</span>) {
<span class="hljs-comment">// Не называем это «заданием» —</span>
<span class="hljs-comment">// просто создаём заказ</span>
<span class="hljs-keyword">const</span> order = <span class="hljs-keyword">await</span> api
<span class="hljs-keyword">let</span> order = <span class="hljs-keyword">await</span> api
.<span class="hljs-title function_">createOrder</span>(…);
}
<span class="hljs-comment">// Здесь происходит крэш приложения,</span>
<span class="hljs-comment">// и те же операции выполняются</span>
<span class="hljs-comment">// повторно</span>
<span class="hljs-keyword">const</span> pendingOrders = <span class="hljs-keyword">await</span> api.
<span class="hljs-keyword">let</span> pendingOrders = <span class="hljs-keyword">await</span> api.
<span class="hljs-title function_">getOngoingOrders</span>();
<span class="hljs-comment">/* → { orders: [{
order_id: &#x3C;идентификатор задания>,
@ -2895,7 +2895,7 @@ X-Idempotency-Token: &#x3C;токен>
</code></pre>
<p><strong>NB</strong>: отметим также, что в формате асинхронного взаимодействия можно передавать не только бинарный статус (выполнено задание или нет), но и прогресс выполнения в процентах, если это возможно.</p><h4>Примечания</h4><ul class="references"><li><p><a href="#ref-chapter-19-no-34-back" class="back-anchor" id="ref-chapter-19-no-34"><sup>1</sup> </a><span>Token bucket<br><a target="_blank" class="external" href="https://en.wikipedia.org/wiki/Token_bucket">https://en.wikipedia.org/wiki/Token_bucket</a></span></p></li></ul><div class="page-break"></div><h3><a href="#api-patterns-lists" class="anchor" id="api-patterns-lists">Глава 20. Списки и организация доступа к ним</a><a href="#chapter-20" class="secondary-anchor" id="chapter-20"> </a></h3>
<p>В предыдущей главе мы пришли вот к такому интерфейсу, позволяющему минимизировать коллизии при создании заказов:</p>
<pre><code class="language-typescript"><span class="hljs-keyword">const</span> pendingOrders = <span class="hljs-keyword">await</span> api
<pre><code class="language-typescript"><span class="hljs-keyword">let</span> pendingOrders = <span class="hljs-keyword">await</span> api
.<span class="hljs-title function_">getOngoingOrders</span>();
{ <span class="hljs-attr">orders</span>: [{
@ -3470,13 +3470,13 @@ Host: partners.host
<li>
<p>Безусловный повтор запроса:</p>
<pre><code class="language-typescript"><span class="hljs-comment">// Получаем все текущие заказы</span>
<span class="hljs-keyword">const</span> pendingOrders = <span class="hljs-keyword">await</span> api
<span class="hljs-keyword">let</span> pendingOrders = <span class="hljs-keyword">await</span> api
.<span class="hljs-title function_">getPendingOrders</span>();
<span class="hljs-comment">// Партнёр проверяет статус</span>
<span class="hljs-comment">// каждого из них в своей</span>
<span class="hljs-comment">// системе и готовит</span>
<span class="hljs-comment">// необходимые изменения</span>
<span class="hljs-keyword">const</span> changes =
<span class="hljs-keyword">let</span> changes =
<span class="hljs-keyword">await</span> <span class="hljs-title function_">prepareStatusChanges</span>(
pendingOrders
);
@ -3505,7 +3505,7 @@ Host: partners.host
</li>
<li>
<p>Повтор только неудавшихся подзапросов:</p>
<pre><code class="language-typescript"><span class="hljs-keyword">const</span> pendingOrders = <span class="hljs-keyword">await</span> api
<pre><code class="language-typescript"><span class="hljs-keyword">let</span> pendingOrders = <span class="hljs-keyword">await</span> api
.<span class="hljs-title function_">getPendingOrders</span>();
<span class="hljs-keyword">let</span> changes =
<span class="hljs-keyword">await</span> <span class="hljs-title function_">prepareStatusChanges</span>(
@ -3540,9 +3540,9 @@ Host: partners.host
<li>
<p>Рестарт всей операции, т.е. в нашем случае — перезапрос всех новых заказов и формирование нового запроса на изменение:</p>
<pre><code class="language-typescript"><span class="hljs-keyword">do</span> {
<span class="hljs-keyword">const</span> pendingOrders = <span class="hljs-keyword">await</span> api
<span class="hljs-keyword">let</span> pendingOrders = <span class="hljs-keyword">await</span> api
.<span class="hljs-title function_">getPendingOrders</span>();
<span class="hljs-keyword">const</span> changes =
<span class="hljs-keyword">let</span> changes =
<span class="hljs-keyword">await</span> <span class="hljs-title function_">prepareStatusChanges</span>(
pendingOrders
);
@ -5630,17 +5630,17 @@ Retry-After: <span class="hljs-number">5</span>
<p>В клиент-серверных API данные передаются только по значению; чтобы сослаться на какую-то сущность, необходимо использовать какие-то внешние идентификаторы. Например, если у нас есть два набора сущностей — рецепты и предложения кофе — то нам необходимо будет построить карту рецептов по id, чтобы понять, на какой рецепт ссылается какое предложение:</p>
<pre><code class="language-typescript"><span class="hljs-comment">// Запрашиваем информацию о рецептах</span>
<span class="hljs-comment">// лунго и латте</span>
<span class="hljs-keyword">const</span> recipes = <span class="hljs-keyword">await</span> api
<span class="hljs-keyword">let</span> recipes = <span class="hljs-keyword">await</span> api
.<span class="hljs-title function_">getRecipes</span>([<span class="hljs-string">'lungo'</span>, <span class="hljs-string">'latte'</span>]);
<span class="hljs-comment">// Строим карту, позволяющую обратиться</span>
<span class="hljs-comment">// к данным о рецепте по его id</span>
<span class="hljs-keyword">const</span> recipeMap = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Map</span>();
<span class="hljs-keyword">let</span> recipeMap = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Map</span>();
recipes.<span class="hljs-title function_">forEach</span>(<span class="hljs-function">(<span class="hljs-params">recipe</span>) =></span> {
recipeMap.<span class="hljs-title function_">set</span>(recipe.<span class="hljs-property">id</span>, recipe);
});
<span class="hljs-comment">// Запрашиваем предложения</span>
<span class="hljs-comment">// лунго и латте</span>
<span class="hljs-keyword">const</span> offers = <span class="hljs-keyword">await</span> api.<span class="hljs-title function_">search</span>({
<span class="hljs-keyword">let</span> offers = <span class="hljs-keyword">await</span> api.<span class="hljs-title function_">search</span>({
<span class="hljs-attr">recipes</span>: [<span class="hljs-string">'lungo'</span>, <span class="hljs-string">'latte'</span>],
location
});
@ -5651,7 +5651,7 @@ recipes.<span class="hljs-title function_">forEach</span>(<span class="hljs-func
<span class="hljs-title function_">promptUser</span>(
<span class="hljs-string">'Найденные предложения'</span>,
offers.<span class="hljs-title function_">map</span>(<span class="hljs-function">(<span class="hljs-params">offer</span>) =></span> {
<span class="hljs-keyword">const</span> recipe = recipeMap
<span class="hljs-keyword">let</span> recipe = recipeMap
.<span class="hljs-title function_">get</span>(offer.<span class="hljs-property">recipe_id</span>);
<span class="hljs-keyword">return</span> {offer, recipe};
})
@ -5660,11 +5660,11 @@ recipes.<span class="hljs-title function_">forEach</span>(<span class="hljs-func
<p>Указанный код мог бы быть вдвое короче, если бы мы сразу получали из метода <code>api.search</code> предложения с заполненной ссылкой на рецепт:</p>
<pre><code class="language-typescript"><span class="hljs-comment">// Запрашиваем информацию о рецептах</span>
<span class="hljs-comment">// лунго и латте</span>
<span class="hljs-keyword">const</span> recipes = <span class="hljs-keyword">await</span> api
<span class="hljs-keyword">let</span> recipes = <span class="hljs-keyword">await</span> api
.<span class="hljs-title function_">getRecipes</span>([<span class="hljs-string">'lungo'</span>, <span class="hljs-string">'latte'</span>]);
<span class="hljs-comment">// Запрашиваем предложения</span>
<span class="hljs-comment">// лунго и латте</span>
<span class="hljs-keyword">const</span> offers = <span class="hljs-keyword">await</span> api.<span class="hljs-title function_">search</span>({
<span class="hljs-keyword">let</span> offers = <span class="hljs-keyword">await</span> api.<span class="hljs-title function_">search</span>({
<span class="hljs-comment">// Передаём не идентификаторы</span>
<span class="hljs-comment">// рецептов, а ссылки на объекты,</span>
<span class="hljs-comment">// описывающие рецепты</span>
@ -5684,7 +5684,7 @@ recipes.<span class="hljs-title function_">forEach</span>(<span class="hljs-func
<p>Клиент-серверные API, как правило, стараются декомпозировать так, чтобы одному запросу соответствовал один тип возвращаемых данных. Даже если эндпойнт композитный (т.е. позволяет при запросе с помощью параметров указать, какие из дополнительных данных необходимо вернуть), это всё ещё ответственность разработчика этими параметрами воспользоваться. Код из примера выше мог бы быть ещё короче, если бы SDK взял на себя инициализацию всех нужных связанных объектов:</p>
<pre><code class="language-typescript"><span class="hljs-comment">// Запрашиваем предложения</span>
<span class="hljs-comment">// лунго и латте</span>
<span class="hljs-keyword">const</span> offers = <span class="hljs-keyword">await</span> api.<span class="hljs-title function_">search</span>({
<span class="hljs-keyword">let</span> offers = <span class="hljs-keyword">await</span> api.<span class="hljs-title function_">search</span>({
<span class="hljs-attr">recipes</span>: [<span class="hljs-string">'lungo'</span>, <span class="hljs-string">'latte'</span>],
location
});
@ -5703,10 +5703,10 @@ recipes.<span class="hljs-title function_">forEach</span>(<span class="hljs-func
<li>
<p>Получение обратных вызовов в клиент-серверном API, даже если это дуплексный канал, с точки зрения клиента выглядит крайне неудобным в разработке, поскольку вновь требует наличия карт объектов. Даже если в API реализована push-модель, код выходит чрезвычайно громоздким:</p>
<pre><code class="language-typescript"><span class="hljs-comment">// Получаем текущие заказы</span>
<span class="hljs-keyword">const</span> orders = <span class="hljs-keyword">await</span> api
<span class="hljs-keyword">let</span> orders = <span class="hljs-keyword">await</span> api
.<span class="hljs-title function_">getOngoingOrders</span>();
<span class="hljs-comment">// Строим карту заказов</span>
<span class="hljs-keyword">const</span> orderMap = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Map</span>();
<span class="hljs-keyword">let</span> orderMap = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Map</span>();
orders.<span class="hljs-title function_">forEach</span>(<span class="hljs-function">(<span class="hljs-params">order</span>) =></span> {
orderMap.<span class="hljs-title function_">set</span>(order.<span class="hljs-property">id</span>, order);
});
@ -5715,7 +5715,7 @@ orders.<span class="hljs-title function_">forEach</span>(<span class="hljs-funct
api.<span class="hljs-title function_">subscribe</span>(
<span class="hljs-string">'order_state_change'</span>,
<span class="hljs-function">(<span class="hljs-params">event</span>) =></span> {
<span class="hljs-keyword">const</span> order = orderMap
<span class="hljs-keyword">let</span> order = orderMap
.<span class="hljs-title function_">get</span>(event.<span class="hljs-property">order_id</span>);
<span class="hljs-comment">// Выполняем какие-то</span>
<span class="hljs-comment">// действия с заказом,</span>
@ -5732,7 +5732,7 @@ api.<span class="hljs-title function_">subscribe</span>(
<li>если пришло событие изменения какого-то неизвестного приложению заказа (который, например, был создан с другого устройства или в другом потоке исполнения), поиск в карте заказов вернёт пустой результат, и обработчик события выбросит исключение, которое никак не обработано.</li>
</ul>
<p>И вновь мы приходим к тому, что недостаточно продуманный SDK приводит к ошибкам в работе использующих его приложений. Разработчику было бы намного удобнее, если бы объект заказа позволял подписаться на свои события, не задумываясь о том, как эта подписка технически работает и как не пропустить события:</p>
<pre><code class="language-typescript"><span class="hljs-keyword">const</span> order = <span class="hljs-keyword">await</span> api
<pre><code class="language-typescript"><span class="hljs-keyword">let</span> order = <span class="hljs-keyword">await</span> api
.<span class="hljs-title function_">createOrder</span>(…)
<span class="hljs-comment">// Нет нужды подписываться</span>
<span class="hljs-comment">// на *все* события и потом</span>
@ -5747,10 +5747,10 @@ api.<span class="hljs-title function_">subscribe</span>(
<li>
<p>Восстановление после ошибок в бизнес-логике, как правило, достаточно сложная операция, которую сложно описать в машиночитаемом виде. Разработчику клиента необходимо самому продумать эти сценарии.</p>
<pre><code class="language-typescript"><span class="hljs-comment">// Получаем предложения</span>
<span class="hljs-keyword">const</span> offers = <span class="hljs-keyword">await</span> api.<span class="hljs-title function_">search</span>(…);
<span class="hljs-keyword">let</span> offers = <span class="hljs-keyword">await</span> api.<span class="hljs-title function_">search</span>(…);
<span class="hljs-comment">// Пользователь выбирает</span>
<span class="hljs-comment">// подходящее предложение</span>
<span class="hljs-keyword">const</span> selectedOffer = <span class="hljs-keyword">await</span> <span class="hljs-title function_">promptUser</span>(offers);
<span class="hljs-keyword">let</span> selectedOffer = <span class="hljs-keyword">await</span> <span class="hljs-title function_">promptUser</span>(offers);
<span class="hljs-keyword">let</span> order;
<span class="hljs-keyword">let</span> offer = selectedOffer;
<span class="hljs-keyword">let</span> numberOfTries = <span class="hljs-number">0</span>;
@ -5842,11 +5842,11 @@ api.<span class="hljs-title function_">subscribe</span>(
<p>Возникает вопрос: если выбрано предложение сетевой кофейни, какая иконка должна быть на кнопке подтверждения заказа — та, что унаследована из данных предложения (логотип кофейни) или та, что унаследована от «рода занятий» самой кнопки? Элемент управления «создать заказ», таким образом, встроен в две иерархии сущностей (по визуальному отображению и по данным) и в равной степени наследует обоим.</p>
<p>Можно легко продемонстрировать, как пересечение нескольких предметных областей в одном объекте быстро приводит к крайне запутанной и неочевидной логике. Поскольку те же соображения справедливы и для кнопки «Показать на карте», вроде бы очевидно, что по умолчанию более частные свойства должны побеждать более общие, т.е. тип кнопки должен быть приоритетнее какой-то абстрактной «иконки» в данных.</p>
<p>Но на этом история не заканчивается. Если разработчик всё-таки хочет именно этого, т.е. показывать иконку сети кофеен (если она есть) на кнопке создания заказа — как ему это сделать? Из той же логики, нам необходимо предоставить ещё более частную возможность такого переопределения. Например, представим себе следующую функциональность: если в данных предложения есть поле <code>createOrderButtonIconUrl</code>, то иконка будет взята из этого поля. Тогда разработчик сможет кастомизировать кнопку заказа, подменив в данных поле <code>createOrderButtonIconUrl</code> для каждого результата поиска:</p>
<pre><code class="language-typescript"><span class="hljs-keyword">const</span> searchBox = <span class="hljs-keyword">new</span> <span class="hljs-title class_">SearchBox</span>({
<pre><code class="language-typescript"><span class="hljs-keyword">let</span> searchBox = <span class="hljs-keyword">new</span> <span class="hljs-title class_">SearchBox</span>({
<span class="hljs-comment">// Предположим, что мы разрешили</span>
<span class="hljs-comment">// переопределять поисковую функцию</span>
<span class="hljs-attr">searchFunction</span>: <span class="hljs-keyword">function</span> (<span class="hljs-params">params</span>) {
<span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> api.<span class="hljs-title function_">search</span>(params);
<span class="hljs-keyword">let</span> res = <span class="hljs-keyword">await</span> api.<span class="hljs-title function_">search</span>(params);
res.<span class="hljs-title function_">forEach</span>(<span class="hljs-keyword">function</span> (<span class="hljs-params">item</span>) {
item.<span class="hljs-property">createOrderButtonIconUrl</span> =
<span class="language-xml"><span class="hljs-tag">&#x3C;<span class="hljs-name">URL</span> <span class="hljs-attr">нужнои</span>̆ <span class="hljs-attr">иконки</span>></span>;
@ -6052,7 +6052,7 @@ api.<span class="hljs-title function_">subscribe</span>(
</li>
<li>
<p>Для реализации новых кнопок мы можем только лишь предложить программисту реализовать свой список предложений (чтобы предоставить методы выбора предыдущего / следующего предложения) и свою панель предложений, которая эти методы будет вызывать. Даже если мы придумаем какой-нибудь простой способ кастомизировать, например, текст кнопки «Сделать заказ», его поддержка всё равно будет ответственностью компонента <code>OfferList</code>:</p>
<pre><code class="language-typescript"><span class="hljs-keyword">const</span> searchBox = <span class="hljs-keyword">new</span> <span class="hljs-title class_">SearchBox</span>(…, {
<pre><code class="language-typescript"><span class="hljs-keyword">let</span> searchBox = <span class="hljs-keyword">new</span> <span class="hljs-title class_">SearchBox</span>(…, {
<em><span class="hljs-attr">offerPanelCreateOrderButtonText</span>:
<span class="hljs-string">'Drink overpriced coffee!'</span></em>
});
@ -6230,7 +6230,7 @@ api.<span class="hljs-title function_">subscribe</span>(
<span class="hljs-string">'offerSelect'</span>, <span class="hljs-variable language_">this</span>.<span class="hljs-property">selectOffer</span>
);
<span class="hljs-variable language_">this</span>.<span class="hljs-property">offerPanelComponent</span>.<span class="hljs-property">events</span>.<span class="hljs-title function_">on</span>(
<span class="hljs-string">'action'</span>, <span class="hljs-variable language_">this</span>.<span class="hljs-property">performAction</span>
<span class="hljs-string">'action'</span>, <span class="hljs-variable language_">this</span>.<span class="hljs-property">performAction</span>
);
}

Binary file not shown.

View File

@ -2,22 +2,32 @@
margin: 0;
padding: 0;
border: none;
font-family: local-serif, Arial, Helvetica, sans-serif;
list-style-type: none;
}
header {
display: flex;
flex-direction: column;
}
h1 {
font-size: 150%;
}
h1.title {
h1 .title {
font-size: 200%;
}
h2 {
font-size: 120%;
}
ul > li {
ul.toc > li,
ul.section > li {
padding-left: 1em;
font-family: local-sans;
}
ul.section > li {
text-transform: none;
}
p,
@ -31,6 +41,11 @@ body {
margin: 5px;
}
body > ul.toc > li,
h3 {
text-transform: uppercase;
}
nav {
text-align: center;
}

View File

@ -16,13 +16,14 @@ The following improvements to the code are left as an exercise for the reader:
4. Refactor `ISearchBoxComposer` as a composition of two interfaces: one facade for interacting with a `SearchBox`, and another for communication with child components.
5. Create a separate composer to bridge the gap between `OfferPanelComponent` and its buttons.
6. Enhance the `SearchBox.search` method to return an operation status:
```
```typescript
public search(query: string): Promise<OperationResult>
```
Where OperationResult is defined as:
```
```typescript
type OperationResult =
| {
status: OperationResultStatus.SUCCESS;
@ -37,7 +38,7 @@ The following improvements to the code are left as an exercise for the reader:
With the enum:
```
```typescript
export enum OperationResultStatus {
SUCCESS = 'success',
FAIL = 'fail',
@ -51,5 +52,7 @@ The following improvements to the code are left as an exercise for the reader:
10. Make options mutable by exposing an `optionChange` event and implementing the `Composer`'s reaction to relevant option changes.
11. Parameterize all extra options, content fields, actions, and events.
12. Parametrize the markups of components either by:
* Encapsulating them in Layout entities controlled through options. Create interfaces for each layout and a VisualComponent base class for entities with layouts. Inherit SearchBox, OfferListComponent, and OfferPanelComponent from this base class.
* Rewriting components as React / ReactNative / SwiftUI / Android View components or as UI components for other platforms of your choice.
* Encapsulating them in Layout entities controlled through options. Create interfaces for each layout and a VisualComponent base class for entities with layouts. Inherit SearchBox, OfferListComponent, and OfferPanelComponent from this base class.
* Rewriting components as React / ReactNative / SwiftUI / Android View components or as UI components for other platforms of your choice.

View File

@ -86,7 +86,7 @@
#editor {
min-width: 0 !important;
min-height: 500px;
width: calc(100% - var(--example-list-width));
width: calc(100% - var(--example-list-width) - 16);
float: none;
flex: 1 0 auto;
}

View File

@ -36,8 +36,10 @@
src="assets/header.jpg"
alt="Sergey Konstantinov. The API"
/><br />
<h1>Sergey Konstantinov<br/><span class="title">The API</span></h1>
<h2>Free e-book</h2>
<header>
<h1>Sergey Konstantinov<br/><span class="title">The API</span></h1>
<h2>Free e-book</h2>
</header>
<br />Subscribe for updates on <a class="github" href="https://github.com/twirl/The-API-Book"></a>
<br/>Follow me on <a class="linkedin" href="https://www.linkedin.com/in/twirl/"></a> · <a class="twitter" href="https://twitter.com/blogovodoved"></a> · <a class="substack" href="https://twirl.substack.com/">Substack</a>
<br />Support this work on <a class="patreon" href="https://www.patreon.com/yatwirl">Patreon</a> · <a class="kindle" href="https://www.amazon.com/gp/product/B09RHH44S5/ref=dbs_a_def_rwt_hsch_vapi_tkin_p1_i0">buy Kindle version [1st edition]</a>
@ -55,9 +57,9 @@
<p>You might download ‘The API’ in <a href="API.en.pdf">PDF</a> / <a href="API.en.epub">EPUB</a> or <a href="API.en.html">read it online</a>.
</p>
<h3>Table of Contents</h3>
<ul><li>
<ul class="toc"><li>
<h4><a href="API.en.html#section-1">Introduction</a></h4>
<ul>
<ul class="section">
<li><a href="API.en.html#intro-structure">Chapter 1. On the Structure of This Book</a></li>
<li><a href="API.en.html#intro-api-definition">Chapter 2. The API Definition</a></li>
<li><a href="API.en.html#intro-api-solutions-overview">Chapter 3. An Overview of Existing API Development Solutions</a></li>
@ -70,7 +72,7 @@
</li>
<li>
<h4><a href="API.en.html#section-2">Section I. The API Design</a></h4>
<ul>
<ul class="section">
<li><a href="API.en.html#api-design-context-pyramid">Chapter 9. The API Contexts Pyramid</a></li>
<li><a href="API.en.html#api-design-defining-field">Chapter 10. Defining an Application Field</a></li>
<li><a href="API.en.html#api-design-separating-abstractions">Chapter 11. Separating Abstraction Levels</a></li>
@ -81,7 +83,7 @@
</li>
<li>
<h4><a href="API.en.html#section-3">Section II. The API Patterns</a></h4>
<ul>
<ul class="section">
<li><a href="API.en.html#api-patterns-context">Chapter 15. On Design Patterns in the API Context</a></li>
<li><a href="API.en.html#api-patterns-aa">Chapter 16. Authenticating Partners and Authorizing API Calls</a></li>
<li><a href="API.en.html#api-patterns-sync-strategies">Chapter 17. Synchronization Strategies</a></li>
@ -97,7 +99,7 @@
</li>
<li>
<h4><a href="API.en.html#section-4">Section III. The Backward Compatibility</a></h4>
<ul>
<ul class="section">
<li><a href="API.en.html#back-compat-statement">Chapter 26. The Backward Compatibility Problem Statement</a></li>
<li><a href="API.en.html#back-compat-iceberg-waterline">Chapter 27. On the Waterline of the Iceberg</a></li>
<li><a href="API.en.html#back-compat-abstracting-extending">Chapter 28. Extending through Abstracting</a></li>
@ -109,7 +111,7 @@
</li>
<li>
<h4><a href="API.en.html#section-5">Section IV. HTTP APIs & the REST Architectural Principles</a></h4>
<ul>
<ul class="section">
<li><a href="API.en.html#http-api-concepts">Chapter 33. On the HTTP API Concept and Terminology</a></li>
<li><a href="API.en.html#http-api-pros-and-cons">Chapter 34. Advantages and Disadvantages of HTTP APIs Compared to Alternative Technologies</a></li>
<li><a href="API.en.html#http-api-rest-myth">Chapter 35. The REST Myth</a></li>
@ -122,7 +124,7 @@
</li>
<li>
<h4><a href="API.en.html#section-6">[Work in Progress] Section V. SDKs & UI Libraries</a></h4>
<ul>
<ul class="section">
<li><a href="API.en.html#sdk-toc">Chapter 41. On the Content of This Section</a></li>
<li><a href="API.en.html#sdk-problems-solutions">Chapter 42. SDKs: Problems and Solutions</a></li>
<li><a href="API.en.html#sdk-ui-components">Chapter 43. Problems of Introducing UI Components</a></li>
@ -136,13 +138,13 @@
</li>
<li>
<h4><a href="API.en.html#section-7">Section VI. The API Product</a></h4>
<ul>
<ul class="section">
<li><a href="API.en.html#api-product">Chapter 50. The API as a Product</a></li>
<li><a href="API.en.html#api-product-business-models">Chapter 51. API Business Models</a></li>
<li><a href="API.en.html#api-product-vision">Chapter 52. Developing a Product Vision</a></li>
<li><a href="API.en.html#api-product-devrel">Chapter 53. Communicating with Developers</a></li>
<li><a href="API.en.html#api-product-business-comms">Chapter 54. Communicating with Business Owners</a></li>
<li><a href="API.en.html#api-product-range">Chapter 55. An API Services Range</a></li>
<li><a href="API.en.html#api-product-lineup">Chapter 55. An API Services Lineup</a></li>
<li><a href="API.en.html#api-product-kpi">Chapter 56. API Key Performance Indicators</a></li>
<li><a href="API.en.html#api-product-antifraud">Chapter 57. Identifying Users and Preventing Fraud</a></li>
<li><a href="API.en.html#api-product-tos-violations">Chapter 58. The Technical Means of Preventing ToS Violations</a></li>
@ -154,7 +156,7 @@
</li>
<li>
<h4>Live Examples</h3>
<ul><li><a href="examples/01. Decomposing UI Components">Decomposing UI Components</a></li>
<ul class="section"><li><a href="examples/01. Decomposing UI Components">Decomposing UI Components</a></li>
</ul>
</li>
</ul>

View File

@ -36,8 +36,10 @@
src="assets/header.jpg"
alt="Сергей Константинов. API"
/><br />
<h1>Сергей Константинов<br/><span class="title">API</span></h1>
<h2>Бесплатная электронная книга</h2>
<header>
<h1>Сергей Константинов<br/><span class="title">API</span></h1>
<h2>Бесплатная электронная книга</h2>
</header>
<br />Подпишитесь на обновления на <a class="habr" href="https://habr.com/ru/users/forgotten/">Хабре</a>
<br/>Follow me on <a class="linkedin" href="https://www.linkedin.com/in/twirl/"></a> · <a class="twitter" href="https://twitter.com/blogovodoved"></a> · <a class="substack" href="https://twirl.substack.com/">Substack</a>
<br />Поддержите эту работу на <a class="patreon" href="https://www.patreon.com/yatwirl">Patreon</a>
@ -55,9 +57,9 @@
<p>Вы можете скачать книгу «API» в формате <a href="API.ru.pdf">PDF</a> / <a href="API.ru.epub">EPUB</a> или <a href="API.ru.html">прочитать её онлайн</a>.
</p>
<h3>Содержание</h3>
<ul><li>
<ul class="toc"><li>
<h4><a href="API.ru.html#section-1">Введение</a></h4>
<ul>
<ul class="section">
<li><a href="API.ru.html#intro-structure">Глава 1. О структуре этой книги</a></li>
<li><a href="API.ru.html#intro-api-definition">Глава 2. Определение API</a></li>
<li><a href="API.ru.html#intro-api-solutions-overview">Глава 3. Обзор существующих решений в области разработки API</a></li>
@ -70,7 +72,7 @@
</li>
<li>
<h4><a href="API.ru.html#section-2">Раздел I. Проектирование API</a></h4>
<ul>
<ul class="section">
<li><a href="API.ru.html#api-design-context-pyramid">Глава 9. Пирамида контекстов API</a></li>
<li><a href="API.ru.html#api-design-defining-field">Глава 10. Определение области применения</a></li>
<li><a href="API.ru.html#api-design-separating-abstractions">Глава 11. Разделение уровней абстракции</a></li>
@ -81,7 +83,7 @@
</li>
<li>
<h4><a href="API.ru.html#section-3">Раздел II. Паттерны дизайна API</a></h4>
<ul>
<ul class="section">
<li><a href="API.ru.html#chapter-15">Глава 15. О паттернах проектирования в контексте API</a></li>
<li><a href="API.ru.html#api-patterns-aa">Глава 16. Аутентификация партнёров и авторизация вызовов API</a></li>
<li><a href="API.ru.html#api-patterns-sync-strategies">Глава 17. Стратегии синхронизации</a></li>
@ -97,7 +99,7 @@
</li>
<li>
<h4><a href="API.ru.html#section-4">Раздел III. Обратная совместимость</a></h4>
<ul>
<ul class="section">
<li><a href="API.ru.html#back-compat-statement">Глава 26. Постановка проблемы обратной совместимости</a></li>
<li><a href="API.ru.html#back-compat-iceberg-waterline">Глава 27. О ватерлинии айсберга</a></li>
<li><a href="API.ru.html#back-compat-abstracting-extending">Глава 28. Расширение через абстрагирование</a></li>
@ -109,7 +111,7 @@
</li>
<li>
<h4><a href="API.ru.html#section-5">Раздел IV. HTTP API и архитектурные принципы REST</a></h4>
<ul>
<ul class="section">
<li><a href="API.ru.html#http-api-concepts">Глава 33. О концепции HTTP API и терминологии</a></li>
<li><a href="API.ru.html#http-api-pros-and-cons">Глава 34. Преимущества и недостатки HTTP API в сравнении с альтернативными технологиями</a></li>
<li><a href="API.ru.html#http-api-rest-myth">Глава 35. Мифология REST</a></li>
@ -122,7 +124,7 @@
</li>
<li>
<h4><a href="API.ru.html#section-6">[В разработке] Раздел V. SDK и UI</a></h4>
<ul>
<ul class="section">
<li><a href="API.ru.html#sdk-toc">Глава 41. О содержании раздела</a></li>
<li><a href="API.ru.html#sdk-problems-solutions">Глава 42. SDK: проблемы и решения</a></li>
<li><a href="API.ru.html#sdk-ui-components">Глава 43. Проблемы встраивания UI-компонентов</a></li>
@ -136,7 +138,7 @@
</li>
<li>
<h4><a href="API.ru.html#section-7">Раздел VI. API как продукт</a></h4>
<ul>
<ul class="section">
<li><a href="API.ru.html#api-product">Глава 50. Продукт API</a></li>
<li><a href="API.ru.html#api-product-business-models">Глава 51. Бизнес-модели API</a></li>
<li><a href="API.ru.html#api-product-vision">Глава 52. Формирование продуктового видения</a></li>
@ -154,7 +156,7 @@
</li>
<li>
<h4>Интерактивные примеры</h3>
<ul><li><a href="examples/01. Decomposing UI Components">Decomposing UI Components</a></li>
<ul class="section"><li><a href="examples/01. Decomposing UI Components">Decomposing UI Components</a></li>
</ul>
</li>
</ul>

View File

@ -80,7 +80,7 @@ But let's go a bit further and imagine there is an error in a new version of the
Therefore, the task of proactively lowering the number of these background errors is crucially important. We may try to reduce their occurrence for typical usage profiles.
**NB**: The “typical usage profile” stipulation is important: an API implies the variability of client scenarios, and API usage cases might fall into several groups, each featuring quite different error profiles. The classical example is client APIs (where it's an end user who makes actions and waits for results) versus server APIs (where the execution time is per se not so important — but let's say mass parallel execution might be). If this happens, it's a strong signal to make a family of API products covering different usage scenarios, as we will discuss in “[The API Services Range](#api-product-range)” chapter of “The API Product” section of this book.
**NB**: The “typical usage profile” stipulation is important: an API implies the variability of client scenarios, and API usage cases might fall into several groups, each featuring quite different error profiles. The classical example is client APIs (where it's an end user who makes actions and waits for results) versus server APIs (where the execution time is per se not so important — but let's say mass parallel execution might be). If this happens, it's a strong signal to make a family of API products covering different usage scenarios, as we will discuss in “[The API Services Lineup](#api-product-lineup)” chapter of “The API Product” section of this book.
Let's return to the coffee example, and imagine we implemented the following scheme:
* Optimistic concurrency control (through, let's say, the id of the last user's order)

View File

@ -395,7 +395,7 @@ class SearchBoxComposer
'offerSelect', this.selectOffer
);
this.offerPanelComponent.events.on(
'action', this.performAction
'action', this.performAction
);
}
}

View File

@ -45,7 +45,7 @@ B2B services are a special case. As B2B service providers benefit from offering
* Internal customers employ a quite specific technological stack, and the API is poorly optimized to work with other programming languages / operating systems / frameworks.
* For external customers, the learning curve will be pretty steep as they can't take a look at the source code or talk to the API developers directly, unlike internal customers who are much more familiar with the API concepts.
* Documentation often covers only some subset of use cases needed by internal customers.
* The API services ecosystem which we will describe in the “[API Services Range](#api-product-range)” chapter usually doesn't exist.
* The API services ecosystem which we will describe in the “[API Services Lineup](#api-product-lineup)” chapter usually doesn't exist.
* Any resources spent are directed towards covering internal customer needs first. It means the following:
* API development plans are totally opaque to partners and sometimes look absurd with obvious problems being neglected for years.
* Technical support of external customers is financed on leftovers.

View File

@ -51,4 +51,4 @@ This fact greatly affects everything we discussed previously (except for, perhap
* A significant share of inquiries to your customer support service will be generated by the first category of developers. It is much harder for amateurs or beginners to find answers to their questions by themselves, and they will reach out to you for assistance.
* At the same time, the second category is much more sensitive to the quality of both the product and customer support, and fulfilling their requests might be non-trivial.
Finally, it is almost impossible to create an API that will suit both amateur and professional developers well within a single product. The former need maximum simplicity in implementing basic use cases, while the latter seek the ability to adapt the API to match their technological stack and development paradigms and the problems they solve usually require deep customization. We will discuss this matter in the “[API Services Range](#api-product-range)” chapter.
Finally, it is almost impossible to create an API that will suit both amateur and professional developers well within a single product. The former need maximum simplicity in implementing basic use cases, while the latter seek the ability to adapt the API to match their technological stack and development paradigms and the problems they solve usually require deep customization. We will discuss this matter in the “[API Services Lineup](#api-product-lineup)” chapter.

View File

@ -1,6 +1,6 @@
### [An API Services Range][api-product-range]
### [An API Services Lineup][api-product-lineup]
The important rule of API product management that any major API provider will soon learn is this: don't just ship one specific API; there is always room for a range of products, and this range is two-dimensional.
The important rule of API product management that any major API provider will soon learn is this: don't just ship one specific API; there is always room for a lineup of products, and this lineup is two-dimensional.
#### Horizontal Scaling of API Services
@ -25,13 +25,13 @@ However, it often makes sense to provide multiple API services that feature the
Ultimately, we will end up with the concept of a meta-API where these high-level components have their own API built on top of the basic API.
The important advantage of having a range of APIs is not only about adapting it to the developer's capabilities but also about increasing the level of control you have over the code that partners embed into their applications:
The important advantage of having a lineup of APIs is not only about adapting it to the developer's capabilities but also about increasing the level of control you have over the code that partners embed into their applications:
1. Applications that use physical interfaces are out of your control. For example, you can't force switching to newer versions of the platform or, let's say, add commercial inlets to them.
2. Applications that operate base APIs will allow you to manipulate underlying abstraction levels — move to newer technologies or alter the way search results are presented to an end user.
3. SDKs, especially those providing UI components, provide a higher degree of control over the look and feel of partners' applications, which allows you to evolve the UI by adding new interactive elements and enriching the functionality of existing ones. [For example, if our coffee SDK contains a map of coffee shops, nothing could stop us from making map objects clickable in the next API version or highlighting paid offerings.]
4. Code generation makes it possible to manipulate the desired form of integrations. For example, if our KPI is the number of searches performed through the API, we can alter the generated code so that the search panel will be displayed in the most convenient position in the application. As partners who use code-generation services rarely make any changes to the resulting code, this will help us reach our goals.
5. Finally, ready-to-use components and widgets are under your full control, and you can experiment with the functionality exposed through them in partners' applications as if it were your own services. (However, this doesn't automatically mean that you can draw profits from having this control. For example, if you allow hotlinking pictures by their direct URL, your control over this integration is rather negligible, so it's generally better to provide integration options that allow for more control over the functionality in partners' applications.)
**NB**: While developing a “vertical” range of APIs, it is crucial to follow the principles discussed in the “[On the Waterline of the Iceberg](#back-compat-iceberg-waterline)” chapter. You will only be able to manipulate the content and behavior of a widget if developers cannot “escape the sandbox,” meaning they do not have direct access to low-level objects encapsulated within the widget.
**NB**: While developing a “vertical” lineup of APIs, it is crucial to follow the principles discussed in the “[On the Waterline of the Iceberg](#back-compat-iceberg-waterline)” chapter. You will only be able to manipulate the content and behavior of a widget if developers cannot “escape the sandbox,” meaning they do not have direct access to low-level objects encapsulated within the widget.
In general, your aim should be to have each partner use the API services in a manner that maximizes your profit as an API vendor. When a partner only needs a typical solution, you would benefit from making them use widgets as they are under your direct control. This will help ease the API version fragmentation problem and allow for experimentation to reach your KPIs. When the partner possesses expertise in the subject area and develops a unique service on top of your API, you would benefit from allowing full freedom in customizing the integration. This way, they can cover specific market niches and enjoy the advantage of offering more flexibility compared to services using competing APIs.

View File

@ -396,7 +396,7 @@ class SearchBoxComposer
'offerSelect', this.selectOffer
);
this.offerPanelComponent.events.on(
'action', this.performAction
'action', this.performAction
);
}

View File

@ -153,8 +153,10 @@ export const templates = {
src="assets/header.jpg"
alt="${l10n.author}. ${l10n.title}"
/><br />
<h1>${l10n.author}<br/><span class="title">${l10n.title}</span></h1>
<h2>${l10n.landing.subTitle}</h2>
<header>
<h1>${l10n.author}<br/><span class="title">${l10n.title}</span></h1>
<h2>${l10n.landing.subTitle}</h2>
</header>
<br />${l10n.landing.subscribeOn} ${l10n.landing.updates
.map(
(source) =>
@ -202,13 +204,13 @@ export const templates = {
} <a href="${link()}">${l10n.landing.readOnline}</a>.
</p>
<h3>${l10n.toc}</h3>
<ul>${structure.sections
<ul class="toc">${structure.sections
.map(
(section) => `<li>
<h4><a href="${link(section.anchor)}">${section.title}</a></h4>
${
section.chapters.length
? `<ul>
? `<ul class="section">
${section.chapters
.map(
(chapter) =>
@ -225,7 +227,7 @@ export const templates = {
.join('\n')}
<li>
<h4>${l10n.landing.liveExamples}</h3>
<ul>${examples
<ul class="section">${examples
.map(
({ name, path }) => `<li><a href="${path}">${name}</a></li>`
)