mirror of
https://github.com/twirl/The-API-Book.git
synced 2025-02-04 18:21:47 +02:00
Fresh build
This commit is contained in:
parent
14f0518793
commit
4d91973220
BIN
docs/API.en.epub
BIN
docs/API.en.epub
Binary file not shown.
@ -2623,7 +2623,7 @@ PUT /partners/{id}/coffee-machines
|
||||
"format"
|
||||
},
|
||||
"program_state_endpoint",
|
||||
"program_stop_endpoint"
|
||||
"program_cancel_endpoint"
|
||||
}
|
||||
}, …]
|
||||
}
|
||||
@ -2841,7 +2841,25 @@ ProgramContext.dispatch = (action) => {
|
||||
<li>Often the requirements like ‘the <code>getEntity</code> function returns the value previously being set by the <code>setEntity</code> function’ appear to be too trivial to both developers and QA engineers to have a proper test. But it's quite possible to make a mistake there, and we have actually encountered such bugs several times.</li>
|
||||
<li>The interface abstraction principle must be tested either. In theory, you might have considered each entity as an implementation of some interface; in practice, it might happen that you have forgotten something, and alternative implementations aren't actually possible. For testing purposes, it's highly desirable to have an alternative realization, even a provisional one.</li>
|
||||
</ol>
|
||||
<h5><a href="#chapter-19-paragraph-3" id="chapter-19-paragraph-3" class="anchor">3. Implement your API functionality atop of public interfaces</a></h5>
|
||||
<h5><a href="#chapter-19-paragraph-3" id="chapter-19-paragraph-3" class="anchor">3. Isolate the dependencies</a></h5>
|
||||
<p>In the case of a gateway API that provides access to some underlying API or aggregates several APIs behind a single façade, there is a strong temptation to proxy the original interface as is, thus not introducing any changes to it and making a life much simpler by sparing an effort needed to implement the weak-coupled interaction between services. For example, while developing program execution interfaces as described in the <a href="#chapter-9">Chapter 9</a> we might have taken the existing first-kind coffee-machine API as a role model and provided it in our API by just proxying the requests and responses as is. Doing so is highly undesirable because of several reasons:</p>
|
||||
<ul>
|
||||
<li>usually, you have no guarantees that the partner will maintain backwards compatibility or at least keep new versions more or less conceptually akin to the older ones;</li>
|
||||
<li>nay partner's problems will automatically ricochet into your customers.</li>
|
||||
</ul>
|
||||
<p>The best practice is quite the opposite: isolate the third-party API usage, e.g. develop an abstraction level that will allow for:</p>
|
||||
<ul>
|
||||
<li>keeping backwards compatibility intact because of extension capabilities incorporated in the API design;</li>
|
||||
<li>negating partner's problems by the technical means:
|
||||
<ul>
|
||||
<li>limiting the partner's API usage in case of an unpredicted surge in your API usage;</li>
|
||||
<li>implementing the retry policies or other methods of recovering after failures;</li>
|
||||
<li>caching some data and states to have the ability to provide some (at least partial) functionality even if the partner's API is fully unreachable;</li>
|
||||
<li>finally, configuring an automatical fallback to another partner or alternative API.</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<h5><a href="#chapter-19-paragraph-4" id="chapter-19-paragraph-4" class="anchor">4. Implement your API functionality atop of public interfaces</a></h5>
|
||||
<p>There is an antipattern that occurs frequently: API developers use some internal closed implementations of some methods which exist in the public API. It happens because of two reasons:</p>
|
||||
<ul>
|
||||
<li>often the public API is just an addition to the existing specialized software, and the functionality, exposed via the API, isn't being ported back to the closed part of the project, or the public API developers simply don't know the corresponding internal functionality exists;</li>
|
||||
@ -2849,7 +2867,7 @@ ProgramContext.dispatch = (action) => {
|
||||
</ul>
|
||||
<p>There are obvious local problems with this approach (like the inconsistency in functions' behavior, or the bugs which were not found while testing the code), but also a bigger one: your API might be simply unusable if a developer tries any non-mainstream approach, because of performance issues, bugs, instability, etc.</p>
|
||||
<p><strong>NB</strong>. The perfect example of avoiding this anti-pattern is compiler development; usually, the next compiler's version is compiled with the previous compiler's version.</p>
|
||||
<h5><a href="#chapter-19-paragraph-4" id="chapter-19-paragraph-4" class="anchor">4. Keep a notepad</a></h5>
|
||||
<h5><a href="#chapter-19-paragraph-5" id="chapter-19-paragraph-5" class="anchor">5. Keep a notepad</a></h5>
|
||||
<p>Whatever tips and tricks that were described in the previous chapters you use, it's often quite probable that you can't do <em>anything</em> to prevent the API inconsistencies start piling up. It's possible to reduce the speed of this stockpiling, foresee some problems, and have some interface durability reserved for future use. But one can't foresee <em>everything</em>. At this stage, many developers tend to make some rash decisions, e.g. releasing a backwards-incompatible minor version to fix some design flaws.</p>
|
||||
<p>We highly recommend never doing that. Remember that the API is a multiplier of your mistakes either. What we recommend is to keep a serenity notepad — to fix the lessons learned, and not to forget to apply this knowledge when the major API version is released.</p><div class="page-break"></div>
|
||||
</div></article>
|
||||
|
BIN
docs/API.en.pdf
BIN
docs/API.en.pdf
Binary file not shown.
BIN
docs/API.ru.epub
BIN
docs/API.ru.epub
Binary file not shown.
@ -2345,7 +2345,7 @@ object.observe('widthchange', observerFunction);
|
||||
<p>Представьте, что в один прекрасный день вы заводите специальный номер телефона, по которому клиент может позвонить в колл-центр и отменить заказ. Вы даже можете сделать это <em>технически</em> обратно-совместимым образом, добавив новых необязательных полей в сущность «заказ». Но конечный потребитель может просто <em>знать</em> нужный номер телефона, и позвонить по нему, даже если приложение его не показало. При этом код бизнес-аналитика партнера всё так же может сломаться или начать показывать погоду на Марсе, т.к. он был написан когда-то, ничего не зная о возможности отменить заказ, сделанный в приложении партнера, каким-то иным образом, не через самого партнёра же.</p>
|
||||
<p><em>Технически</em> корректным решением в данной ситуации могло бы быть добавление параметра «разрешено отменять через колл-центр» в функцию создания заказа — и, соответственно, запрет операторам колл-центра отменять заказы, если флаг не был указан при их создании. Но это в свою очередь плохое решение <em>с точки зрения продукта</em>. «Хорошее» решение здесь только одно — изначально предусмотреть возможность внешних отмен в API; если же вы её не предвидели — остаётся воспользоваться «блокнотом душевного спокойствия», речь о котором пойдёт в последней главе настоящего раздела.</p><div class="page-break"></div><h3><a href="#chapter-15" class="anchor" id="chapter-15">Глава 15. Расширение через абстрагирование</a></h3>
|
||||
<p>В предыдущих разделах мы старались приводить теоретические правила и иллюстрировать их на практических примерах. Однако понимание принципов проектирования API, устойчивого к изменениям, как ничто другое требует прежде всего практики. Знание о том, куда стоит «постелить соломку» — оно во многом «сын ошибок трудных». Нельзя предусмотреть всего — но можно выработать необходимый уровень технической интуиции.</p>
|
||||
<p>Поэтому в этом разделе мы поступим следующим образом: возьмём наше <a href="#chapter-12">модельный API</a> из предыдущего раздела, и проверим его на устойчивость в каждой возможной точке — проведём некоторый «вариационный анализ» наших интерфейсов. Ещё более конкретно — к каждой сущности мы подойдём с вопросом «что, если?» — что, если нам потребуется предоставить партнерам возможность написать свою независимую реализацию этого фрагмента логики.</p>
|
||||
<p>Поэтому в этом разделе мы поступим следующим образом: возьмём наш <a href="#chapter-12">модельный API</a> из предыдущего раздела, и проверим его на устойчивость в каждой возможной точке — проведём некоторый «вариационный анализ» наших интерфейсов. Ещё более конкретно — к каждой сущности мы подойдём с вопросом «что, если?» — что, если нам потребуется предоставить партнерам возможность написать свою независимую реализацию этого фрагмента логики.</p>
|
||||
<p><strong>NB</strong>. В рассматриваемых нами примерах мы будем выстраивать интерфейсы так, чтобы связывание разных сущностей происходило динамически в реальном времени; на практике такие интеграции будут делаться на стороне сервера путём написания ad hoc кода и формирования конкретных договорённостей с конкретным клиентом, однако мы для целей обучения специально будем идти более сложным и абстрактным путём. Динамическое связывание в реальном времени применимо скорее к сложным программным конструктам типа API операционных систем или встраиваемых библиотек; приводить обучающие примеры на основе систем подобной сложности было бы, однако, чересчур затруднительно.</p>
|
||||
<p>Начнём с базового интерфейса. Предположим, что мы пока что вообще не раскрывали никакой функциональности помимо поиска предложений и заказа, т.е. мы предоставляем API из двух методов — <code>POST /offers/search</code> и <code>POST /orders</code>.</p>
|
||||
<p>Сделаем следующий логический шаг и предположим, что партнёры захотят динамически подключать к нашей платформе свои собственные кофе машины с каким-то новым API. Для этого нам будет необходимо договориться о формате обратного вызова, каким образом мы будем вызывать API партнёра, и предоставить два новых эндпойта для:</p>
|
||||
@ -2625,7 +2625,7 @@ PUT /v1/api-types/{api_type}
|
||||
"format"
|
||||
},
|
||||
"program_state_endpoint",
|
||||
"program_stop_endpoint"
|
||||
"program_cancel_endpoint"
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
@ -2842,7 +2842,25 @@ ProgramContext.dispatch = (action) => {
|
||||
<li>Часто требования вида «функция <code>getEntity</code> возвращает значение, установленное вызовом функции <code>setEntity</code>» кажутся и разработчикам, и QA-инженерам самоочевидными и не проверяются. Между тем допустить ошибку в их реализации очень даже возможно, мы встречались с такими случаями на практике.</li>
|
||||
<li>Принцип абстрагирования интерфейсов тоже необходимо проверять. В теории вы может быть и рассматриваете каждую сущность как конкретную имплементацию абстрактного интерфейса — но на практике может оказаться, что вы чего-то не учли и ваш абстрактный интерфейс на деле невозможен. Для целей тестирования очень желательно иметь пусть условную, но отличную от базовой реализацию каждого интерфейса.</li>
|
||||
</ol>
|
||||
<h5><a href="#chapter-19-paragraph-3" id="chapter-19-paragraph-3" class="anchor">3. Реализуйте функциональность своего API поверх публичных интерфейсов</a></h5>
|
||||
<h5><a href="#chapter-19-paragraph-3" id="chapter-19-paragraph-3" class="anchor">3. Изолируйте зависимости</a></h5>
|
||||
<p>В случае, если API является гейтвеем, предоставляющим доступ к какому-то нижележащему API или агрегирующим несколько различных API за одним фасадом, велик соблазн предоставить оригинальный интерфейс as is, не внося в него изменений и не усложняя себя жизнь разработкой слабо связанного взаимодействия. Например, разрабатывая интерфейс для запуска программ, описанный в <a href="#chapter-9">главе 9</a>, мы могли бы взять за основу интерфейс кофе-машин первого типа и предоставить его в виде API, проксируя запросы и ответы как есть. Делать так ни в коем случае нельзя по нескольким причинам:</p>
|
||||
<ul>
|
||||
<li>как правило, у вас нет никаких гарантий, что партнёр будет поддерживать свой API в обратно-совместимом или хотя бы концептуально похожем виде;</li>
|
||||
<li>любые проблемы партнёра будут автоматически отражаться на ваших клиентах.</li>
|
||||
</ul>
|
||||
<p>Напротив, хорошей практикой будет изолировать использование API третьей стороны, т.е. разработать программную обвязку, которая позволит:</p>
|
||||
<ul>
|
||||
<li>сохранять обратную совместимость за счёт правильно подобранных точек расширения;</li>
|
||||
<li>нивелировать проблемы партнёра техническими средствами:
|
||||
<ul>
|
||||
<li>ограничивать нагрузку на API партнёра в случае непредвиденного всплеска нагрузки на ваш API;</li>
|
||||
<li>реализовывать политики перезапросов и иных способов восстановления после ошибок;</li>
|
||||
<li>кэшировать какие-то критичные данные и состояния, чтобы иметь возможность предоставлять какую-то (хотя бы частичную) функциональность, даже если API партнёра недоступен полностью;</li>
|
||||
<li>наконец, настроить автоматическое переключение на другого партнёра или альтернативное API.</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<h5><a href="#chapter-19-paragraph-4" id="chapter-19-paragraph-4" class="anchor">4. Реализуйте функциональность своего API поверх публичных интерфейсов</a></h5>
|
||||
<p>Часто можно увидеть антипаттерн: разработчики API используют внутренние непубличные реализации тех или иных методов взамен существующих в их API публичных. Это происходит по двум причинам:</p>
|
||||
<ul>
|
||||
<li>часто публичный API является лишь дополнением к более специализированному внутреннему ПО компании, и наработки, представленные в публичном API, не портируются обратно в непубличную часть проекта, или же разработчики публичного API попросту не знают о существовании аналогичных непубличных функций;</li>
|
||||
@ -2850,7 +2868,7 @@ ProgramContext.dispatch = (action) => {
|
||||
</ul>
|
||||
<p>Помимо очевидных частных проблем, вытекающих из такого подхода (неконсистентность поведения разных функций в API, не найденные при тестировании ошибки), здесь есть и одна глобальная: легко может оказаться, что вашим API попросту невозможно будет пользоваться, если сделать хоть один «шаг в сторону» — попытка воспользоваться любой нестандартной функциональностью может привести к проблемам производительности, многочисленным ошибкам, нестабильной работе и так далее.</p>
|
||||
<p><strong>NB</strong>. Идеальным примером строгого избегания данного антипаттерна следует признать разработку компиляторов — в этой сфере принято компилировать новую версию компилятора при помощи его же предыдущей версии.</p>
|
||||
<h5><a href="#chapter-19-paragraph-4" id="chapter-19-paragraph-4" class="anchor">4. Заведите блокнот</a></h5>
|
||||
<h5><a href="#chapter-19-paragraph-5" id="chapter-19-paragraph-5" class="anchor">5. Заведите блокнот</a></h5>
|
||||
<p>Несмотря на все приёмы и принципы, изложенные в настоящем разделе, с большой вероятностью вы <em>ничего</em> не сможете сделать с накапливающейся неконсистентностью вашего API. Да, можно замедлить скорость накопления, предусмотреть какие-то проблемы заранее, заложить запасы устойчивости — но предугадать <em>всё</em> решительно невозможно. На этом этапе многие разработчики склонны принимать скоропалительные решения — т.е. выпускать новые минорные версии API с явным или неявным нарушением обратной совместимости в целях исправления ошибок дизайна.</p>
|
||||
<p>Так делать мы крайне не рекомендуем — поскольку, напомним, API является помимо прочего и мультипликатором ваших ошибок. Что мы рекомендуем — так это завести блокнот душевного покоя, где вы будете записывать выученные уроки, которые потом нужно будет не забыть применить на практике при выпуске новой мажорной версии API.</p><div class="page-break"></div>
|
||||
</div></article>
|
||||
|
BIN
docs/API.ru.pdf
BIN
docs/API.ru.pdf
Binary file not shown.
@ -40,8 +40,8 @@ Access to the API might be unconditionally paid. However, hybrid models are more
|
||||
|
||||
B2B services are a special case. As B2B Service providers benefit from offering diverse capabilities to partners, and conversely partners often require maximum flexibility to cover their specific needs, providing an API might be the optimal solution for both. Large companies have their own IT departments are more frequently need APIs to connect to their internal systems and integrate into business processes. Also, the API provider company itself might play the role of such a B2B customer if its own products are built on top of the API.
|
||||
|
||||
**NB**: we absolutely condemn the practice of providing external API merely as a byproduct of the internal one without any additional product and technical efforts. The main problem of such APIs is that partners' interests are not taken into account, which leads to numerous problems.
|
||||
* the API doesn't cover integration use-cases well:
|
||||
**NB**: we absolutely condemn the practice of providing an external API merely as a byproduct of the internal one without any additional product and technical efforts. The main problem of such APIs is that partners' interests are not taken into account, which leads to numerous problems.
|
||||
* The API doesn't cover integration use-cases well:
|
||||
* internal customers usually employ quite a specific technological stack, and the API is poorly optimized to work with other programming languages / operating systems / frameworks;
|
||||
* internal customers are much more familiar with the API concepts; they might take a look at the source code or talk to the API developers directly, so the learning curve is pretty flat for them;
|
||||
* documentation only covers some subset of use-cases needed by internal customers;
|
||||
|
Loading…
x
Reference in New Issue
Block a user