1
0
mirror of https://github.com/twirl/The-API-Book.git synced 2025-08-10 21:51:42 +02:00

stylistic fixes

This commit is contained in:
Sergey Konstantinov
2020-12-07 21:54:50 +03:00
parent 45346a5648
commit e638172a36
6 changed files with 40 additions and 40 deletions

View File

@@ -137,10 +137,10 @@ h4, h5 {
<p>Более подробно о жизненном цикле API и политиках выпуска новых версий будет рассказано в разделе II.</p><div class="page-break"></div><h3 id="5">Глава 5. О версионировании</h3>
<p>Здесь и далее мы будем придерживаться принципов версионирования <a href="https://semver.org/">semver</a>:</p>
<ol>
<li>Версия API задаётся тремя цифрами, вида <code>1.2.3</code></li>
<li>Первая цифра (мажорная версия) увеличивается при обратно несовместимых изменениях в API</li>
<li>Вторая цифра (минорная версия) увеличивается при добавлении новой функциональности с сохранением обратной совместимости</li>
<li>Третья цифра (патч) увеличивается при выпуске новых версий, содержащих только исправление ошибок</li>
<li>Версия API задаётся тремя цифрами, вида <code>1.2.3</code>.</li>
<li>Первая цифра (мажорная версия) увеличивается при обратно несовместимых изменениях в API.</li>
<li>Вторая цифра (минорная версия) увеличивается при добавлении новой функциональности с сохранением обратной совместимости.</li>
<li>Третья цифра (патч) увеличивается при выпуске новых версий, содержащих только исправление ошибок.</li>
</ol>
<p>Выражения «мажорная версия API» и «версия API, содержащая обратно несовместимые изменения функциональности» тем самым следует считать эквивалентными.</p>
<p>Более подробно о политиках версионирования будет рассказано в разделе II. В разделе I мы ограничимся лишь указанием версии API в формате <code>v1</code>, <code>v2</code>, etc.</p><div class="page-break"></div><h3 id="6">Глава 6. Условные обозначения и терминология</h3>
@@ -168,10 +168,10 @@ Cache-Control: no-cache
<p>Её следует читать так:</p>
<ul>
<li>выполняется POST-запрос к ресурсу <code>/v1/bucket/{id}/some-resource</code>, где <code>{id}</code> заменяется на некоторый идентификатор <code>bucket</code>-а (при отсутствии уточнений подстановки вида <code>{something}</code> следует относить к ближайшему термину слева);</li>
<li>запрос сопровождается (помимо стандартных заголовков, которые мы опускаем) дополнительным заголовком X-Idempotency-Token;</li>
<li>запрос сопровождается (помимо стандартных заголовков, которые мы опускаем) дополнительным заголовком <code>X-Idempotency-Token</code>;</li>
<li>фразы в угловых скобках (<code>&lt;токен идемпотентности&gt;</code>) описывают семантику значения сущности (поля, заголовка, параметра);</li>
<li>в качестве тела запроса передаётся JSON, содержащий поле <code>some_parameter</code> со значением <code>value</code> и ещё какие-то поля, которые для краткости опущены (что показано многоточием);</li>
<li>в ответ (индицируется стрелкой <code></code>) сервер возвращает статус 404 Not Found; статус может быть опущен (отсутствие статуса следует трактовать как <code>200 OK</code>);</li>
<li>в ответ (индицируется стрелкой <code></code>) сервер возвращает статус <code>404 Not Found</code>; статус может быть опущен (отсутствие статуса следует трактовать как <code>200 OK</code>);</li>
<li>в ответе также могут находиться дополнительные заголовки, на которые мы обращаем внимание;</li>
<li>телом ответа является JSON, состоящий из единственного поля <code>error_message</code>; отсутствие значения поля означает, что его значением является именно то, что в этом поле и ожидается — в данном случае какое-то сообщение об ошибке.</li>
</ul>
@@ -196,7 +196,7 @@ Cache-Control: no-cache
<li><p>Какую проблему <em>мы</em> решаем? Действительно ли решение этой проблемы находится в нашей компетенции? Действительно ли мы находимся в той позиции, чтобы решить эту проблему?</p></li>
<li><p>Какую проблему мы <em>решаем</em>? Правда ли, что решение, которое мы предлагаем, действительно решает проблему? Не создаём ли мы на её месте другую проблему, более сложную?</p></li>
</ol>
<p>Итак, предположим, что мы хотим предоставить API автоматического заказа кофе в городских кофейнях. Попробуем применить к ней этот принцип.</p>
<p>Итак, предположим, что мы хотим предоставить API автоматического заказа кофе в городских кофейнях. Попробуем применить к нему этот принцип.</p>
<ol>
<li><p>Зачем кому-то может потребоваться API для приготовления кофе? В чем неудобство заказа кофе через интерфейс, человек-человек или человек-машина? Зачем нужна возможность заказа машина-машина?</p>
<ul>
@@ -212,14 +212,14 @@ Cache-Control: no-cache
<p>На все эти вопросы, в общем случае, простого ответа нет. В идеале ответы на эти вопросы должны даваться с цифрами в руках. Сколько конкретно времени тратится неоптимально, и какого значения мы рассчитываем добиться, располагая какой плотностью кофе-машин? Заметим также, что в реальной жизни просчитать такого рода цифры можно в основном для проектов, которые пытаются влезть на уже устоявшийся рынок; если вы пытаетесь сделать что-то новое, то, вероятно, вам придётся ориентироваться в основном на свою интуицию.</p>
<h4 id="api">Почему API?</h4>
<p>Т.к. наша книга посвящена не просто разработке программного обеспечения, а разработке API, то на все эти вопросы мы должны взглянуть под другим ракурсом: а почему для решения этих задач требуется именно API, а не просто программное обеспечение? В нашем вымышленном примере мы должны спросить себя: зачем нам нужно предоставлять сервис для других разработчиков, чтобы они могли готовить кофе своим клиентам, а не сделать своё приложение для конечного потребителя?</p>
<p>Иными словами, должна иметься веская причина, по которой два домена разработки ПО должны быть разделены: есть оператор(ы), предоставляющий API; есть оператор(ы), предоставляющий сервисы пользователям. Их интересы в чем-то различны настолько, что объединение этих двух ролей в одном лице нежелательно. Более подробно мы изложим причины и мотивации делать именно API в разделе II.</p>
<p>Иными словами, должна иметься веская причина, по которой два домена разработки ПО должны быть разделены: есть оператор(ы), предоставляющий API; есть оператор(ы), предоставляющий сервисы пользователям. Их интересы в чем-то различны настолько, что объединение этих двух ролей в одном лице нежелательно. Более подробно мы изложим причины и мотивации делать именно API в разделе III.</p>
<p>Заметим также следующее: вы должны браться делать API тогда и только тогда, когда в ответе на второй вопрос написали «потому что в этом состоит наша экспертиза». Разрабатывая API вы занимаетесь некоторой мета-разработкой: вы пишете ПО для того, чтобы другие могли разрабатывать ПО для решения задачи пользователя. Не обладая экспертизой в обоих этих доменах (API и конечные продукты) написать хорошее API сложно.</p>
<p>Для нашего умозрительного примера предположим, что в недалеком будущем произошло разделение рынка кофе на две группы игроков: одни предоставляют само железо, кофейные аппараты, а другие имеют доступ к потребителю — примерно как это произошло, например, с рынком авиабилетов, где есть собственно авиакомпании, осуществляющие перевозку, и сервисы планирования путешествий, где люди выбирают варианты перелётов. Мы хотим агрегировать доступ к железу, чтобы владельцы приложений могли встраивать заказ кофе.</p>
<h4 id="">Что и как</h4>
<p>Закончив со всеми теоретическими упражнениями, мы должны перейти непосредственно к дизайну и разработке API, имея понимание по двум пунктам:</p>
<p>Закончив со всеми теоретическими упражнениями, мы должны перейти непосредственно к дизайну и разработке API, имея понимание по двум пунктам.</p>
<ol>
<li>Что конкретно мы делаем</li>
<li>Как мы это делаем</li>
<li>Что конкретно мы делаем.</li>
<li>Как мы это делаем.</li>
</ol>
<p>В случае нашего кофе-примера мы:</p>
<ol>
@@ -855,25 +855,25 @@ POST /v1/orders/statistics/aggregate
<p>Два важных следствия:</p>
<p><strong>1.1.</strong> Если операция модифицирующая, это должно быть очевидно из сигнатуры. В частности, не может быть модифицирующих операций за <code>GET</code>.</p>
<p><strong>1.2.</strong> Если в номенклатуре вашего API есть как синхронные операции, так и асинхронные, то (а)синхронность должна быть очевидна из сигнатур, <strong>либо</strong> должна существовать конвенция именования, позволяющая отличать синхронные операции от асинхронных.</p>
<h4 id="2">2. Использованные стандарты указывайте явно</h4>
<h4 id="2">2. Указывайте использованные стандарты</h4>
<p>К сожалению, человечество не в состоянии договориться о таких простейших вещах, как «с какого дня начинается неделя», что уж говорить о каких-то более сложных стандартах.</p>
<p>Поэтому <em>всегда</em> указывайте, по какому конкретно стандарту вы отдаёте те или иные величины. Исключения возможны только там, где вы на 100% уверены, что в мире существует только один стандарт для этой сущности, и всё население земного шара о нём в курсе.</p>
<p><strong>Плохо</strong>: <code>"date":"11/12/2020"</code> — стандартов записи дат существует огромное количество, плюс из этой записи невозможно даже понять, что здесь число, а что месяц.</p>
<p><strong>Хорошо</strong>: <code>"iso_date":"2020-11-12"</code>.</p>
<p><strong>Плохо</strong>: <code>"duration":5000</code> — пять тысяч чего?</p>
<p><strong>Плохо</strong>: <code>"date": "11/12/2020"</code> — стандартов записи дат существует огромное количество, плюс из этой записи невозможно даже понять, что здесь число, а что месяц.</p>
<p><strong>Хорошо</strong>: <code>"iso_date": "2020-11-12"</code>.</p>
<p><strong>Плохо</strong>: <code>"duration": 5000</code> — пять тысяч чего?</p>
<p><strong>Хорошо</strong>:<br />
<code>"duration_ms":5000</code><br />
<code>"duration_ms": 5000</code><br />
либо<br />
<code>"duration":"5000ms"</code><br />
<code>"duration": "5000ms"</code><br />
либо<br />
<code>"duration":{"unit":"ms","value":5000}</code>.</p>
<code>"duration": {"unit": "ms", "value": 5000}</code>.</p>
<p>Отдельное следствие из этого правила — денежные величины <em>всегда</em> должны сопровождаться указанием кода валюты.</p>
<p>Также следует отметить, что в некоторых областях ситуация со стандартами настолько плоха, что как ни сделай — кто-то останется недовольным. Классический пример такого рода — порядок географических координат ("широта-долгота" против "долгота-широта"). Здесь, увы, есть только один работающий метод борьбы с фрустрацией — «блокнот душевного спокойствия», который будет описан в разделе II.</p>
<h4 id="3">3. Сохраняйте точность дробных чисел</h4>
<p>Там, где это позволено протоколом, дробные числа с фиксированной запятой — такие, как денежные суммы, например — должны передаваться в виде специально предназначенных для этого объектов, например, Decimal или аналогичных.</p>
<p>Если в протоколе нет Decimal-типов (в частности, в JSON нет чисел с фиксированной запятой), следует либо привести к целому (путём домножения на указанный множитель), либо использовать строковый тип.</p>
<h4 id="4">4. Сущности должны именоваться конкретно</h4>
<p>Избегайте слов-«амёб» без определённой семантики, таких как get, apply, make. Сущности должны именоваться конкретно.</p>
<p>Избегайте одиночных слов-«амёб» без определённой семантики, таких как get, apply, make.</p>
<p><strong>Плохо</strong>: <code>user.get()</code> — неочевидно, что конкретно будет возвращено.</p>
<p><strong>Хорошо</strong>: <code>user.get_id()</code>.</p>
<h4 id="5">5. Не экономьте буквы</h4>
@@ -890,7 +890,7 @@ strpbrk (str1, str2)
— однако необходимость существования такого метода вообще вызывает сомнения, достаточно было бы иметь удобную функцию поиска подстроки с нужными параметрами. Аналогично сокращение <code>string</code> до <code>str</code> выглядит совершенно бессмысленным, но, увы, является устоявшимся для большого количества предметных областей.</p>
<h4 id="6">6. Тип поля должен быть ясен из его названия</h4>
<p>Если поле называется <code>recipe</code> — мы ожидаем, что его значением является сущность типа <code>Recipe</code>. Если поле называется <code>recipe_id</code> — мы ожидаем, что его значением является идентификатор, который мы сможем найти в составе сущности <code>Recipe</code>.</p>
<p>Сущности-массивы должны именоваться во множественном числе или собирательными выражениями — <code>objects</code>, <code>children</code>; если это невозможно (термин неисчисляемый), следует добавить префикс или постфикс, не оставляющий сомнений.</p>
<p>То же касается и примитивных типов. Сущности-массивы должны именоваться во множественном числе или собирательными выражениями — <code>objects</code>, <code>children</code>; если это невозможно (термин неисчисляемый), следует добавить префикс или постфикс, не оставляющий сомнений.</p>
<p><strong>Плохо</strong>: <code>GET /news</code> — неясно, будет ли получена какая-то конкретная новость или массив новостей.</p>
<p><strong>Хорошо</strong>: <code>GET /news-list</code>.</p>
<p>Аналогично, если ожидается булево значение, то из названия это должно быть очевидно, т.е. именование должно описывать некоторое качественное состояние, например, <code>is_ready</code>, <code>open_now</code>.</p>

Binary file not shown.

View File

@@ -2,10 +2,10 @@
Здесь и далее мы будем придерживаться принципов версионирования [semver](https://semver.org/):
1. Версия API задаётся тремя цифрами, вида `1.2.3`
2. Первая цифра (мажорная версия) увеличивается при обратно несовместимых изменениях в API
3. Вторая цифра (минорная версия) увеличивается при добавлении новой функциональности с сохранением обратной совместимости
4. Третья цифра (патч) увеличивается при выпуске новых версий, содержащих только исправление ошибок
1. Версия API задаётся тремя цифрами, вида `1.2.3`.
2. Первая цифра (мажорная версия) увеличивается при обратно несовместимых изменениях в API.
3. Вторая цифра (минорная версия) увеличивается при добавлении новой функциональности с сохранением обратной совместимости.
4. Третья цифра (патч) увеличивается при выпуске новых версий, содержащих только исправление ошибок.
Выражения «мажорная версия API» и «версия API, содержащая обратно несовместимые изменения функциональности» тем самым следует считать эквивалентными.

View File

@@ -28,10 +28,10 @@ Cache-Control: no-cache
Её следует читать так:
* выполняется POST-запрос к ресурсу `/v1/bucket/{id}/some-resource`, где `{id}` заменяется на некоторый идентификатор `bucket`-а (при отсутствии уточнений подстановки вида `{something}` следует относить к ближайшему термину слева);
* запрос сопровождается (помимо стандартных заголовков, которые мы опускаем) дополнительным заголовком X-Idempotency-Token;
* запрос сопровождается (помимо стандартных заголовков, которые мы опускаем) дополнительным заголовком `X-Idempotency-Token`;
* фразы в угловых скобках (`<токен идемпотентности>`) описывают семантику значения сущности (поля, заголовка, параметра);
* в качестве тела запроса передаётся JSON, содержащий поле `some_parameter` со значением `value` и ещё какие-то поля, которые для краткости опущены (что показано многоточием);
* в ответ (индицируется стрелкой `→`) сервер возвращает статус 404 Not Found; статус может быть опущен (отсутствие статуса следует трактовать как `200 OK`);
* в ответ (индицируется стрелкой `→`) сервер возвращает статус `404 Not Found`; статус может быть опущен (отсутствие статуса следует трактовать как `200 OK`);
* в ответе также могут находиться дополнительные заголовки, на которые мы обращаем внимание;
* телом ответа является JSON, состоящий из единственного поля `error_message`; отсутствие значения поля означает, что его значением является именно то, что в этом поле и ожидается — в данном случае какое-то сообщение об ошибке.

View File

@@ -10,7 +10,7 @@
4. Какую проблему мы _решаем_? Правда ли, что решение, которое мы предлагаем, действительно решает проблему? Не создаём ли мы на её месте другую проблему, более сложную?
Итак, предположим, что мы хотим предоставить API автоматического заказа кофе в городских кофейнях. Попробуем применить к ней этот принцип.
Итак, предположим, что мы хотим предоставить API автоматического заказа кофе в городских кофейнях. Попробуем применить к нему этот принцип.
1. Зачем кому-то может потребоваться API для приготовления кофе? В чем неудобство заказа кофе через интерфейс, человек-человек или человек-машина? Зачем нужна возможность заказа машина-машина?
* Возможно, мы хотим решить проблему выбора и знания? Чтобы человек наиболее полно знал о доступных ему здесь и сейчас опциях.
@@ -33,7 +33,7 @@
Т.к. наша книга посвящена не просто разработке программного обеспечения, а разработке API, то на все эти вопросы мы должны взглянуть под другим ракурсом: а почему для решения этих задач требуется именно API, а не просто программное обеспечение? В нашем вымышленном примере мы должны спросить себя: зачем нам нужно предоставлять сервис для других разработчиков, чтобы они могли готовить кофе своим клиентам, а не сделать своё приложение для конечного потребителя?
Иными словами, должна иметься веская причина, по которой два домена разработки ПО должны быть разделены: есть оператор(ы), предоставляющий API; есть оператор(ы), предоставляющий сервисы пользователям. Их интересы в чем-то различны настолько, что объединение этих двух ролей в одном лице нежелательно. Более подробно мы изложим причины и мотивации делать именно API в разделе II.
Иными словами, должна иметься веская причина, по которой два домена разработки ПО должны быть разделены: есть оператор(ы), предоставляющий API; есть оператор(ы), предоставляющий сервисы пользователям. Их интересы в чем-то различны настолько, что объединение этих двух ролей в одном лице нежелательно. Более подробно мы изложим причины и мотивации делать именно API в разделе III.
Заметим также следующее: вы должны браться делать API тогда и только тогда, когда в ответе на второй вопрос написали «потому что в этом состоит наша экспертиза». Разрабатывая API вы занимаетесь некоторой мета-разработкой: вы пишете ПО для того, чтобы другие могли разрабатывать ПО для решения задачи пользователя. Не обладая экспертизой в обоих этих доменах (API и конечные продукты) написать хорошее API сложно.
@@ -41,10 +41,10 @@
#### Что и как
Закончив со всеми теоретическими упражнениями, мы должны перейти непосредственно к дизайну и разработке API, имея понимание по двум пунктам:
Закончив со всеми теоретическими упражнениями, мы должны перейти непосредственно к дизайну и разработке API, имея понимание по двум пунктам.
1. Что конкретно мы делаем
2. Как мы это делаем
1. Что конкретно мы делаем.
2. Как мы это делаем.
В случае нашего кофе-примера мы:

View File

@@ -53,24 +53,24 @@ POST /v1/orders/statistics/aggregate
**1.2.** Если в номенклатуре вашего API есть как синхронные операции, так и асинхронные, то (а)синхронность должна быть очевидна из сигнатур, **либо** должна существовать конвенция именования, позволяющая отличать синхронные операции от асинхронных.
#### 2. Использованные стандарты указывайте явно
#### 2. Указывайте использованные стандарты
К сожалению, человечество не в состоянии договориться о таких простейших вещах, как «с какого дня начинается неделя», что уж говорить о каких-то более сложных стандартах.
Поэтому _всегда_ указывайте, по какому конкретно стандарту вы отдаёте те или иные величины. Исключения возможны только там, где вы на 100% уверены, что в мире существует только один стандарт для этой сущности, и всё население земного шара о нём в курсе.
**Плохо**: `"date":"11/12/2020"` — стандартов записи дат существует огромное количество, плюс из этой записи невозможно даже понять, что здесь число, а что месяц.
**Плохо**: `"date": "11/12/2020"` — стандартов записи дат существует огромное количество, плюс из этой записи невозможно даже понять, что здесь число, а что месяц.
**Хорошо**: `"iso_date":"2020-11-12"`.
**Хорошо**: `"iso_date": "2020-11-12"`.
**Плохо**: `"duration":5000` — пять тысяч чего?
**Плохо**: `"duration": 5000` — пять тысяч чего?
**Хорошо**:
`"duration_ms":5000`
`"duration_ms": 5000`
либо
`"duration":"5000ms"`
`"duration": "5000ms"`
либо
`"duration":{"unit":"ms","value":5000}`.
`"duration": {"unit": "ms", "value": 5000}`.
Отдельное следствие из этого правила — денежные величины *всегда* должны сопровождаться указанием кода валюты.
@@ -84,7 +84,7 @@ POST /v1/orders/statistics/aggregate
#### 4. Сущности должны именоваться конкретно
Избегайте слов-«амёб» без определённой семантики, таких как get, apply, make. Сущности должны именоваться конкретно.
Избегайте одиночных слов-«амёб» без определённой семантики, таких как get, apply, make.
**Плохо**: `user.get()` — неочевидно, что конкретно будет возвращено.
@@ -113,7 +113,7 @@ strpbrk (str1, str2)
Если поле называется `recipe` — мы ожидаем, что его значением является сущность типа `Recipe`. Если поле называется `recipe_id` — мы ожидаем, что его значением является идентификатор, который мы сможем найти в составе сущности `Recipe`.
Сущности-массивы должны именоваться во множественном числе или собирательными выражениями — `objects`, `children`; если это невозможно (термин неисчисляемый), следует добавить префикс или постфикс, не оставляющий сомнений.
То же касается и примитивных типов. Сущности-массивы должны именоваться во множественном числе или собирательными выражениями — `objects`, `children`; если это невозможно (термин неисчисляемый), следует добавить префикс или постфикс, не оставляющий сомнений.
**Плохо**: `GET /news` — неясно, будет ли получена какая-то конкретная новость или массив новостей.