From 8d0dd11ed8de0853a34019980883aa23e7f8f99d Mon Sep 17 00:00:00 2001 From: Sergey Konstantinov Date: Sun, 9 Apr 2023 17:38:54 +0300 Subject: [PATCH] grammar --- docs/API.en.html | 38 +++++++------- src/en/clean-copy/01-Introduction/01.md | 2 +- src/en/clean-copy/01-Introduction/02.md | 8 +-- src/en/clean-copy/01-Introduction/03.md | 4 +- src/en/clean-copy/01-Introduction/06.md | 2 +- .../02-Section I. The API Design/02.md | 10 ++-- .../02-Section I. The API Design/03.md | 51 +++++++++--------- .../02-Section I. The API Design/04.md | 40 +++++++------- .../02-Section I. The API Design/05.md | 50 +++++++++--------- .../01.md | 18 +++---- .../02.md | 10 ++-- .../03.md | 6 +-- .../04.md | 16 +++--- .../05.md | 10 ++-- .../06.md | 4 +- .../07.md | 2 +- .../04-Section III. The API Product/01.md | 14 ++--- .../04-Section III. The API Product/02.md | 52 +++++++++---------- .../04-Section III. The API Product/03.md | 16 +++--- .../04-Section III. The API Product/04.md | 10 ++-- .../04-Section III. The API Product/05.md | 2 +- .../04-Section III. The API Product/06.md | 10 ++-- .../04-Section III. The API Product/07.md | 12 ++--- .../04-Section III. The API Product/08.md | 18 +++---- .../04-Section III. The API Product/09.md | 20 +++---- .../04-Section III. The API Product/10.md | 6 +-- .../04-Section III. The API Product/11.md | 22 ++++---- .../04-Section III. The API Product/12.md | 6 +-- .../04-Section III. The API Product/13.md | 8 +-- .../04.md | 2 +- .../04-Раздел III. API как продукт/02.md | 2 +- 31 files changed, 237 insertions(+), 234 deletions(-) diff --git a/docs/API.en.html b/docs/API.en.html index 8552711..1a7ebcf 100644 --- a/docs/API.en.html +++ b/docs/API.en.html @@ -3420,50 +3420,50 @@ ProgramContext.dispatch = (action) => {

To properly develop the API product, you must be able to answer exactly this question: why your customers would prefer making some actions programmatically. It's not an idle question: out of this book's author's experience, the product owners' lack of expertise in working with APIs exactly is the largest problem of API product development.

-

End users don't interact with your API directly but through the application built upon it by software engineers acting on behalf of some company (and sometimes there is more than one engineer in between you and an end user). From this point of view, the API's target audience resembles a Maslow-like pyramid:

+

End users interact with your API indirectly, through applications built upon it by software engineers acting on behalf of some company (and sometimes there is more than one engineer in between you and an end user). From this point of view, the API's target audience resembles a Maslow-like pyramid:

The obvious conclusion of this model is that you must advertise the advantages of your API to developers. They will select the technology, and business owners will translate the concept to end users. If former or acting developers manage the API product, they often tend to evaluate the API market competitiveness in this dimension only and mainly channel the product promotion efforts to the developers' auditory.

-

'Stop!', the mindful reader must yell at this moment. The actual order of things is exactly the opposite:

+

“Stop!” the mindful reader must yell at this moment. “The actual order of things is exactly the opposite!”

So it turns out that customers are at the apex of the pyramid: it is customers you need to convince they need not any cup of coffee, but a cup of coffee brewed using our API (interesting question: how will we convey the knowledge which API works under the hood, and why customers should pay their money for it!); then business owners will set the task to integrate the API, and developers will have no other choice but to implement it (which, by the way, means that investing into API's readability and consistency is not that important).

The truth, of course, lies somewhere in between. In some markets and subject areas, it is developers who make decisions (i.e. which framework to choose); in other markets and areas, it might be business owners or customers. It also depends on the competitiveness of the market: introducing a new frontend framework does not meet any resistance while developing, let's say, a new mobile operating system requires million-dollar investments into promotions and strategic partnerships.

Here and after, we will describe some “averaged” situations, meaning that all three pyramid levels are important: customers choosing the product which fits their needs best, business owners seeking quality guarantees and lower development costs, as well as software engineers caring about the API capabilities and the convenience of working with it.

Chapter 21. The API Business Models 

-

Before we proceed to the API product management principles description, let us draw your attention to the issue of profits that companies providing APIs might extract from it. As we will demonstrate in the next chapters, this issue directly affects making product decisions and setting KPIs for the API team. [In brackets, we will provide examples of such models applicable to our coffee-machine API study.]

-

Developers = end users

-

The easiest and the most understandable case is that of providing a service for developers, with no end users involved. First of all, we talk about different software engineering tools: APIs of programming languages, frameworks, operating systems, UI libraries, game engines, etc; in other words, general-purpose interfaces. [In our coffee API case, it means the following: we've developed a library for ordering a cup of coffee, possibly furnished with UI components, and now selling it to coffeeshop chains owners whoever willing to buy it to ease the development of their own applications.] In this case, the answer to the “why have an API” question is self-evident.

+

Before we proceed to the API product management principles, let us draw your attention to the matter of profits that the API vendor company might extract from it. As we will demonstrate in the next chapters, this is not an idle question as it directly affects making product decisions and setting KPIs for the API team. In this chapter, we will enumerate the main API monetization models. [In brackets, we will provide examples of such models applicable to our coffee-machine API study.]

+
1. Developers = end users
+

The easiest and the most understandable case is that of providing a service for developers, with no end users involved. First of all, we talk about software engineering tools: APIs of programming languages, frameworks, operating systems, UI libraries, game engines, etc. — general-purpose interfaces, in other words. [In our coffee API case, it means the following: we've developed a library for ordering a cup of coffee, possibly furnished with UI components, and now selling it to coffeeshop chains owners whoever willing to buy it to ease the development of their own applications.] In this case, the answer to the “why have an API” question is self-evident.

There is also a plethora of monetizing techniques; in fact, we're just talking about monetizing software for developers.

  1. The framework / library / platform might be paid per se, e.g. distributed under a commercial license. Nowadays such models are becoming less and less popular with the rise of free and open source software but are still quite common.

  2. -

    The API may be licensed under an open license with some restrictions that might be lifted by buying an extended license. It might be either functional limitations (an inability to publish the app in the app store or an incapacity to build the app in the production mode) or usage restrictions (for example, an open license might be “contagious,” e.g. require publishing the derived code under the same license, or using the API for some purposes might be prohibited).

    +

    The API may be licensed under an open license with some restrictions that might be lifted by buying an extended license. It might be either functional limitations (an inability to publish the app in the app store or an incapacity to build the app in the production mode) or usage restrictions (for example, using the API for some purposes might be prohibited or an open license might be “contagious,” e.g. require publishing the derived code under the same license).

  3. -

    The API itself might be free, but the developer company might provide additional paid services (for example, consulting or integrating ones), or just sell the extended technical support.

    +

    The API itself might be free, but the API vendor might provide additional paid services (for example, consulting or integrating ones), or just sell the extended technical support.

  4. The API development might be sponsored (explicitly or implicitly) by the platform or operating system owners [in our coffee case — by the vendors of smart coffee machines] who are interested in providing a wide range of convenient tools for developers to work with the platform.

  5. -

    Finally, the API developer company might be attracting attention to other related programming tools and hoping to increase sales by publishing the API under a free license.

    +

    Finally, by publishing the API under a free license, the API vendor might be attracting attention to other programming tools it makes to increase sales.

Remarkably, such APIs are probably the only “pure” case when developers choose the solution solely because of its clean design, elaborate documentation, thought-out use-cases, etc. There are examples of copying the API design (which is the sincerest form of flattery, as we all know!) by other companies or even enthusiastic communities — that happened, for example, with the Java language API (an alternate implementation by Google) and the C# one (the Mono project) — or just borrowing apt solutions — as it happened with the concept of selecting DOM elements with CSS selectors, initially implemented in the cssQuery project, then adopted by jQuery, and after the latter became popular, incorporated as a part of the DOM standard itself.

-
1. API = the main and/or the only mean of accessing the service
+
2. API = the main and/or the only mean of accessing the service

This case is close to the previous one as developers again, not end users, are API consumers. The difference is that the API is not a product per se, but the service exposed via the API is. The purest examples are cloud platforms APIs like Amazon AWS or Braintree API. Some operations are possible through end-user interfaces, but generally speaking, the services are useless without APIs. [In our coffee example, imagine we are an operator of “cloud” coffee machines equipped with drone-powered delivery, and the API is the only mean of making an order.]

Usually, customers pay for the service usage, not for the API itself, though frequently the tariffs depend on the number of API calls.

-
2. API = a partner program
+
3. API = a partner program

Many commercial services provide the access to their platforms for third-party developers to increase sales or attract additional audiences. Examples include the Google Books partner program, Skyscanner Travel APIs, and Uber API. [In our case study, it might be the following model: we are a large chain of coffeeshops, and we encourage partners to sell our coffee through their websites or applications.] Such partnerships are fully commercial: partners monetize their own audience, and the API provider company yearns to get access to extended auditory and additional advertisement channels. As a rule, the API provider company pays for users reaching target goals and sets requirements for the integration performance level (for example, in a form of a minimum acceptable click-target ratio) to avoid misusing the API.

-
3. API = additional access to the service
+
4. API = additional access to the service

If a company possesses some unique expertise, usually in a form of some dataset that couldn't be easily gathered if needed, quite logically a demand for the API exposing this expertise arises. The most classical examples of such APIs are cartographical APIs: collecting detailed and precise geodata and maintaining its freshness is extremely expensive, and a wide range of services would become much more useful if they featured an integrated map. [Our coffee example hardly matches this pattern as the data we accumulate — coffee machines locations, beverages types — is something useless in any other context but ordering a cup of coffee.]

This case is the most interesting one from the API developers' point of view as the existence of the API does really serve as a multiplier to the opportunities: the expertise owner could not physically develop all imaginable services utilizing the expertise. Providing the API is a win-win: third-party services got their functionality improved, and the API provider got some profits.

Access to the API might be unconditionally paid. However, hybrid models are more common: the API is free till some conditions are met, such as usage limits or constraints (for example, only non-commercial projects are allowed). Sometimes the API is provided for free with minimal restrictions to popularize the platform (for example, Apple Maps).

@@ -3486,9 +3486,9 @@ ProgramContext.dispatch = (action) => {

All those problems lead to having an external API that actually hurts the company's reputation, not improves it. In fact, you're providing a very bad service for a very critical and skeptical auditory. If you don't have a resource to develop the API as a product for external customers, better don't even start.

-
4. API = an advertisment site
+
5. API = an advertisment site

In this case, we talk mostly about widgets and search engines; to display commercials direct access to end users is a must. The most typical examples of such APIs are advertisement networks APIs. However, mixed approaches do exist either — meaning that some API, usually a searching one, goes with commercial incuts. [In our coffee example, it means that the offer searching function will start promoting paid results on the search results page.]

-
5. API = self-advertisement and self-PR
+
6. API = self-advertisement and self-PR

If an API has neither explicit nor implicit monetization, it might still generate some income, increasing the company's brand awareness through displaying logos and other recognizable elements while working with the API, either native (if the API goes with UI elements) or agreed-upon ones (if partners are obliged to embed specifical branding in those places where the API functionality is used or the data acquired through API is displayed). The API provider company's goals in this case are either attracting users to the company's services or just increasing brand awareness in general. [In the case of our coffee API, let's imagine that we're providing some totally unrelated service, like selling tires, and by providing the API we hope to increase brand recognition and get a reputation as an IT company.]

The target audiences of such self-promotion might also differ:

Additionally, we might talk about forming a community, e.g. the network of developers (or customers, or business owners) who are loyal to the product. The benefits of having such a community might be substantial: lowering the technical support costs, getting a convenient channel for publishing announcements regarding new services and new releases, and obtaining beta users for upcoming products.

-
6. API = a feedback and UGC tool
+
7. API = a feedback and UGC tool

If a company possesses some big data, it might be useful to provide a public API for users to make corrections in the data or otherwise get involved in working with it. For example, cartographical API providers usually allow to post feedback or correct a mistake right on partners' websites and applications. [In the case of our coffee API, we might be collecting feedback to improve the service, both passively through building coffeeshops ratings or actively through contacting business owners to convey users' requests or through finding new coffee shops that are still not integrated with the platform.]

-
7. Terraforming
+
8. Terraforming

Finally, the most altruistic approach to API product development is providing it free of charge (or as an open source and open data project) just to change the landscape. If today nobody's willing to pay for the API, we might invest in popularizing the functionality hoping to find commercial niches later (in any of the abovementioned formats) or to increase the significance and usefulness of the API integrations for end users (and therefore the readiness of the partners to pay for the API). [In the case of our coffee example, imagine a coffee machine maker that starts providing APIs for free aiming to make having an API a “must” for every coffee machine vendor thus allowing for the development of commercial API-based services in the future.]

-
8. Gray zones
+
9. Gray zones

One additional source of income for the API provider is the analysis of the requests that end users make. In other words — collecting and re-selling some user data. You must be aware that the difference between acceptable data collecting (such as aggregating search requests to understand trends or finding promising locations for opening a coffee shop) and unacceptable ones are quite vague, and tends to vary in time and space (e.g. some actions might be totally legal at the one side of the state border, and totally illegal at the other side), so making a decision of monetizing the API with it should be carried out with extreme caution.

The API-first approach

Last several years we see the trend of providing some functionality as an API (e.g. as a product for developers) instead of developing the service for end users. This approach, dubbed “API-first,” reflects the growing specialization in the IT world: developing APIs becomes a separate area of expertise that business is ready to out-source instead of spending resources to develop internal APIs for the applications by in-house IT department. However, this approach is not universally accepted (yet), and you should keep in mind the factors that affect the decision of launching a service in the API-first paradigm.

diff --git a/src/en/clean-copy/01-Introduction/01.md b/src/en/clean-copy/01-Introduction/01.md index 90a7fba..8c73353 100644 --- a/src/en/clean-copy/01-Introduction/01.md +++ b/src/en/clean-copy/01-Introduction/01.md @@ -8,6 +8,6 @@ Section II is dedicated to an API lifecycle: how interfaces evolve over time, an Finally, Section III is more about the un-engineering sides of the API, like API marketing, organizing customer support, working with a community, etc. -First two sections are interesting to engineers mostly, while the third section is more relevant to both engineers and product managers. However, we insist that the third section is the most important one for the API software developer. Since an API is a product for engineers, you cannot simply pronounce a non-engineering team responsible for product planning and support. Nobody but you knows better your API's product features. +The first two sections are interesting to engineers mostly, while the third section is more relevant to both engineers and product managers. However, we insist that the third section is the most important one for the API software developer. Since an API is a product for engineers, you cannot simply pronounce a non-engineering team responsible for product planning and support. Nobody but you knows better your API's product features. Let's start. \ No newline at end of file diff --git a/src/en/clean-copy/01-Introduction/02.md b/src/en/clean-copy/01-Introduction/02.md index 0114796..5e4ffab 100644 --- a/src/en/clean-copy/01-Introduction/02.md +++ b/src/en/clean-copy/01-Introduction/02.md @@ -1,12 +1,12 @@ ### [The API Definition][intro-api-definition] -Before we start talking about the API design, we need to explicitly define what the API is. Encyclopedia tells us that “API” is an acronym for the “Application Program Interface.” This definition is fine, but useless. Much like the “Man” definition by Plato: Man stood upright on two legs without feathers. This definition is fine again, but it gives us no understanding of what's so important about a Man. (Actually, not “fine” either. Diogenes of Sinope once brought a plucked chicken, saying “That's Plato's Man.” And Plato had to add “with broad nails” to his definition.) +Before we start talking about the API design, we need to explicitly define what the API is. Encyclopedia tells us that “API” is an acronym for the “Application Program Interface.” This definition is fine but useless. Much like the “Man” definition by Plato: Man stood upright on two legs without feathers. This definition is fine again, but it gives us no understanding of what's so important about a Man. (Actually, not “fine” either. Diogenes of Sinope once brought a plucked chicken, saying “That's Plato's Man.” And Plato had to add “with broad nails” to his definition.) What the API *means* apart from the formal definition? You're possibly reading this book using a Web browser. To make the browser display this page correctly, a bunch of stuff must work correctly: parsing the URL according to the specification, the DNS service, the TLS handshake protocol, transmitting the data over HTTP protocol, HTML document parsing, CSS document parsing, correct HTML+CSS rendering, and so on and so forth. -But those are just the tip of the iceberg. To make the HTTP protocol work you need the entire network stack (comprising 4-5 or even more different level protocols) work correctly. HTML document parsing is being performed according to hundreds of different specifications. The document rendering operations call the underlying operating system APIs, or even directly graphical processor APIs. And so on: down to modern CISC processor commands that are implemented on top of the microcommands API. +But those are just the tip of the iceberg. To make the HTTP protocol work you need the entire network stack (comprising 4-5 or even more different level protocols) to work correctly. HTML document parsing is being performed according to hundreds of different specifications. The document rendering operations call the underlying operating system APIs, or even directly graphical processor APIs. And so on: down to modern CISC processor commands that are implemented on top of the API of microcommands. In other words, hundreds or even thousands of different APIs must work correctly to make basic actions possible, like viewing a webpage. Modern Internet technologies simply couldn't exist without these tons of APIs working fine. @@ -16,10 +16,10 @@ When I'm asked of an example of a well-designed API, I usually show a picture of [![igorelick @ pixabay](/img/pont-du-gard.jpg "The Pont-du-Gard aqueduct. Built in the 1st century AD")](https://pixabay.com/photos/pont-du-gard-france-aqueduct-bridge-3909998/) - * it interconnects two areas + * it interconnects two areas, * backwards compatibility being broken not a single time in two thousand years. -What differs between a Roman aqueduct and a good API is that APIs presume the contract to be *programmable*. To connect two areas *writeing some code* is needed. The goal of this book is to help you in designing APIs which serve their purposes as solidly as a Roman aqueduct does. +What differs between a Roman aqueduct and a good API is that in the case of APIs, the contract is presumed to be *programmable*. To connect the two areas, *writing some code* is needed. The goal of this book is to help you in designing APIs that serve their purposes as solidly as a Roman aqueduct does. An aqueduct also illustrates another problem of the API design: your customers are engineers themselves. You are not supplying water to end-users: suppliers are plugging their pipes into your engineering structure, building their own structures upon it. On one hand, you may provide access to the water to many more people through them, not spending your time plugging each individual house into your network. On the other hand, you can't control the quality of suppliers' solutions, and you are to be blamed every time there is a water problem caused by their incompetence. diff --git a/src/en/clean-copy/01-Introduction/03.md b/src/en/clean-copy/01-Introduction/03.md index e870a85..cdee864 100644 --- a/src/en/clean-copy/01-Introduction/03.md +++ b/src/en/clean-copy/01-Introduction/03.md @@ -4,7 +4,7 @@ Before we start laying out the recommendations, we ought to specify what API we Let's discuss the second question first. Obviously, API “finesse” is first of all defined through its capability to solve developers' and users' problems. (One may reasonably say that solving problems might not be the main purpose of offering the API of ours to developers. However, manipulating public opinion is out of this book's author's interest. Here we assume that APIs exist primarily to help people, not for some other covertly declared purposes.) -So, how the “fine” API design might assist developers in solving their (and their users') problems? Quite simple: a well-designed API allows developers to do their jobs in the most efficient and comprehensible manner. The distance from formulating a task to writing working code must be as short as possible. Among other things, it means that: +So, how the “fine” API design might assist developers in solving their (and their users') problems? Quite simply: a well-designed API allows developers to do their jobs in the most efficient and comprehensible manner. The distance from formulating a task to writing working code must be as short as possible. Among other things, it means that: * it must be totally obvious out of your API's structure how to solve a task * ideally, developers at first glance should be able to understand, what entities are meant to solve their problem * the API must be readable; @@ -17,4 +17,4 @@ However, the static convenience and clarity of APIs are simple parts. After all, Problems begin when we start to expand our API. Adding new functionality sooner or later results in transforming once plain and simple API into a mess of conflicting concepts, and our efforts to maintain backwards compatibility will lead to illogical, unobvious, and simply bad design solutions. It is partly related to an inability to predict the future in detail: your understanding of “fine” APIs will change over time, both in objective terms (what problems the API is to solve, and what is the best practice) and in subjective terms too (what obviousness, readability, and consistency *really mean* to your API design). -The principles we are explaining below are specifically oriented to making APIs evolve smoothly over time, not being turned into a pile of mixed inconsistent interfaces. It is crucial to understand that this approach isn't free: a necessity to bear in mind all possible extension variants and to preserve essential growth points means interface redundancy and possibly excessing abstractions being embedded in the API design. Besides, both make the developers' jobs harder. **Providing excess design complexities being reserved for future use makes sense only if this future actually exists for your API. Otherwise, it's simply an overengineering.** +The principles we are explaining below are specifically oriented to making APIs evolve smoothly over time, not being turned into a pile of mixed inconsistent interfaces. It is crucial to understand that this approach isn't free: a necessity to bear in mind all possible extension variants and to preserve essential growth points means interface redundancy and possibly excessing abstractions being embedded in the API design. Besides, both make the developers' jobs harder. **Providing excess design complexities being reserved for future use makes sense only if this future actually exists for your API. Otherwise, it's simply overengineering.** diff --git a/src/en/clean-copy/01-Introduction/06.md b/src/en/clean-copy/01-Introduction/06.md index c947e73..3260220 100644 --- a/src/en/clean-copy/01-Introduction/06.md +++ b/src/en/clean-copy/01-Introduction/06.md @@ -39,7 +39,7 @@ It should be read like this: * a specific JSON, containing a `some_parameter` field and some other unspecified fields (indicated by ellipsis) is being sent as a request body payload; * in response (marked with an arrow symbol `→`) server returns a `404 Not Founds` status code; the status might be omitted (treat it like a `200 OK` if no status is provided); * the response could possibly contain additional notable headers; - * the response body is a JSON comprising two fields: `error_reason` and `error_message`; field value absence means that field contains exactly what you expect it should contain — so there is some generic error reason value which we omitted; + * the response body is a JSON comprising two fields: `error_reason` and `error_message`; field value absence means that the field contains exactly what you expect it should contain — so there is some generic error reason value which we omitted; * if some token is too long to fit a single line, we will split it into several lines adding `⮠` to indicate it continues next line. The term “client” here stands for an application being executed on a user's device, either a native or a web one. The terms “agent” and “user agent” are synonymous to “client.” diff --git a/src/en/clean-copy/02-Section I. The API Design/02.md b/src/en/clean-copy/02-Section I. The API Design/02.md index cc49473..338948c 100644 --- a/src/en/clean-copy/02-Section I. The API Design/02.md +++ b/src/en/clean-copy/02-Section I. The API Design/02.md @@ -18,17 +18,17 @@ So, let's imagine that we are going to develop an API for automated coffee order * Possibly, we're optimizing waiting times? To save the time people waste while waiting for their beverages. * Possibly, we're reducing the number of errors? To help people get exactly what they wanted to order, stop losing information in imprecise conversational communication, or in dealing with unfamiliar coffee machine interfaces? - The “why” question is the most important of all questions you must ask yourself. And not only about global project goals, but also locally about every single piece of functionality. **If you can't briefly and clearly answer the question “what this entity is needed for” then it's not needed**. + The “why” question is the most important of all questions you must ask yourself. And not only about global project goals but also locally about every single piece of functionality. **If you can't briefly and clearly answer the question “what this entity is needed for” then it's not needed**. Here and throughout we assume, to make our example more complex and bizarre, that we are optimizing all three factors. - 2. Do the problems we outlined really exist? Do we really observe unequal coffee-machines utilization in mornings? Do people really suffer from the inability to find nearby a toffee nut latte they long for? Do they really care about the minutes they spend in lines? + 2. Do the problems we outlined really exist? Do we really observe unequal coffee-machines utilization in the mornings? Do people really suffer from the inability to find nearby a toffee nut latte they long for? Do they really care about the minutes they spend in lines? 3. Do we actually have resources to solve the problem? Do we have access to a sufficient number of coffee machines and users to ensure the system's efficiency? 4. Finally, will we really solve a problem? How we're going to quantify the impact our API makes? -In general, there are no simple answers to those questions. Ideally, you should start you work having all the relevant metrics measured: how much time is wasted exactly, and what numbers we're going to achieve providing we have such coffee machines density. Let us also stress that in the real world obtaining these numbers is only possible if you're entering a stable market. If you try to create something new, your only option is to rely on your intuition. +In general, there are no simple answers to those questions. Ideally, you should start the work having all the relevant metrics measured: how much time is wasted exactly, and what numbers we're going to achieve providing we have such coffee machines density. Let us also stress that in the real world obtaining these numbers is only possible if you're entering a stable market. If you try to create something new, your only option is to rely on your intuition. #### Why an API? @@ -38,7 +38,7 @@ In other words, there must be a solid reason to split two software development d We should also note that you should try making an API when, and only when, your answer to question (3) is "because that's our area of expertise". Developing APIs is a sort of meta-engineering: you're writing some software to allow other vendors to develop software to solve users' problems. You must possess expertise in both domains (APIs and user products) to design your API well. -As for our speculative example, let us imagine that in the near future some tectonic shift happened within the coffee brewing market. Two distinct player groups took shape: some companies provide “hardware,” i.e. coffee machines; other companies have access to customer auditory. Something like the modern-day flights market looks like: there are air companies, which actually transport passengers; and there are trip planning services where users are choosing between trip variants the system generates for them. We're aggregating hardware access to allow app vendors for ordering freshly brewed coffee. +As for our speculative example, let us imagine that in the nearby future, some tectonic shift happened within the coffee brewing market. Two distinct player groups took shape: some companies provide “hardware,” i.e. coffee machines; other companies have access to customer auditory. Something like the modern-day flights market looks like: there are air companies, which actually transport passengers; and there are trip planning services where users are choosing between trip variants the system generates for them. We're aggregating hardware access to allow app vendors for ordering freshly brewed coffee. #### What and How @@ -48,4 +48,4 @@ After finishing all these theoretical exercises, we should proceed right to desi In our coffee case, we are: * providing an API to services with a larger audience, so their users may order a cup of coffee in the most efficient and convenient manner; - * abstracting access to coffee machines “hardware” and developing generalized sotware methods to select a beverage kind and some location to make an order. + * abstracting access to coffee machines' “hardware” and developing generalized software methods to select a beverage kind and some location to make an order. diff --git a/src/en/clean-copy/02-Section I. The API Design/03.md b/src/en/clean-copy/02-Section I. The API Design/03.md index 25ae4b5..c3f7a43 100644 --- a/src/en/clean-copy/02-Section I. The API Design/03.md +++ b/src/en/clean-copy/02-Section I. The API Design/03.md @@ -1,6 +1,6 @@ ### [Separating Abstraction Levels][api-design-separating-abstractions] -“Separate abstraction levels in your code” is possibly the most general advice to software developers. However, we don't think it would be a grave exaggeration to say that abstraction levels separation is also the most difficult task for API developers. +“Separate abstraction levels in your code” is possibly the most general advice to software developers. However, we don't think it would be a grave exaggeration to say that abstraction level separation is also the most difficult task for API developers. Before proceeding to the theory, we should formulate clearly *why* abstraction levels are so important, and what goals we're trying to achieve by separating them. @@ -70,7 +70,7 @@ This solution intuitively looks bad, and it really is: it violates all the above **Second**, we will have automatically got problems if we need to vary the beverage size. For example, if one day we decide to offer a choice to a customer, how many milliliters of lungo they desire exactly, then we have to perform one of the following tricks. -Option I: we have a list of possible volumes fixed and introduce bogus recipes like `/recipes/small-lungo` or `recipes/large-lungo`. Why “bogus”? Because it's still the same lungo recipe, same ingredients, same preparation steps, only volumes differ. We will have to start the mass production of recipes, only different in volume, or to introduce some recipe “inheritance” to be able to specify the “base” recipe and just redefine the volume. +Option I: we have a list of possible volumes fixed and introduce bogus recipes like `/recipes/small-lungo` or `recipes/large-lungo`. Why “bogus”? Because it's still the same lungo recipe, same ingredients, same preparation steps, only volumes differ. We will have to start the mass production of recipes, only different in volume, or introduce some recipe “inheritance” to be able to specify the “base” recipe and just redefine the volume. Option II: we modify an interface, pronouncing volumes stated in recipes being just the default values. We allow requesting different cup volumes while placing an order: @@ -83,11 +83,12 @@ POST /v1/orders } ``` -For those orders with an arbitrary volume requested, a developer will need to obtain the requested volume not from the `GET /v1/recipes` endpoint, but the `GET /v1/orders` one. Doing so we're getting a whole bunch of related problems: +For those orders with an arbitrary volume requested, a developer will need to obtain the requested volume, not from the `GET /v1/recipes` endpoint, but the `GET /v1/orders` one. Doing so we're getting a whole bunch of related problems: * there is a significant chance that developers will make mistakes in this functionality implementation if they add arbitrary volume support in the code working with the `POST /v1/orders` handler, but forget to make corresponding changes in the order readiness check code; - * the same field (coffee volume) now means different things in different interfaces. In the `GET /v1/recipes` context the `volume` field means “a volume to be prepared if no arbitrary volume is specified in the `POST /v1/orders` request”; and it cannot be renamed to “default volume” easily. + * the same field (coffee volume) now means different things in different interfaces. In the context of the `GET /v1/recipes` endpoint, the `volume` field means “a volume to be prepared if no arbitrary volume is specified in the `POST /v1/orders` request”; and it cannot be renamed to “default volume” easily. + +So we will get this: -So we will get ``` GET /v1/orders/{id} → @@ -111,11 +112,11 @@ Abstraction levels separation should go in three directions: 1. From user scenarios to their internal representation: high-level entities and their method nomenclatures must directly reflect the API usage scenarios; low-level entities reflect the decomposition of the scenarios into smaller parts. - 2. From user to “raw” data subject field terms — in our case from high-level terms like “order,” “recipe,” “café” to low-level terms like “beverage temperature,” “coffee machine geographical coordinates,” etc. + 2. From user to “raw” data subject field terms — in our case from high-level terms like “order,” “recipe,” and “café” to low-level terms like “beverage temperature,” “coffee machine geographical coordinates,” etc. - 3. Finally, from data structures suitable for end users to “raw” data structures — in our case, from “lungo recipe” and “"Chamomile" café chain” to the raw byte data stream from “Good Morning” coffee machine sensors. + 3. Finally, from data structures suitable for end users to “raw” data structures — in our case, from “lungo recipe” and “the "Chamomile" café chain” to the raw byte data stream from “Good Morning” coffee machine sensors. -The more is the distance between programmable contexts our API connects, the deeper is the hierarchy of the entities we are to develop. +The more the distance between programmable contexts our API connects, the deeper the hierarchy of the entities we are to develop. In our example with coffee readiness detection, we clearly face the situation when we need an interim abstraction level: * on one hand, an “order” should not store the data regarding coffee machine sensors; @@ -158,10 +159,10 @@ GET /v1/orders/{id} We call this approach “naïve” not because it's wrong; on the contrary, that's quite a logical “default” solution if you don't know yet (or don't understand yet) how your API will look like. The problem with this approach lies in its speculativeness: it doesn't reflect the subject area's organization. -An experienced developer in this case must ask: what options do exist? how should we really determine the beverage readiness? If it turns out that comparing volumes *is* the only working method to tell whether the beverage is ready, then all the speculations above are wrong. You may safely include readiness-by-volume detection into your interfaces since no other methods exist. Before abstracting something we need to learn what exactly we're abstracting. +An experienced developer in this case must ask: what options do exist? how should we really determine the readiness of the beverage? If it turns out that comparing volumes *is* the only working method to tell whether the beverage is ready, then all the speculations above are wrong. You may safely include readiness-by-volume detection into your interfaces since no other methods exist. Before abstracting something we need to learn what exactly we're abstracting. In our example let's assume that we have studied coffee machines' API specs, and learned that two device types exist: - * coffee machines capable of executing programs coded in the firmware; the only customizable options are some beverage parameters, like desired volume, a syrup flavor, and a kind of milk; + * coffee machines capable of executing programs coded in the firmware; the only customizable options are some beverage parameters, like the desired volume, a syrup flavor, and a kind of milk; * coffee machines with built-in functions, like “grind specified coffee volume,” “shed the specified amount of water,” etc.; such coffee machines lack “preparation programs,” but provide access to commands and sensors. To be more specific, let's assume those two kinds of coffee machines provide the following physical API. @@ -268,14 +269,14 @@ To be more specific, let's assume those two kinds of coffee machines provide the } ``` - **NB**. The example is intentionally factitious to model a situation described above: to determine beverage readiness you have to compare the requested volume with volume sensor readings. + **NB**. The example is intentionally fictitious to model the situation described above: to determine beverage readiness you have to compare the requested volume with volume sensor readings. Now the picture becomes more apparent: we need to abstract coffee machine API calls so that the “execution level” in our API provides general functions (like beverage readiness detection) in a unified form. We should also note that these two coffee machine API kinds belong to different abstraction levels themselves: the first one provides a higher-level API than the second one. Therefore, a “branch” of our API working with second-kind machines will be deeper. -The next step in abstraction level separating is determining what functionality we're abstracting. To do so, we need to understand the tasks developers solve at the “order” level, and to learn what problems they get if our interim level is missing. +The next step in abstraction level separating is determining what functionality we're abstracting. To do so, we need to understand the tasks developers solve at the “order” level and to learn what problems they get if our interim level is missing. 1. Obviously, the developers desire to create an order uniformly: list high-level order properties (beverage kind, volume, and special options like syrup or milk type), and don't think about how the specific coffee machine executes it. - 2. Developers must be able to learn the execution state: is the order ready? if not — when to expect it's ready (and is there any sense to wait in case of execution errors). + 2. Developers must be able to learn the execution state: is the order ready? If not — when to expect it's ready (and is there any sense to wait in case of execution errors)? 3. Developers need to address the order's location in space and time — to explain to users where and when they should pick the order up. 4. Finally, developers need to run atomic operations, like canceling orders. @@ -283,11 +284,11 @@ Note, that the first-kind API is much closer to developers' needs than the secon * absence of explicit “programs” to “recipes” relation; program identifier is of no use to developers since there is a “recipe” concept; * absence of explicit “ready” status. -But with the second-kind API, it's much worse. The main problem we foresee is an absence of “memory” for actions being executed. Functions and sensors API is totally stateless, which means we don't even understand who called a function being currently executed, or when, or which order it relates. +But with the second-kind API, it's much worse. The main problem we foresee is an absence of “memory” for actions being executed. Functions and sensors API is totally stateless, which means we don't even understand who called a function being currently executed, when, or to what order it relates. So we need to introduce two abstraction levels. - 1. Execution control level, which provides the uniform interface to indivisible programs. “Uniform interface” means here that, regardless of a coffee machine's kind, developers may expect: + 1. Execution control level, which provides a uniform interface to indivisible programs. “Uniform interface” means here that, regardless of a coffee machine's kind, developers may expect: * statuses and other high-level execution parameters nomenclature (for example, estimated preparation time or possible execution errors) being the same; * methods nomenclature (for example, order cancellation method) and their behavior being the same. @@ -342,7 +343,7 @@ This approach has some benefits, like the possibility to provide different sets * call `POST /execute` physical API method, passing internal program identifier — for the first API kind; * initiate runtime creation to proceed with the second API kind. -Out of general considerations, runtime level for the second-kind API will be private, so we are more or less free in implementing it. The easiest solution would be to develop a virtual state machine that creates a “runtime” (e.g. a stateful execution context) to run a program and control its state. +Out of general considerations, the runtime level for the second-kind API will be private, so we are more or less free in implementing it. The easiest solution would be to develop a virtual state machine that creates a “runtime” (e.g. a stateful execution context) to run a program and control its state. ``` POST /v1/runtimes @@ -435,7 +436,7 @@ Let's now look at how the order cancel operation flows through our abstraction l * the `runs/cancel` handler completes operations on its level of responsibility and, depending on the coffee machine API kind, proceeds with one of two possible execution branches: * either calls the `POST /execution/cancel` method of a physical coffee machine API; * or invokes the `POST /v1/runtimes/{id}/terminate` method; - * in the second case the call chain continues as the `terminate` handler operates its internal state: + * in the second case, the call chain continues as the `terminate` handler operates its internal state: * changes the `resolution` to `"terminated"`; * runs the `"discard_cup"` command. @@ -446,28 +447,28 @@ Handling state-modifying operations like the `cancel` one requires more advanced * at the second API kind, physical level the “cancel” operation itself doesn't exist: “cancel” means “executing the `discard_cup` command,” which is quite the same as any other command. The interim API level is needed to make this transition between different level “cancels” smooth and rational without jumping over canyons. - 2. From a high-level point of view, canceling an order is a terminal action, since no further operations are possible. From a low-level point of view, the processing continues until the cup is discarded, and then the machine is to be unlocked (e.g. new runtimes creation allowed). It's a task to the execution control level to couple those two states, outer (the order is canceled) and inner (the execution continues). + 2. From a high-level point of view, canceling an order is a terminal action since no further operations are possible. From a low-level point of view, the processing continues until the cup is discarded, and then the machine is to be unlocked (e.g. new runtimes creation allowed). It's a task to the execution control level to couple those two states, outer (the order is canceled) and inner (the execution continues). It might look like forcing the abstraction levels isolation is redundant and makes interfaces more complicated. In fact, it is: it's very important to understand that flexibility, consistency, readability, and extensibility come with a price. One may construct an API with zero overhead, essentially just providing access to the coffee machine's microcontrollers. However using such an API would be a disaster for a developer, not to mention the inability to extend it. -Separating abstraction levels is first of all a logical procedure: how we explain to ourselves and developers what our API consists of. **The abstraction gap between entities exists objectively**, no matter what interfaces we design. Our task is just separate this gap into levels *explicitly*. The more implicitly abstraction levels are separated (or worse — blended into each other), the more complicated is your API's learning curve, and the worse is the code that uses it. +Separating abstraction levels is first of all a logical procedure: how we explain to ourselves and developers what our API consists of. **The abstraction gap between entities exists objectively**, no matter what interfaces we design. Our task is just to sort this gap into levels *explicitly*. The more implicitly abstraction levels are separated (or worse — blended into each other), the more complicated is your API's learning curve, and the worse is the code that uses it. #### The Data Flow -One useful exercise allowing us to examine the entire abstraction hierarchy is excluding all the particulars and constructing (on a paper or just in your head) a data flow chart: what data is flowing through your API entities, and how it's being altered at each step. +One useful exercise allowing us to examine the entire abstraction hierarchy is excluding all the particulars and constructing (on paper or just in your head) a data flow chart: what data is flowing through your API entities, and how it's being altered at each step. -This exercise doesn't just help but also allows to design really large APIs with huge entity nomenclatures. Human memory isn't boundless; any project which grows extensively will eventually become too big to keep the entire entity hierarchy in mind. But it's usually possible to keep in mind the data flow chart, or at least keep a much larger portion of the hierarchy. +This exercise doesn't just help but also allows us design really large APIs with huge entity nomenclatures. Human memory isn't boundless; any project which grows extensively will eventually become too big to keep the entire entity hierarchy in mind. But it's usually possible to keep in mind the data flow chart, or at least keep a much larger portion of the hierarchy. What data flow do we have in our coffee API? 1. It starts with the sensors data, i.e. volumes of coffee / water / cups. This is the lowest data level we have, and here we can't change anything. - 2. A continuous sensors data stream is being transformed into discrete command execution statuses, injecting new concepts which don't exist within the subject area. A coffee machine API doesn't provide a “coffee is being poured” or a “cup is being set” notion. It's our software that treats incoming sensors data and introduces new terms: if the volume of coffee or water is less than the target one, then the process isn't over yet. If the target value is reached, then this synthetic status is to be switched, and the next command to be executed. - It is important to note that we don't calculate new variables out of sensors data: we need to create a new dataset first, a context, an “execution program” comprising a sequence of steps and conditions, and to fill it with initial values. If this context is missing, it's impossible to understand what's happening with the machine. + 2. A continuous sensors data stream is being transformed into discrete command execution statuses, injecting new concepts which don't exist within the subject area. A coffee machine API doesn't provide a “coffee is being poured” or a “cup is being set” notion. It's our software that treats incoming sensor data and introduces new terms: if the volume of coffee or water is less than the target one, then the process isn't over yet. If the target value is reached, then this synthetic status is to be switched, and the next command is executed. + It is important to note that we don't calculate new variables out of sensor data: we need to create a new dataset first, a context, an “execution program” comprising a sequence of steps and conditions, and fill it with initial values. If this context is missing, it's impossible to understand what's happening with the machine. 3. Having logical data about the program execution state, we can (again via creating a new high-level data context) merge two different data streams from two different kinds of APIs into a single stream, which provides in a unified form the data regarding executing a beverage preparation program with logical variables like the recipe, volume, and readiness status. -Each API abstraction level, therefore corresponds to some data flow generalization and enrichment, converting the low-level (and in fact useless to end users) context terms into the higher-level context terms. +Each API abstraction level, therefore corresponds to some data flow generalization and enrichment, converting low-level (and in fact useless to end users) context terms into higher-level context terms. We may also traverse the tree backward. @@ -479,6 +480,6 @@ We may also traverse the tree backward. Also, if we take a deeper look into the “bad” decision (forcing developers to determine actual order status on their own), being discussed at the beginning of this chapter, we could notice a data flow collision there: * on one hand, in the order context “leaked” physical data (beverage volume prepared) is injected, therefore stirring abstraction levels irreversibly; - * on the other hand, the order context itself is deficient: it doesn't provide new meta-variables, non-existent at the lower levels (the order status, in particular), doesn't initialize them, and doesn't set the game rules. + * on the other hand, the order context itself is deficient: it doesn't provide new meta-variables non-existent at the lower levels (the order status, in particular), doesn't initialize them, and doesn't set the game rules. We will discuss data contexts in more detail in Section II. Here we will just state that data flows and their transformations might be and must be examined as a specific API facet, which helps us to separate abstraction levels properly and to check if our theoretical concepts work as intended. diff --git a/src/en/clean-copy/02-Section I. The API Design/04.md b/src/en/clean-copy/02-Section I. The API Design/04.md index 28bee97..0a5e0e4 100644 --- a/src/en/clean-copy/02-Section I. The API Design/04.md +++ b/src/en/clean-copy/02-Section I. The API Design/04.md @@ -6,7 +6,7 @@ In the previous chapter, we concluded that the hierarchy of abstractions in our * the program execution control level (the entities responsible for transforming orders into machine commands); * the runtime level for the second API kind (the entities describing the command execution state machine). -We are now to define each entity's responsibility area: what's the reasoning for keeping this entity within our API boundaries; what operations are applicable to the entity directly (and which are delegated to other objects). In fact, we are to apply the “why”-principle to every single API entity. +We are now to define each entity's responsibility area: what's the reasoning for keeping this entity within our API boundaries? What operations are applicable to the entity directly (and which are delegated to other objects)? In fact, we are to apply the “why”-principle to every single API entity. To do so, we must iterate all over the API and formulate in subject area terms what every object is. Let us remind that the abstraction levels concept implies that each level is some interim subject area per se; a step we take in the journey from describing a task in terms belonging to the first connected context (“a lungo ordered by a user”) terms belonging to the second connected context (“a command performed by a coffee machine”). @@ -72,7 +72,7 @@ let matchingCoffeeMachines = app.display(matchingCoffeeMachines); ``` -As you see, developers are to write a lot of redundant code (to say nothing about the complexity of implementing spatial indexes). Besides, if we take into consideration our Napoleonic plans to cover all coffee machines in the world with our API, then we need to admit that this algorithm is just a waste of computatonal resources on retrieving lists and indexing them. +As you see, developers are to write a lot of redundant code (to say nothing about the complexity of implementing spatial indexes). Besides, if we take into consideration our Napoleonic plans to cover all coffee machines in the world with our API, then we need to admit that this algorithm is just a waste of computational resources on retrieving lists and indexing them. The necessity of adding a new endpoint for searching becomes obvious. To design such an interface we must imagine ourselves being UX designers, and think about how an app could try to arouse users' interest. Two scenarios are evident: * display all cafes in the vicinity and the types of coffee they offer (a “service discovery” scenario) — for new users or just users with no specific preferences; @@ -106,12 +106,12 @@ POST /v1/offers/search ``` Here: - * an `offer` — is a marketing bid: on what conditions a user could have the requested coffee beverage (if specified in the request), or some kind of a marketing offer — prices for the most popular or interesting products (if no specific preference was set); - * a `place` — is a spot (café, restaurant, street vending machine) where the coffee machine is located; we never introduced this entity before, but it's quite obvious that users need more convenient guidance to find a proper coffee machine than just geographical coordinates. + * an `offer` — a marketing bid: on what conditions a user could have the requested coffee beverage (if specified in the request), or some kind of a marketing offer — prices for the most popular or interesting products (if no specific preference was set); + * a `place` — a spot (café, restaurant, street vending machine) where the coffee machine is located; we never introduced this entity before, but it's quite obvious that users need more convenient guidance to find a proper coffee machine than just geographical coordinates. -**NB**. We could have enriched the existing `/coffee-machines` endpoint instead of adding a new one. This decision, however, looks less semantically viable: coupling in one interface different modes of listing entities, by relevance and by order, is usually a bad idea because these two types of rankings imply different usage features and scenarios. Furthermore, enriching the search with “offers” pulls this functionality out of the `coffee-machines` namespace: the fact of getting offers to prepare specific beverages in specific conditions is a key feature to users, with specifying the coffee machine being just a part of an offer. And users actually rarely care about coffee machine models. +**NB**. We could have enriched the existing `/coffee-machines` endpoint instead of adding a new one. This decision, however, looks less semantically viable: coupling different modes of listing entities in one interface, by relevance and by order, is usually a bad idea because these two types of rankings imply different features and usage scenarios. Furthermore, enriching the search with “offers” pulls this functionality out of the `coffee-machines` namespace: the fact of getting offers to prepare specific beverages in specific conditions is a key feature to users, with specifying the coffee machine being just a part of an offer. And users actually rarely care about coffee machine models. -**NB**. Actually, having `coffee_machine_id` in the interface is at some extent violating the abstraction separation principle. It should be organized in a more complex way: coffee shops shall somehow map the incoming orders against available coffee machines, and only the type of the coffee machine (if a coffee shop really operates several of them) is something meaningful in the context of the order creation. However, we make it deliberately simplified by making coffee machine selectable in the API to keep our API example readable enough. +**NB**. Actually, having `coffee_machine_id` in the interface is to some extent violating the abstraction separation principle. It should be organized in a more complex way: coffee shops shall somehow map the incoming orders against available coffee machines, and only the type of the coffee machine (if a coffee shop really operates several of them) is something meaningful in the context of the order creation. However, we make it deliberately simplified by making a coffee machine selectable in the API to keep our API example readable enough. Coming back to the code developers are writing, it would now look like that: @@ -129,7 +129,7 @@ Methods similar to the newly invented `offers/search` one are called *helpers*. For instance, let's consider the order price question. Our search function returns some “offers” with prices. But “price” is volatile; coffee could cost less during “happy hours,” for example. Developers could make a mistake thrice while implementing this functionality: * cache search results on a client device for too long (as a result, the price will always be outdated); - * contrary to previous, call the search endpoint excessively just to actualize prices, thus overloading the network and the API servers; + * contrary to the previous, call the search endpoint excessively just to actualize prices, thus overloading the network and the API servers; * create an order with an invalid price (therefore deceiving a user, displaying one sum, and debiting another). To solve the third problem we could demand including the displayed price in the order creation request, and return an error if it differs from the actual one. (In fact, any API working with money *shall* do so.) But it isn't helping with the first two problems and deteriorates the user experience. Displaying the actual price is always a much more convenient behavior than displaying errors upon pressing the “place an order” button. @@ -156,9 +156,9 @@ One solution is to provide a special identifier to an offer. This identifier mus "cursor" } ``` -By doing so we're not only helping developers to grasp the concept of getting the relevant price, but also solving a UX task of telling users about “happy hours.” +By doing so we're not only helping developers to grasp the concept of getting the relevant price but also solving a UX task of telling users about “happy hours.” -As an alternative, we could split endpoints: one for searching, another one for obtaining offers. This second endpoint would only be needed to actualize prices if needed. +As an alternative, we could split endpoints: one for searching, and one for obtaining offers. This second endpoint would only be needed to actualize prices if needed. #### Error Handling @@ -175,8 +175,8 @@ Formally speaking, this error response is enough: users get the “Invalid price The main rule of error interfaces in the APIs is that an error response must help a client to understand *what to do with this error*. An error response content must address the following questions: - 1. Which party is the problem's source: client or server? - HTTP APIs traditionally employ the `4xx` status codes to indicate client problems, `5xx` to indicate server problems (with the exception of the `404` code, which is an uncertainty status). + 1. Which party is the problem's source: the client or the server? + HTTP APIs traditionally employ the `4xx` status codes to indicate client problems and `5xx` to indicate server problems (with the exception of the `404` code, which is an uncertainty status). 2. If the error is caused by a server, is there any sense to repeat the request? If yes, then when? 3. If the error is caused by a client, is it resolvable, or not? The invalid price error is resolvable: a client could obtain a new price offer and create a new order with it. But if the error occurred because of a mistake in the client code, then eliminating the cause is impossible, and there is no need to make the user push the “place an order” button again: this request will never succeed. @@ -205,19 +205,19 @@ In our case, the price mismatch error should look like this: } ``` -After getting this error, a client is to check the error's kind (“some problem with offer”), check the specific error reason (“order lifetime expired”), and send an offer retrieving request again. If the `checks_failed` field indicated another error reason (for example, the offer isn't bound to the specified user), client actions would be different (re-authorize the user, then get a new offer). If there was no error handler for this specific reason, a client should show the `localized_message` to the user, and invoke the standard error recovery procedure. +After getting this error, a client is to check the error's kind (“some problem with the offer”), check the specific error reason (“order lifetime expired”), and send an offer retrieving request again. If the `checks_failed` field indicated another error reason (for example, the offer isn't bound to the specified user), client actions would be different (re-authorize the user, then get a new offer). If there was no error handler for this specific reason, a client should show the `localized_message` to the user, and invoke the standard error recovery procedure. -It is also worth mentioning that unresolvable errors are useless to a user at the time when error occurs (since the client couldn't react meaningfully to unknown errors), but it doesn't mean that providing extended error data is excessive. A developer will read it while fixing the issue in their code. +It is also worth mentioning that unresolvable errors are useless to a user at the time when the error occurs (since the client couldn't react meaningfully to unknown errors), but it doesn't mean that providing extended error data is excessive. A developer will read it while fixing the issue in their code. #### Decomposing Interfaces. The “7±2” Rule -Out of our own API development experience, we can tell without any doubt that the greatest final interface design mistake (and the greatest developers' pain accordingly) is excessive overloading of entities' interfaces with fields, methods, events, parameters, and other attributes. +Out of our own API development experience, we can tell without any doubt that the greatest final interface design mistake (and the greatest developers' pain accordingly) is the excessive overloading of entities' interfaces with fields, methods, events, parameters, and other attributes. -Meanwhile, there is the “Golden Rule” of interface design (applicable not only to APIs but almost to anything): humans could comfortably keep 7±2 entities in short-term memory. Manipulating a larger number of chunks complicates things for most humans. The rule is also known as the [“Miller's law”](https://en.wikipedia.org/wiki/Working_memory#Capacity). +Meanwhile, there is the “Golden Rule” of interface design (applicable not only to APIs but almost to anything): humans could comfortably keep 7±2 entities in short-term memory. Manipulating a larger number of chunks complicates things for most humans. The rule is also known as the [Miller's law](https://en.wikipedia.org/wiki/Working_memory#Capacity). The only possible method of overcoming this law is decomposition. Entities should be grouped under a single designation at every concept level of the API, so developers are never to operate more than a reasonable amount of entities (let's say, ten) at a time. -Let's take a look at the the coffee machine search function response in our API. To ensure an adequate UX of the app, quite bulky datasets are required: +Let's take a look at the coffee machine search function response in our API. To ensure an adequate UX of the app, quite bulky datasets are required: ``` { @@ -255,11 +255,11 @@ Let's take a look at the the coffee machine search function response in our API. } ``` -This approach is regretfully quite usual, and could be found in almost every API. As we see, the number of entities' fields exceeds recommended seven, and even nine. Fields are being mixed into one single list, grouped by a common prefix. +This approach is regretfully quite usual and could be found in almost every API. As we see, the number of entities' fields exceeds recommended seven, and even nine. Fields are mixed into one single list, grouped by a common prefix. In this situation, we are to split this structure into data domains: which fields are logically related to a single subject area. In our case we may identify at least 7 data clusters: - * data regarding a place where the coffee machine is located; + * data regarding the place where the coffee machine is located; * properties of the coffee machine itself; * route data; * recipe data; @@ -307,8 +307,8 @@ Let's try to group it together: } ``` -Such a decomposed API is much easier to read than a long list of different attributes. Furthermore, it's probably better to group even more entities in advance. For example, a `place` and a `route` could be nested fields under a synthetic `location` property, or an `offer` and a `pricing` might be combined into some generalized object. +Such a decomposed API is much easier to read than a long list of different attributes. Furthermore, it's probably better to group even more entities in advance. For example, a `place` and a `route` could be nested fields under a synthetic `location` property, or `offer` and `pricing` fields might be combined into some generalized object. -It is important to say that readability is achieved not only by mere grouping the entities. Decomposing must be performed in such a manner that a developer, while reading the interface, instantly understands, “Here is the place description of no interest to me right now, no need to traverse deeper.” If the data fields needed to complete some action are scattered all over different composites, the readability doesn't improve and even degrades. +It is important to say that readability is achieved not only by merely grouping the entities. Decomposing must be performed in such a manner that a developer, while reading the interface, instantly understands, “Here is the place description of no interest to me right now, no need to traverse deeper.” If the data fields needed to complete some action are scattered all over different composites, the readability doesn't improve and even degrades. Proper decomposition also helps with extending and evolving an API. We'll discuss the subject in Section II. diff --git a/src/en/clean-copy/02-Section I. The API Design/05.md b/src/en/clean-copy/02-Section I. The API Design/05.md index b492f29..bb923f6 100644 --- a/src/en/clean-copy/02-Section I. The API Design/05.md +++ b/src/en/clean-copy/02-Section I. The API Design/05.md @@ -10,9 +10,9 @@ Rules are just simply formulated generalizations from one's experience. They are For example, demanding a specification be consistent exists to help developers spare time on reading docs. If you *need* developers to read some entity's doc, it is totally rational to make its signature deliberately inconsistent. -This idea applies to every concept listed below. If you get an unusable, bulky, unobvious API because you follow the rules, it's a motive to revise the rules (or the API). +This idea applies to every concept listed below. If you get an unusable, bulky, unobvious API because you follow the rules, it's a motivation to revise the rules (or the API). -It is important to understand that you always can introduce concepts of your own. For example, some frameworks willfully reject paired `set_entity` / `get_entity` methods in a favor of a single `entity()` method, with an optional argument. The crucial part is being systematic in applying the concept. If it's rendered into life, you must apply it to every single API method, or at the very least elaborate a naming rule to discern such polymorphic methods from regular ones. +It is important to understand that you can always introduce concepts of your own. For example, some frameworks willfully reject paired `set_entity` / `get_entity` methods in a favor of a single `entity()` method, with an optional argument. The crucial part is being systematic in applying the concept. If it's rendered into life, you must apply it to every single API method, or at the very least elaborate a naming rule to discern such polymorphic methods from regular ones. #### Ensuring readability and consistency @@ -126,7 +126,7 @@ str_search_for_characters( ##### Naming implies typing -Field named `recipe` must be of a `Recipe` type. Field named `recipe_id` must contain a recipe identifier that we could find within the `Recipe` entity. +A field named `recipe` must be of a `Recipe` type. A field named `recipe_id` must contain a recipe identifier that we could find within the `Recipe` entity. Same for primitive types. Arrays must be named in a plural form or as collective nouns, i.e. `objects`, `children`. If that's impossible, better add a prefix or a postfix to avoid doubt. @@ -146,12 +146,14 @@ Specific platforms imply specific additions to this rule with regard to the firs If an entity name is a polysemantic term itself, which could confuse developers, better add an extra prefix or postfix to avoid misunderstanding. **Bad**: + ``` // Returns a list of // coffee machine builtin functions GET /coffee-machines/{id}/functions ``` -Word “function” is many-valued. It could mean built-in functions, but also “a piece of code,” or a state (machine is functioning). + +The word “function” is many-valued. It could mean built-in functions, but also “a piece of code,” or a state (machine is functioning). **Better**: `GET /v1/coffee-machines/{id}/builtin-functions-list` @@ -179,7 +181,7 @@ Several rules are violated: * functionally close methods have different `needle`/`haystack` argument ordering; * the first function finds the first occurrence while the second one finds them all, and there is no way to deduce that fact out of the function signatures. -We're leaving the exercise of making these signatures better to the reader. +We're leaving the exercise of making these signatures better for the reader. ##### Avoid double negations @@ -189,7 +191,7 @@ We're leaving the exercise of making these signatures better to the reader. **Better**: `"prohibit_calling": true` or `"avoid_calling": true` — it's easier to read, though you shouldn't deceive yourself. Avoid semantical double negations, even if you've found a “negative” word without a “negative” prefix. -Also worth mentioning is that making mistakes in the [de Morgan's laws](https://en.wikipedia.org/wiki/De_Morgan's_laws) usage is even simpler. For example, if you have two flags: +Also worth mentioning is that making mistakes in [de Morgan's laws](https://en.wikipedia.org/wiki/De_Morgan's_laws) usage is even simpler. For example, if you have two flags: ``` GET /coffee-machines/{id}/stocks @@ -223,7 +225,7 @@ const order = api.createOrder( ); ``` -This new `contactless_delivery` option isn't required, but its default value is `true`. A question arises: how developers should discern explicit intention to abolish the option (`false`) from knowing not it exists (the field isn't set). They have to write something like: +This new `contactless_delivery` option isn't required, but its default value is `true`. A question arises: how developers should discern explicit intention to abolish the option (`false`) from knowing not it exists (the field isn't set)? They have to write something like: ``` if ( @@ -238,7 +240,7 @@ if ( This practice makes the code more complicated, and it's quite easy to make mistakes, which will effectively treat the field in an opposite manner. The same could happen if some special values (i.e. `null` or `-1`) to denote value absence are used. -**NB**: this observation is not valid if both the platform and the protocol unambiguously support special tokens to reset a field to its default value with zero abstraction overhead. However, full and consistent support of this functionality rarely sees implementation. Arguably, the only example of such an API among those being popular nowadays is SQL: the language has the `NULL` concept, and default field values functionality, and the support for operations like `UPDATE … SET field = DEFAULT` (in most dialects). Though working with the protocol is still complicated (for example, in many dialects there is no simple method of getting back those values reset by an `UPDATE … DEFAULT` query), SQL features working with defaults conveniently enough to use this functionality as is. +**NB**: this observation is not valid if both the platform and the protocol unambiguously support special tokens to reset a field to its default value with zero abstraction overhead. However, full and consistent support of this functionality rarely sees implementation. Arguably, the only example of such an API among those being popular nowadays is SQL: the language has the `NULL` concept, default field values functionality, and support for operations like `UPDATE … SET field = DEFAULT` (in most dialects). Though working with the protocol is still complicated (for example, in many dialects there is no simple method of getting back those values reset by an `UPDATE … DEFAULT` query), SQL features operating defaults conveniently enough to use this functionality as is. If the protocol does not support resetting to default values as a first-class citizen, the universal rule is to make all new Boolean flags false by default. @@ -330,7 +332,7 @@ This rule might be reduced to: if an array is the result of the operation, then ##### Errors must be informative -While writing the code developers face problems, many of them quite trivial, like invalid parameter types or some boundary violations. The more convenient are the error responses your API return, the less is the amount of time developers waste struggling with it, and the more comfortable is working with the API. +While writing the code developers face problems, many of them quite trivial, like invalid parameter types or some boundary violations. The more convenient the error responses your API return, the less the amount of time developers waste struggling with it, and the more comfortable working with the API. **Bad**: ``` @@ -494,7 +496,7 @@ You may note that in this setup the error can't be resolved in one step: this si #### Developing machine-readable interfaces -In pursuit of the API clarity for humans, we frequently forget that it's not developers themselves who interact with the endpoints, but the code they've written. Many concepts that work well with user interfaces, are badly suited for the program ones: specifically, developers can't make decisions based on textual information, and they can't “refresh” the state in case of some confusing situation. +In pursuit of API clarity for humans, we frequently forget that it's not developers themselves who interact with the endpoints, but the code they've written. Many concepts that work well with user interfaces are badly suited for the program ones: specifically, developers can't make decisions based on textual information, and they can't “refresh” the state in case of some confusing situation. ##### The system state must be observable by clients @@ -518,7 +520,7 @@ GET /v1/orders/{id} ``` — though the operation looks to be executed successfully, the client must store the order id and recurrently check the `GET /v1/orders/{id}` state. This pattern is bad per se, but gets even worse when we consider two cases: - * clients might lose the id, if system failure happened in between sending the request and getting the response, or if app data storage was damaged or cleansed; + * clients might lose the id if system failure happened in between sending the request and getting the response, or if app data storage was damaged or cleansed; * customers can't use another device; in fact, the knowledge of orders being created is bound to a specific user agent. In both cases, customers might consider order creating failed, and make a duplicate order, with all the consequences to be blamed on you. @@ -645,7 +647,7 @@ At the first glance, this is the most standard way of organizing the pagination * some problem occurred, and a batch of new records awaits processing; * the client requests new records (`offset=0`) but can't find any known records on the first page; * the client continues iterating over records page by page until it finds the last known identifier; all this time the order processing is idle; - * the client might never start processing, being preoccupied with chaotic page requests to restore records sequence. + * the client might never start processing, being preoccupied with chaotic page requests to restore the sequence of records. 2. What happens if some record is deleted from the head of the list? Easy: the client will miss one record and will never learn this. 3. What cache parameters to set for this endpoint? @@ -758,7 +760,7 @@ GET /v1/records/modified/list⮠ This scheme's downsides are the necessity to create separate indexed event storage, and the multiplication of data items, since for a single record many events might exist. -#### Ensuring technical quality of APIs +#### Ensuring the technical quality of APIs Fine APIs must not only solve developers' and end users' problems but also ensure the quality of the solution, e.g. do not contain logical and technical mistakes (and do not provoke developers to make them), save computational resources, and in general implement the best practices applicable to the subject area. @@ -852,7 +854,7 @@ X-Idempotency-Token: Furthermore, adding idempotency tokens not only resolves the issue but also makes advanced optimizations possible. If the server detects an access conflict, it could try to resolve it, “rebasing” the update like modern version control systems do, and return a `200 OK` instead of a `409 Conflict`. This logic dramatically improves user experience, being fully backwards compatible, and helps to avoid conflict-resolving code fragmentation. Also, be warned: clients are bad at implementing idempotency tokens. Two problems are common: - * you can't really expect that clients generate truly random tokens — they may share the same seed or simply use weak algorithms or entropy sources; therefore you must put constraints on token checking: token must be unique to a specific user and resource, not globally; + * you can't really expect clients generate truly random tokens — they may share the same seed or simply use weak algorithms or entropy sources; therefore you must put constraints on token checking: token must be unique to a specific user and resource, not globally; * clients tend to misunderstand the concept and either generate new tokens each time they repeat the request (which deteriorates the UX, but otherwise healthy) or conversely use one token in several requests (not healthy at all and could lead to catastrophic disasters; another reason to implement the suggestion in the previous clause); writing detailed doc and/or client library is highly recommended. ##### Avoid non-atomic operations @@ -1007,9 +1009,9 @@ Just in case: nested operations must be idempotent themselves. If they are not, If the author of this book was given a dollar each time he had to implement the additional security protocol invented by someone, he would already retire. The API developers' passion for signing request parameters or introducing complex schemes of exchanging passwords for tokens is as obvious as meaningless. -**First**, almost all security-enhancing procedures for every kind of operation *are already invented*. There is no need to re-think them anew; just take the existing approach and implement it. No self-invented algorithm for request signature checking provides the same level of preventing [Man-in-the-Middle attack](https://en.wikipedia.org/wiki/Man-in-the-middle_attack) as a TLS connection with mutual certificate pinning. +**First**, almost all security-enhancing procedures for every kind of operation *are already invented*. There is no need to re-think them anew; just take the existing approach and implement it. No self-invented algorithm for request signature checking provides the same level of preventing the [Man-in-the-Middle attack](https://en.wikipedia.org/wiki/Man-in-the-middle_attack) as a TLS connection with mutual certificate pinning. -**Second**, it's quite presumptuous (and dangerous) to assume you're an expert in security. New attack vectors come every day, and being aware of all the actual threats is a full-day job. If you do something different during workdays, the security system designed by you will contain vulnerabilities that you have never heard about — for example, your password-checking algorithm might be susceptible to the [timing attack](https://en.wikipedia.org/wiki/Timing_attack), and your web-server, to the [request splitting attack](https://capec.mitre.org/data/definitions/105.html). +**Second**, it's quite presumptuous (and dangerous) to assume you're an expert in security. New attack vectors come every day, and being aware of all the actual threats is a full-day job. If you do something different during workdays, the security system designed by you will contain vulnerabilities that you have never heard about — for example, your password-checking algorithm might be susceptible to the [timing attack](https://en.wikipedia.org/wiki/Timing_attack), and your webserver, to the [request splitting attack](https://capec.mitre.org/data/definitions/105.html). ##### Explicitly declare technical restrictions @@ -1024,7 +1026,7 @@ The same reasoning applies to quotas as well: partners must have access to the s Nowadays the amount of traffic is rarely taken into account — the Internet connection is considered unlimited almost universally. However, it's still not entirely unlimited: with some degree of carelessness, it's always possible to design a system generating the amount of traffic that is uncomfortable even for modern networks. There are three obvious reasons for inflating network traffic: - * no data pagination provided; + * no data pagination is provided; * no limits on the data fields set, or too large binary data (graphics, audio, video, etc.) is being transmitted; * clients query for the data too frequently or cache them too little. @@ -1037,7 +1039,7 @@ If the first two problems are solved by applying pure technical measures (see th * declare an explicit retry policy (for example, with the `Retry-After` header); * yes, some partners will ignore it as developers will get too lazy to implement it, but some will not (especially if you provide the SDKs as well); - * if you expect a significant number of asynchronous operations in the API, allow developers to choose between the poll model (clients make repeated requests to an endpoint to check the asynchronous procedure status) and the push model (the server notifies clients of status changes, for example, via webhooks or server-push mechanics); + * if you expect a significant number of asynchronous operations in the API, allow developers to choose between the poll model (clients make repeated requests to an endpoint to check the asynchronous procedure status) and the push model (the server notifies clients of status changes, for example, via *webhooks* or server-push mechanics); * if some entity comprises both “lightweight” data (let's say, the name and the description of the recipe) and “heavy” data (let's say, the promo picture of the beverage which might easily be a hundred times larger than the text fields), it's better to split endpoints and pass only a reference to the “heavy” data (a link to the image, in our case) — this will allow at least setting different cache policies for different kinds of data. @@ -1081,7 +1083,7 @@ PATCH /v1/orders/{id} This signature is bad per se as it's unreadable. What does `null` as the first array element mean — is it a deletion of an element or an indication that no actions are needed towards it? What happens with the fields that are not stated in the update operation body (`delivery_address`, `milk_type`) — will they be reset to defaults, or stay unchanged? -The nastiest part is that whatever option you choose, the number of problems will only multiply further. Let's say we agreed that the `{"items":[null, {…}]}` statement means that the first element of the array is left untouched, e.g. no changes are needed. Then, how shall we encode its deletion? Invent one more “magical” value meaning “remove it”? Similarly, if the fields that are not explicitly mentioned retain their value — how to reset them to defaults? +The nastiest part is that whatever option you choose, the number of problems will only multiply further. Let's say we agreed that the `{"items":[null, {…}]}` statement means that the first element of the array is left untouched, e.g. no changes are needed. Then, how shall we encode its deletion? Invent one more “magical” value meaning “remove it”? Similarly, if the fields that are not explicitly mentioned retain their value — how would you reset them to defaults? **The simple solution** is always rewriting the data entirely, e.g. to require passing the entire object, to replace the current state with it, and to return the full state as a result of the operation. This obvious solution is frequently rejected with the following reasoning: * increased requests sizes and therefore, the amount of traffic; @@ -1096,7 +1098,7 @@ However, if we take a deeper look, all these disadvantages are actually imaginat * finally, this naïve approach to organizing collaborative editing works only with transitive changes (e.g. if the final result does not depend on the order in which the operations were executed), and in our case, it's already not true: deletion of the first element and editing the second element are non-transitive; * often, in addition to sparing traffic on requests, the same concept is applied to responses as well, e.g. no data is returned for modifying operations; thus two clients making simultaneous edits do not see one another's changes. -**Better**: split the functionality. This also correlates well with the decomposition principle we've discussed in the previous chapter. +**Better**: split the functionality. This also correlates well with the decomposition principle we discussed in the previous chapter. ``` // Creates an order comprising @@ -1161,7 +1163,7 @@ This approach also allows for separating non-mutable and calculated fields (in o It is also possible to return full order objects from `PUT` endpoints instead of just the sub-resource that was overwritten (though it requires some naming convention). -**NB**: while decomposing endpoints, the idea of splitting them into mutable and non-mutable data often looks tempting. It makes possible to mark the latter as infinitely cacheable and never bother about pagination ordering and update format consistency. The plan looks solid on paper, but with the API expansion, it frequently happens that immutable fields eventually cease being immutable, and the entire concept not only stops working properly but even starts looking like a design flaw. We would rather recommend designating data as immutable in one of the two cases: (1) making them editable will really mean breaking backwards compatibility, or (2) the link to the resource (for example, an image) is served via the API as well, and you do possess the capability of making those links persistent (e.g. you might generate a new link to the image instead of rewriting the contents of the old one). +**NB**: while decomposing endpoints, the idea of splitting them into mutable and non-mutable data often looks tempting. It makes it possible to mark the latter as infinitely cacheable and never bother about pagination ordering and update format consistency. The plan looks solid on paper, but with the API expansion, it frequently happens that immutable fields eventually cease being immutable, and the entire concept not only stops working properly but even starts looking like a design flaw. We would rather recommend designating data as immutable in one of the two cases: (1) making them editable will really mean breaking backwards compatibility, or (2) the link to the resource (for example, an image) is served via the API as well, and you do possess the capability of making those links persistent (e.g. you might generate a new link to the image instead of rewriting the contents of the old one). **Even better**: design a format for atomic changes. @@ -1186,7 +1188,7 @@ This approach is much harder to implement, but it's the only viable method to im #### Ensuring API product quality -Apart from the technological limitations, any real API will soon face the imperfection of the surrounding reality. Of course, any one of us would prefer living in the world of pink unicorns, free of piles of legacy code, evil-doers, national conflicts, and competitors' scheming. Fortunately or not, we live in the real world, and API vendors have to mind all those while developing the API. +Apart from the technological limitations, any real API will soon face the imperfection of the surrounding reality. Of course, any one of us would prefer living in the world of pink unicorns, free of piles of legacy code, evil-doers, national conflicts, and competitors' scheming. Fortunately or not, we live in the real world, and API vendors have to mind all of those while developing the API. ##### Use globally unique identifiers @@ -1202,13 +1204,13 @@ One important implication: **never use increasing numbers as external identifier With the API popularity growth, it will inevitably become necessary to introduce technical means of preventing illicit API usage, such as displaying captchas, setting honeypots, raising the “too many requests” exceptions, installing anti-DDoS proxies, etc. All these things cannot be done if the corresponding errors and messages were not described in the docs from the very beginning. -You are not obliged to actually generate those exceptions, but you might stipulate this possibility in the terms of service. For example, you might describe the `429 Too Many Requests` error or captcha redirect, but implement the functionality when it's actually needed. +You are not obliged to actually generate those exceptions, but you might stipulate this possibility in the terms of service. For example, you might describe the `429 Too Many Requests` error or captcha redirect but implement the functionality when it's actually needed. It is extremely important to leave room for multi-factored authentication (such as TOTP, SMS, or 3D-secure-like technologies) if it's possible to make payments through the API. In this case, it's a must-have from the very beginning. ##### Don't provide endpoints for mass downloading of sensitive data -If it's possible to get through the API users' personal data, bank card numbers, private messages, or any other kind of information, exposing of which might seriously harm users, partners, and/or you — there must be *no* methods of bulk getting the data, or at least there must be rate limiters, page size restrictions, and, ideally, multi-factored authentication in front of them. +If it's possible to get through the API users' personal data, bank card numbers, private messages, or any other kind of information, exposing which might seriously harm users, partners, and/or you — there must be *no* methods of bulk getting the data, or at least there must be rate limiters, page size restrictions, and, ideally, multi-factored authentication in front of them. Often, making such offloads on an ad-hoc basis, e.g. bypassing the API, is a reasonable practice. diff --git a/src/en/clean-copy/03-Section II. The Backwards Compatibility/01.md b/src/en/clean-copy/03-Section II. The Backwards Compatibility/01.md index abbd8e4..411f73d 100644 --- a/src/en/clean-copy/03-Section II. The Backwards Compatibility/01.md +++ b/src/en/clean-copy/03-Section II. The Backwards Compatibility/01.md @@ -38,9 +38,9 @@ When you shipped the very first API version, and the very first clients started 1. If the platform allows for fetching code on-demand as the good old Web does, and you weren't too lazy to implement that code-on-demand feature (in a form of a platform SDK — for example, JS API), then the evolution of your API is more or less under your control. Maintaining backwards compatibility effectively means keeping *the client library* backwards-compatible. As for client-server interaction, you're free. - It doesn't mean that you can't break backwards compatibility. You still can make a mess with cache-control headers or just overlook a bug in the code. Besides, even code-on-demand systems don't get updated instantly. The author of this book faced the situation when users were deliberately keeping a browser tab open *for weeks* to get rid of updates. But still, you usually don't have to support more than two API versions — the last one and the penultimate one. Furthermore, you may try to rewrite the previous major version of the library, implementing it on top of the actual API version. + It doesn't mean that you can't break backwards compatibility. You still can make a mess with cache-control headers or just overlook a bug in the code. Besides, even code-on-demand systems don't get updated instantly. The author of this book faced a situation when users were deliberately keeping a browser tab open *for weeks* to get rid of updates. But still, you usually don't have to support more than two API versions — the last one and the penultimate one. Furthermore, you may try to rewrite the previous major version of the library, implementing it on top of the actual API version. - 2. If the code-on-demand feature isn't supported or is prohibited by the platform, as in modern mobile operating systems, then the situation becomes more severe. Each client effectively borrows a snapshot of the code working with your API, frozen at the moment of compilation. Client application updates are scattered over time at much more extent than Web application updates. The most painful thing is that *some clients will never be up to date*, because of one of the three reasons: + 2. If the code-on-demand feature isn't supported or is prohibited by the platform, as in modern mobile operating systems, then the situation becomes more severe. Each client effectively borrows a snapshot of the code working with your API, frozen at the moment of compilation. Client application updates are scattered over time to much more extent than Web application updates. The most painful thing is that *some clients will never be up to date*, because one of three reasons: * developers simply don't want to update the app, e.g. its development stopped; * users don't want to get updates (sometimes because users think that developers “spoiled” the app in new versions); @@ -48,7 +48,7 @@ When you shipped the very first API version, and the very first clients started In modern times these three categories combined could easily constitute tens of per cent of auditory. It implies that cutting the support of any API version might be a nightmare experience — especially if developers' apps continue supporting a more broad spectrum of platforms than the API does. - You could have never issued any SDK, providing just the server-side API, for example in a form of HTTP endpoints. You might think that the backwards compatibility problem is mitigated (by making your API is less competitive on the market because of a lack of SDKs). That's not true: if you don't provide an SDK, then developers will either adopt an unofficial one (if someone bothered to make it) or just write a framework themselves — independently. “Your framework — your problems” strategy, fortunately or not, works badly: if developers write poor quality code upon your API, then your API is of poor quality itself. Definitely in the view of developers, possibly in the view of end-users, if the API performance within the app is visible to them. + You could have never issued any SDK, providing just the server-side API, for example in a form of HTTP endpoints. You might think that the backwards compatibility problem is mitigated (by making your API less competitive on the market because of a lack of SDKs). That's not true: if you don't provide an SDK, then developers will either adopt an unofficial one (if someone bothered to make it) or just write a framework themselves — independently. “Your framework — your problems” strategy, fortunately, or not, works badly: if developers write low-quality code atop your API, then your API is of low quality itself — definitely in the view of developers, possibly in the view of end-users, if the API performance within the app is visible to them. Certainly, if you provide a stateless API that doesn't require client SDKs (or they might be auto-generated from the spec), those problems will be much less noticeable, but not fully avoidable unless you never issue any new API version. If you do, you will still have to deal with some fragmentation of users by API and SDK versions. @@ -66,7 +66,7 @@ Let us also stress that vendors of low-level API are not always as resolute rega #### Platform drift -Finally, there is a third side to a story — the “canyon” you're crossing over with a bridge of your API. Developers write code that is executed in some environment you can't control, and it's evolving. New versions of operating systems, browsers, protocols, and programming language SDKs emerge. New standards are being developed, new arrangements made, some of them being backwards-incompatible, and nothing could be done about that. +Finally, there is a third side to the story — the “canyon” you're crossing over with a bridge of your API. Developers write code that is executed in some environment you can't control, and it's evolving. New versions of operating systems, browsers, protocols, and programming language SDKs emerge. New standards are being developed and new arrangements made, some of them being backwards-incompatible, and nothing could be done about that. Older platform versions lead to fragmentation just like older app versions do, because developers (including the API developers) are struggling with supporting older platforms, and users are struggling with platform updates — and often can't get updated at all, since newer platform versions require newer devices. @@ -83,7 +83,7 @@ Let's briefly describe these decisions and the key factors for making them. 1. How often new major API versions should be released? - That's primarily a *product* question. A new major API version is to be released when the critical mass of functionality is reached — a critical mass of features that couldn't be introduced in the previous API versions, or introducing them is too expensive. In stable markets, such a situation occurs once in several years, usually. In emerging markets, new major API versions might be shipped more frequently, only depending on your ability of supporting the zoo of the previous versions. However, we should note that deploying a new version before the previous one was stabilized (which commonly takes from several months up to a year) is always a troubling sign to developers meaning they're risking dealing with the platform glitches permanently. + That's primarily a *product* question. A new major API version is to be released when the critical mass of functionality is reached — a critical mass of features that couldn't be introduced in the previous API versions, or introducing them is too expensive. In stable markets, such a situation occurs once in several years, usually. In emerging markets, new major API versions might be shipped more frequently, only depending on your ability to support the zoo of the previous versions. However, we should note that deploying a new version before the previous one was stabilized (which commonly takes from several months up to a year) is always a troubling sign to developers meaning they're risking dealing with the platform glitches permanently. 2. How many *major* versions should be supported at a time? @@ -93,21 +93,21 @@ Let's briefly describe these decisions and the key factors for making them. As for minor versions, there are two options: - * if you provide server-side APIs and compiled SDKs only, you may basically do not expose minor versions at all (see below); however, at some maturity stage providing at least two latest versions becomes a must. - * if you provide code-on-demand SDKs, it is considered a good form to provide an access to previous minor versions of SDK for a period of time sufficient enough for developers to test their applicationы and fix issues if necessary. Since minor changes do not require rewriting large portions of code, it's fine to align the lifecycle of a minor version with app release cycle duration in your industry, which is usually several months in worst cases. + * if you provide server-side APIs and compiled SDKs only, you may basically not expose minor versions at all (see below); however, at some maturity stage providing at least two latest versions becomes a must. + * if you provide code-on-demand SDKs, it is considered a good form to provide an access to previous minor versions of SDK for a period of time sufficient enough for developers to test their applications and fix issues if necessary. Since minor changes do not require rewriting large portions of code, it's fine to align the lifecycle of a minor version with the app release cycle duration in your industry, which is usually several months in the worst cases. #### Simultaneous access to several API versions In modern professional software development, especially if we talk about internal APIs, a new API version usually fully replaces the previous one. If some problems are found, it might be rolled back (by releasing the previous version), but the two builds never co-exist. However, in the case of public APIs, the more the number of partner integrations is, the more dangerous this approach becomes. -Indeed, with the growth of the number of users, the “rollback the API version in case of problems” paradigm becomes increasingly destructive. To a partner, the optimal solution is rigidly referencing the specific API version — the one that had been tested (ideally, at the same time having the API vendor somehow seamlessly fixing security concerns and making their software compliant with newly introduced legislation). +Indeed, with the growth of the number of users, the “rollback the API version in case of problems” paradigm becomes increasingly destructive. To a partner, the optimal solution is rigidly referencing the specific API version — the one that had been tested (ideally, at the same time having the API vendor somehow seamlessly fix security concerns and make their software compliant with newly introduced legislation). **NB**. From the same considerations, providing beta (or maybe even alpha) versions of the popular APIs becomes more and more desirable as well, to make partners test the upcoming versions and address the possible issues in advance. The important (and undeniable) advantage of the *semver* system is that it provides the proper version granularity: * stating the first digit (major version) allows for getting a backwards-compatible version of the API; - * stating two digits (major and minor versions) allows to guarantee that some functionality that was added after the initial release will be available; + * stating two digits (major and minor versions) allows guaranteeing that some functionality that was added after the initial release will be available; * finally, stating all three numbers (major version, minor version, and patch) allows for fixing a concrete API release with all its specificities (and errors), which — theoretically — means that the integration will remain operable till this version is physically available. Of course, preserving minor versions infinitely isn't possible (partly because of security and compliance issues that tend to pile up). However, providing such access for a reasonable period of time is rather a hygienic norm for popular APIs. diff --git a/src/en/clean-copy/03-Section II. The Backwards Compatibility/02.md b/src/en/clean-copy/03-Section II. The Backwards Compatibility/02.md index 6b2e7c4..7ecfddb 100644 --- a/src/en/clean-copy/03-Section II. The Backwards Compatibility/02.md +++ b/src/en/clean-copy/03-Section II. The Backwards Compatibility/02.md @@ -37,9 +37,9 @@ let status = api.getStatus(order.id); Let's imagine that you're struggling with scaling your service, and at some point moved to the asynchronous replication of the database. This would lead to the situation when querying for the order status right after the order creation might return `404` if an asynchronous replica hasn't got the update yet. In fact, thus we abandon a strict [consistency policy](https://en.wikipedia.org/wiki/Consistency_model) in a favor of an eventual one. -What would be the result? The code above will stop working. A user creates an order, then tries to get its status — but gets the error. It's very hard to predict what approach developers would implement to tackle this error. Probably, they would not expect this happen at all. +What would be the result? The code above will stop working. A user creates an order, then tries to get its status — but gets the error. It's very hard to predict what approach developers would implement to tackle this error. Probably, they would not expect this to happen at all. -You may say something like, “But we've never promised the strict consistency in the first place” — and that is obviously not true. You may say that if, and only if, you have really described the eventual consistency in the `createOrder` docs, and all your SDK examples look like: +You may say something like, “But we've never promised strict consistency in the first place” — and that is obviously not true. You may say that if, and only if, you have really described the eventual consistency in the `createOrder` docs, and all your SDK examples look like this: ``` let order = api.createOrder(); @@ -61,7 +61,7 @@ if (status) { We presume we may skip the explanations why such code must never be written under any circumstances. If you're really providing a non-strictly consistent API, then either the `createOrder` operation must be asynchronous and return the result when all replicas are synchronized, or the retry policy must be hidden inside the `getStatus` operation implementation. -If you failed to describe the eventual consistency in the first place, then you simply couldn't make these changes in the API. You will effectively break backwards compatibility, which will leads to huge problems with your customers' apps, intensified by the fact they can't be simply reproduced by QA engineers. +If you failed to describe the eventual consistency in the first place, then you simply couldn't make these changes in the API. You will effectively break backwards compatibility, which will lead to huge problems with your customers' apps, intensified by the fact they can't be simply reproduced by QA engineers. **Example \#2**. Take a look at the following code: @@ -129,7 +129,7 @@ GET /v1/orders/{id}/events/history Suppose at some moment we decided to allow trustworthy clients to get their coffee in advance before the payment is confirmed. So an order will jump straight to `"preparing_started"` or even `"ready"` without a `"payment_approved"` event being emitted. It might appear to you that this modification *is* backwards-compatible since you've never really promised any specific event order being maintained, but it is not. -Let's assume that a developer (probably, your company's business partner) wrote some code implementing some valuable business procedure, for example, gathering income and expenses analytics. It's quite logical to expect this code operates a state machine, which switches from one state to another depending on getting (or getting not) specific events. This analytical code will be broken if the event order changes. In the best-case scenario, a developer will get some exceptions and will have to cope with the error's cause; in the worst case, partners will operate wrong statistics for an indefinite period of time until they find the issue. +Let's assume that a developer (probably, your company's business partner) wrote some code implementing some valuable business procedures, for example, gathering income and expenses analytics. It's quite logical to expect this code operates a state machine, which switches from one state to another depending on getting (or getting not) specific events. This analytical code will be broken if the event order changes. In the best-case scenario, a developer will get some exceptions and will have to cope with the error's cause; in the worst case, partners will operate wrong statistics for an indefinite period of time until they find the issue. A proper decision would be, first, documenting the event order and the allowed states; second, continuing generating the "payment_approved" event before the "preparing_started" one (since you're making a decision to prepare that order, so you're in fact approving the payment) and add extended payment information. @@ -141,4 +141,4 @@ State transition graph, event order, possible causes of status changes — such Imagine that one day you start to take phone calls. A client may contact the call center to cancel an order. You might even make this functionality *technically* backwards-compatible, introducing new fields to the “order” entity. But the end-user might simply *know* the number, and call it even if the app wasn't suggesting anything like that. Partner's business analytical code might be broken likewise, or start displaying weather on Mars since it was written knowing nothing about the possibility of canceling orders somehow in circumvention of the partner's systems. -A *technically* correct decision would be to add a “canceling via call center allowed” parameter to the order creation function. Conversely, call center operators might only cancel those orders which were created with this flag set. But that would be a bad decision from a *product* point of view as it's quite unobvious to a user we they can cancel some orders by phone and can't cancel others. The only “good” decision in this situation is to foresee the possibility of external order cancellations in the first place. If you haven't foreseen it, your only option is the “Serenity Notepad” to be discussed in the last chapter of this Section. \ No newline at end of file +A *technically* correct decision would be to add a “canceling via call center allowed” parameter to the order creation function. Conversely, call center operators might only cancel those orders which were created with this flag set. But that would be a bad decision from a *product* point of view as it's quite unobvious to users that they can cancel some orders by phone and can't cancel others. The only “good” decision in this situation is to foresee the possibility of external order cancellations in the first place. If you haven't foreseen it, your only option is the “Serenity Notepad” to be discussed in the last chapter of this Section. \ No newline at end of file diff --git a/src/en/clean-copy/03-Section II. The Backwards Compatibility/03.md b/src/en/clean-copy/03-Section II. The Backwards Compatibility/03.md index 4fb5237..df7b3d9 100644 --- a/src/en/clean-copy/03-Section II. The Backwards Compatibility/03.md +++ b/src/en/clean-copy/03-Section II. The Backwards Compatibility/03.md @@ -37,7 +37,7 @@ PUT /v1/partners/{partnerId}/coffee-machines } ``` -So the mechanics is like that: +So the mechanics are like that: * a partner registers their API types, coffee machines, and supported recipes; * with each incoming order, our server will call the callback function, providing the order data in the stipulated format. @@ -87,9 +87,9 @@ Usually, just adding a new optional parameter to the existing interface is enoug #### Limits of Applicability -Though this exercise looks very simple and universal, its consistent usage is possible only if the hierarchy of entities is well-designed from the very beginning and, which is more important, the vector of the further API expansion is clear. Imagine that after some time passed, the options list got new items; let's say, adding syrup or a second espresso shot. We are totally capable of expanding the list — but not the defaults. So the “default” `PUT /coffee-machines` interface will eventually become totally useless because the default set of three options will not only be any longer of use but will also look ridiculously: why these three options, what are the selection criteria? In fact, the defaults and the method list will be reflecting the historical stages of our API development, and that's totally not what you'd expect from the helpers and defaults nomenclature. +Though this exercise looks very simple and universal, its consistent usage is possible only if the hierarchy of entities is well-designed from the very beginning and, which is more important, the vector of the further API expansion is clear. Imagine that after some time passed, the options list got new items; let's say, adding syrup or a second espresso shot. We are totally capable of expanding the list — but not the defaults. So the “default” `PUT /coffee-machines` interface will eventually become totally useless because the default set of three options will not only be any longer of use but will also look ridiculous: why these three options, what are the selection criteria? In fact, the defaults and the method list will be reflecting the historical stages of our API development, and that's totally not what you'd expect from the helpers and defaults nomenclature. -Alas, this dilemma can't be easily resolved. On one hand, we want developers to write neat and laconic code, so we must provide useful helpers and defaults. On the other hand, we can't know in advance which sets of options will be the most useful after several years of the API expansion. +Alas, this dilemma can't be easily resolved. On one hand, we want developers to write neat and laconic code, so we must provide useful helpers and defaults. On the other hand, we can't know in advance which sets of options will be the most useful after several years of expanding the API. **NB**. We might mask this problem in the following manner: one day gather all these oddities and re-define all the defaults with one single parameter. For example, introduce a special method like `POST /use-defaults {"version": "v2"}` which would overwrite all the defaults with more suitable values. That will ease the learning curve, but your documentation will become even worse after that. diff --git a/src/en/clean-copy/03-Section II. The Backwards Compatibility/04.md b/src/en/clean-copy/03-Section II. The Backwards Compatibility/04.md index f2bb356..89b5023 100644 --- a/src/en/clean-copy/03-Section II. The Backwards Compatibility/04.md +++ b/src/en/clean-copy/03-Section II. The Backwards Compatibility/04.md @@ -1,6 +1,6 @@ ### [Strong Coupling and Related Problems][back-compat-strong-coupling] -To demonstrate the strong coupling problematics let us move to *really interesting* things. Let's continue our “variation analysis”: what if the partners wish to offer not only the standard beverages but their own unique coffee recipes to end-users? The catch is that the partner API as we described it in the previous chapter does not expose the very existence of the partner network to the end-user, and thus describes a simple case. Once we start providing methods to alter the core functionality, not just API extensions, we will soon face next-level problems. +To demonstrate the strong coupling problematics let us move to *really interesting* things. Let's continue our “variation analysis”: what if the partners wish to offer not only the standard beverages but their own unique coffee recipes to end-users? The catch is that the partner API as we described it in the previous chapter does not expose the very existence of the partner network to the end user, and thus describes a simple case. Once we start providing methods to alter the core functionality, not just API extensions, we will soon face next-level problems. So, let us add one more endpoint for registering the partner's own recipe: @@ -34,10 +34,10 @@ The first problem is obvious to those who read the [“Describing Final Interfac "name", "description" }, /* other languages and countries */ … ] -] +} ``` -And here the first big question arises: what should we do with the `default_volume` field? From one side, that's an objective property measured in standardized units, and it's being passed to the program execution engine. On the other side, in countries like the United States, we had to specify beverage volume not like “300 ml,” but “10 fl oz.” We may propose two solutions: +And here the first big question arises: what should we do with the `default_volume` field? From one side, that's an objective property measured in standardized units, and it's being passed to the program execution engine. On the other side, in countries like the United States, we had to specify beverage volumes not like “300 ml,” but “10 fl oz.” We may propose two solutions: * either the partner provides the corresponding number only, and we will make readable descriptions on our own behalf, * or the partner provides both the number and all of its localized representations. @@ -46,11 +46,11 @@ The flaw in the first option is that a partner might be willing to use the servi The localization flaws are not the only problem with this API. We should ask ourselves a question — *why* do we really need these `name` and `description`? They are simply non-machine-readable strings with no specific semantics. At first glance, we need them to return them back in the `/v1/search` method response, but that's not a proper answer: why do we really return these strings from `search`? -The correct answer lies a way beyond this specific interface. We need them *because some representation exists*. There is a UI for choosing beverage type. Probably the `name` and `description` fields are simply two designations of the beverage for a user to read, a short one (to be displayed on the search results page) and a long one (to be displayed in the extended product specification block). It actually means that we set the requirements to the API based on some specific design. But *what if* a partner is making their own UI for their own app? Not only they might not actually need two descriptions, but we are also *deceiving* them. The `name` is not “just a name”, it implies some restrictions: it has recommended length which is optimal to some specific UI, and it must look consistently on the search results page. Indeed, “our best quality™ coffee” or “Invigorating Morning Freshness®” designation would look very weird in-between “Cappuccino,” “Lungo,” and “Latte.” +The correct answer lies a way beyond this specific interface. We need them *because some representation exists*. There is a UI for choosing beverage type. Probably the `name` and `description` fields are simply two designations of the beverage for a user to read, a short one (to be displayed on the search results page) and a long one (to be displayed in the extended product specification block). It actually means that we set the requirements to the API based on some specific design. But *what if* a partner is making their own UI for their own app? Not only they might not actually need two descriptions, but we are also *deceiving* them. The `name` is not “just a name”, it implies some restrictions: it has recommended length which is optimal to some specific UI, and it must look consistently on the search results page. Indeed, the “our best quality™ coffee” or “Invigorating Morning Freshness®” designations would look very weird in-between “Cappuccino,” “Lungo,” and “Latte.” There is also another side to this story. As UIs (both ours' and partners') tend to evolve, new visual elements will be eventually introduced. For example, a picture of the beverage, its energy value, allergen information, etc. The `product_properties` entity will become a scrapyard for tons of optional fields, and learning how setting what field results in what effects in the UI will be an interesting quest, full of probes and mistakes. -Problems we're facing are the problems of *strong coupling*. Each time we offer an interface like described above, we in fact prescript implementing one entity (recipe) based on implementations of other entities (UI layout, localization rules). This approach disrespects the very basic principle of the “top to bottom” API design because **low-level entities must not define high-level ones**. +The problems we're facing are the problems of *strong coupling*. Each time we offer an interface like described above, we in fact prescript implementing one entity (recipe) based on implementations of other entities (UI layout, localization rules). This approach disrespects the very basic principle of the “top to bottom” API design because **low-level entities must not define high-level ones**. #### The rule of contexts @@ -98,9 +98,9 @@ PUT /formatters/volume/ru/US so the above-mentioned `l10n.volume.format` function implementation might retrieve the formatting rules for the new language-region pair and use them. -**NB**: we are more than aware that such a simple format isn't enough to cover real-world localization use-cases, and one either relies on existing libraries or designs a sophisticated format for such templating, which takes into account such things as grammatical cases and rules of rounding numbers up or allow defining formatting rules in a form of function code. The example above is simplified for purely educational purposes. +**NB**: we are more than aware that such a simple format isn't enough to cover real-world localization use cases, and one either relies on existing libraries or designs a sophisticated format for such templating, which takes into account such things as grammatical cases and rules of rounding numbers up or allow defining formatting rules in a form of function code. The example above is simplified for purely educational purposes. -Let us deal with the `name` and `description` problem then. To lower the coupling level there we need to formalize (probably just to ourselves) a “layout” concept. We are asking for providing `name` and `description` not because we just need them, but for representing them in some specific user interface. This specific UI might have an identifier or a semantic name. +Let us deal with the `name` and `description` problem then. To lower the coupling level there, we need to formalize (probably just to ourselves) a “layout” concept. We are asking for providing the `name` and `description` fields not because we just need them, but for representing them in some specific user interface. This specific UI might have an identifier or a semantic name. ``` @@ -190,7 +190,7 @@ POST /v1/recipe-builder } ``` -We should also note that providing a newly created entity identifier by the requesting side isn't exactly the best practice. However, since we decided from the very beginning to keep recipe identifiers semantically meaningful, we have to live on with this convention. Obviously, we're risking getting lots of collisions on recipe names used by different partners, so we actually need to modify this operation: either partners must always use a pair of identifiers (i.e. the recipe id plus partner's own id), or we need to introduce composite identifiers, as we recommended earlier in the [“Describing Final Interfaces”](#api-design-describing-interfaces) chapter. +We should also note that providing a newly created entity identifier by the requesting side isn't exactly the best practice. However, since we decided from the very beginning to keep recipe identifiers semantically meaningful, we have to live on with this convention. Obviously, we're risking getting lots of collisions on recipe names used by different partners, so we actually need to modify this operation: either a partner must always use a pair of identifiers (i.e. the recipe id plus the partner's own id), or we need to introduce composite identifiers, as we recommended earlier in the [“Describing Final Interfaces”](#api-design-describing-interfaces) chapter. ``` POST /v1/recipes/custom diff --git a/src/en/clean-copy/03-Section II. The Backwards Compatibility/05.md b/src/en/clean-copy/03-Section II. The Backwards Compatibility/05.md index ba29277..e678743 100644 --- a/src/en/clean-copy/03-Section II. The Backwards Compatibility/05.md +++ b/src/en/clean-copy/03-Section II. The Backwards Compatibility/05.md @@ -39,7 +39,7 @@ Though this API looks absolutely universal, it's quite easy to demonstrate how o 1. It describes nicely the integrations we've already implemented (it costs almost nothing to support the API types we already know) but brings no flexibility to the approach. In fact, we simply described what we'd already learned, not even trying to look at the larger picture. 2. This design is ultimately based on a single principle: every order preparation might be codified with these three imperative commands. -We may easily disprove the \#2 statement, and that will uncover the implications of the \#1. For the beginning, let us imagine that on a course of further service growth, we decided to allow end-users to change the order after the execution started. For example, request a contactless takeout. That would lead us to creating a new endpoint, let's say, `program_modify_endpoint`, and new difficulties in data format development (as new fields for contactless delivery requested and satisfied flags need to be passed both directions). What *is* important is that both the endpoint and the new data fields would be optional because of backwards compatibility requirement. +We may easily disprove the \#2 statement, and that will uncover the implications of the \#1. For the beginning, let us imagine that on a course of further service growth, we decided to allow end-users to change the order after the execution started. For example, request a contactless takeout. That would lead us to the creation of a new endpoint, let's say, `program_modify_endpoint`, and new difficulties in data format development (as new fields for contactless delivery requested and satisfied flags need to be passed both directions). What *is* important is that both the endpoint and the new data fields would be optional because of the backwards compatibility requirement. Now let's try to imagine a real-world example that doesn't fit into our “three imperatives to rule them all” picture. That's quite easy as well: what if we're plugging not a coffee house, but a vending machine via our API? From one side, it means that the `modify` endpoint and all related stuff are simply meaningless: the contactless takeout requirement means nothing to a vending machine. On the other side, the machine, unlike the people-operated café, requires *takeout approval*: the end-user places an order while being somewhere in some other place then walks to the machine and pushes the “get the order” button in the app. We might, of course, require the user to stand up in front of the machine when placing an order, but that would contradict the entire product concept of users selecting and ordering beverages and then walking to the takeout point. @@ -51,12 +51,12 @@ Furthermore, we have to describe both endpoints in the docs. It's quite natural **NB**: in this example, we assumed that passing `program_takeout_endpoint` parameter is the flag to the application to display the “get the order” button; it would be better to add something like a `supported_flow` field to the `PUT /api-types/` endpoint to provide an explicit flag instead of this implicit convention; however, this wouldn't change the problematics of stockpiling optional methods in the interface, so we skipped it to keep examples laconic. -We actually don't know, whether in the real world of coffee machine APIs this problem will occur or not. But we can say with all confidence regarding “bare metal” integrations that the processes we described *always* happen. The underlying technology shifts; an API that seemed clear and straightforward, becomes a trash bin full of legacy methods, half of which borrows no practical sense under any specific set of conditions. If we add technical progress to the situation, i.e. imagine that after a while all coffee houses have become automated, we will finally end up with the situation with half of the methods *aren't actually needed at all*, like the requesting a contactless takeout one. +We actually don't know, whether in the real world of coffee machine APIs this problem will occur or not. But we can say with all confidence regarding “bare metal” integrations that the processes we described *always* happen. The underlying technology shifts; an API that seemed clear and straightforward, becomes a trash bin full of legacy methods, half of which borrows no practical sense under any specific set of conditions. If we add technical progress to the situation, i.e. imagine that after a while all coffee houses have become automated, we will finally end up with the situation with half of the methods *aren't actually needed at all*, like requesting a contactless takeout one. It is also worth mentioning that we unwittingly violated the abstraction levels isolation principle. At the vending machine API level, there is no such thing as a “contactless takeout,” that's actually a product concept. So, how would we tackle this issue? Using one of two possible approaches: either thoroughly study the entire subject area and its upcoming improvements for at least several years ahead, or abandon strong coupling in favor of a weak one. How would the *ideal* solution look to both parties? Something like this: - * the higher-level program API level doesn't actually know how the execution of its commands works; it formulates the tasks at its own level of understanding: brew this recipe, send user's requests to a partner, allow user to collect their order; + * the higher-level program API level doesn't actually know how the execution of its commands works; it formulates the tasks at its own level of understanding: brew this recipe, send user's requests to a partner, allow the user to collect their order; * the underlying program execution API level doesn't care what other same-level implementations exist; it just interprets those parts of the task that make sense to it. If we take a look at the principles described in the previous chapter, we would find that this principle was already formulated: we need to describe *informational contexts* at every abstraction level and design a mechanism to translate them between levels. Furthermore, in a more general sense, we formulated it as early as in “The Data Flow” paragraph of the [“Separating Abstraction Levels”](#api-design-separating-abstractions) chapter. @@ -65,7 +65,7 @@ In our case we need to implement the following mechanisms: * running a program creates a corresponding context comprising all the essential parameters; * there is the information stream regarding the state modifications: the execution level may read the context, learn about all the changes and report back the changes of its own. -There are different techniques to organize this data flow, but, basically, we always have two contexts and a two-way data pipe in-between. If we were developing an SDK, we would express the idea with emitting and listening events, like this: +There are different techniques to organize this data flow, but, basically, we always have two contexts and a two-way data pipe in between. If we were developing an SDK, we would express the idea with emitting and listening events, like this: ``` /* Partner's implementation of the program @@ -111,7 +111,7 @@ At this point, a mindful reader might begin protesting because if we take a look And this remark is totally correct. Changing API formats doesn't solve any problems related to the evolution of functionality and underlying technology. Changing API formats serves another purpose: to make the code written by developers stay clean and maintainable. Why would strong-coupled integration (i.e. making entities interact via calling methods) render the code unreadable? Because both sides *are obliged* to implement the functionality which is meaningless in their corresponding subject areas. Code that integrates vending machines into the system *must* respond “ok” to the contactless delivery request — so after a while, these implementations would comprise a handful of methods that just always return `true` (or `false`). -The difference between strong coupling and weak coupling is that the field-event mechanism *isn't obligatory to both actors*. Let us remember what we sought to achieve: +The difference between strong coupling and weak coupling is that the field-event mechanism *isn't obligatory for both actors*. Let us remember what we sought to achieve: * a higher-level context doesn't know how low-level API works — and it really doesn't; it describes the changes that occur within the context itself, and reacts only to those events that mean something to it; * a low-level context doesn't know anything about alternative implementations — and it really doesn't; it handles only those events which mean something at its level and emits only those events that could happen under its specific conditions. diff --git a/src/en/clean-copy/03-Section II. The Backwards Compatibility/06.md b/src/en/clean-copy/03-Section II. The Backwards Compatibility/06.md index 21e49e9..ffcc791 100644 --- a/src/en/clean-copy/03-Section II. The Backwards Compatibility/06.md +++ b/src/en/clean-copy/03-Section II. The Backwards Compatibility/06.md @@ -8,12 +8,12 @@ Let us summarize what we have written in the three previous chapters: **NB**. There is nothing novel about these rules: one might easily recognize them being the [SOLID](https://en.wikipedia.org/wiki/SOLID) architecture principles. There is no surprise in that either, because SOLID concentrates on contract-oriented development, and APIs are contracts by definition. We've just added the “abstraction levels” and “informational contexts” concepts there. -However, there is an unanswered question: how should we design the entity nomenclature from the beginning so that extending the API won't make it a mess of different inconsistent methods of different ages. The answer is pretty obvious: to avoid clumsy situations while abstracting (as with the recipe properties), all the entities must be originally considered being a specific implementation of a more general interface, even if there are no planned alternative implementations for them. +However, there is an unanswered question: how should we design the entity nomenclature from the beginning so that extending the API won't make it a mess of different inconsistent methods of different ages? The answer is pretty obvious: to avoid clumsy situations while abstracting (as with the recipe properties), all the entities must be originally considered being a specific implementation of a more general interface, even if there are no planned alternative implementations for them. For example, we should have asked ourselves a question while designing the `POST /search` API: what is a “search result”? What abstract interface does it implement? To answer this question we must neatly decompose this entity to find which facet of it is used for interacting with which objects. Then we would have come to the understanding that a “search result” is actually a composition of two interfaces: - * when we create an order, we need from the search result to provide those fields which describe the order itself; it might be a structure like: + * when we create an order, we need the search result to provide those fields which describe the order itself; it might be a structure like: `{coffee_machine_id, recipe_id, volume, currency_code, price}`, diff --git a/src/en/clean-copy/03-Section II. The Backwards Compatibility/07.md b/src/en/clean-copy/03-Section II. The Backwards Compatibility/07.md index f057ff5..de2a5f8 100644 --- a/src/en/clean-copy/03-Section II. The Backwards Compatibility/07.md +++ b/src/en/clean-copy/03-Section II. The Backwards Compatibility/07.md @@ -20,7 +20,7 @@ Any software must be tested, and APIs ain't an exclusion. However, there are som ##### Isolate the dependencies -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 [“Separating Abstraction Levels”](#api-design-separating-abstractions) chapter 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: +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 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 [“Separating Abstraction Levels”](#api-design-separating-abstractions) chapter 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: * 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; * any partner's problem will automatically ricochet into your customers. diff --git a/src/en/clean-copy/04-Section III. The API Product/01.md b/src/en/clean-copy/04-Section III. The API Product/01.md index 764e283..5819dcf 100644 --- a/src/en/clean-copy/04-Section III. The API Product/01.md +++ b/src/en/clean-copy/04-Section III. The API Product/01.md @@ -4,21 +4,21 @@ There are two important statements regarding APIs viewed as products. 1. APIs are *proper products*, just like any other kind of software. You're “selling” them in the same manner, and all the principles of product management are fully applicable to them. It's quite doubtful you would be able to develop APIs well unless you conducted proper market research, learned customers' needs, and studied competitors, supply, and demand. - 2. Still, APIs are *quite special products*. You're selling a possibility to make some actions programmatically by writing code, and this fact puts some restrictions on product management. + 2. Still, APIs are *quite special products*. You're selling the possibility to make some actions programmatically by writing code, and this fact puts some restrictions on product management. -To properly develop the API product, you must be able to answer exactly this question: why your customers would prefer making some actions programmatically. It's not an idle question: out of this book's author's experience, the product owners' lack of expertise in working with APIs exactly *is* the largest problem of API product development. +To properly develop the API product, you must be able to answer exactly this question: why would your customers prefer making some actions programmatically? It's not an idle question: out of this book's author's experience, the product owners' lack of expertise in working with APIs exactly *is* the largest problem of API product development. -End users don't interact with your API directly but through the application built upon it by software engineers acting on behalf of some company (and sometimes there is more than one engineer in between you and an end user). From this point of view, the API's target audience resembles a Maslow-like pyramid: +End users interact with your API indirectly, through applications built upon it by software engineers acting on behalf of some company (and sometimes there is more than one engineer in between you and an end user). From this point of view, the API's target audience resembles a Maslow-like pyramid: * users constitute the pyramid's base; they look for the fulfillment of their needs and don't think about technicalities; * business owners form a middle level; they match users' needs against technical capabilities declared by developers and build products; * developers make up the pyramid's apex; it is developers who work with APIs directly, and they decide which of the competing APIs to choose. The obvious conclusion of this model is that you must advertise the advantages of your API to developers. They will select the technology, and business owners will translate the concept to end users. If former or acting developers manage the API product, they often tend to evaluate the API market competitiveness in this dimension only and mainly channel the product promotion efforts to the developers' auditory. -'Stop!', the mindful reader must yell at this moment. The actual order of things is exactly the opposite: - * developers are normally a hired workforce implementing the tasks set by business owners (and even if a developer implements a project of his own, they still choose an API which fits the project best, thus being an employer of themselves); - * business leaders don't set tasks out of their sense of style or code elegance; they need some functionality being implemented — one that is needed to solve their customers' problems; - * finally, customers don't care about the technical aspects of the solution; they choose the product they need and ask for some specific functionality implemented. +“Stop!” the mindful reader must yell at this moment. “The actual order of things is exactly the opposite!” + * Developers are normally a hired workforce implementing the tasks set by business owners (and even if a developer implements a project of his own, they still choose an API that fits the project best, thus being an employer of themselves). + * Business leaders don't set tasks out of their sense of style or code elegance; they need some functionality being implemented — one that is needed to solve their customers' problems. + * Finally, customers don't care about the technical aspects of the solution; they choose the product they need and ask for some specific functionality implemented. So it turns out that customers are at the apex of the pyramid: it is customers you need to convince they need not *any* cup of coffee, but a cup of coffee brewed using our API (interesting question: how will we convey the knowledge which API works under the hood, and why customers should pay their money for it!); then business owners will set the task to integrate the API, and developers will have no other choice but to implement it (which, by the way, means that investing into API's readability and consistency is not *that* important). diff --git a/src/en/clean-copy/04-Section III. The API Product/02.md b/src/en/clean-copy/04-Section III. The API Product/02.md index 50e2fa4..1a2a4d8 100644 --- a/src/en/clean-copy/04-Section III. The API Product/02.md +++ b/src/en/clean-copy/04-Section III. The API Product/02.md @@ -1,24 +1,24 @@ ### [The API Business Models][api-product-business-models] -Before we proceed to the API product management principles description, let us draw your attention to the issue of profits that companies providing APIs might extract from it. As we will demonstrate in the next chapters, this issue directly affects making product decisions and setting KPIs for the API team. [In brackets, we will provide examples of such models applicable to our coffee-machine API study.] +Before we proceed to the API product management principles, let us draw your attention to the matter of profits that the API vendor company might extract from it. As we will demonstrate in the next chapters, this is not an idle question as it directly affects making product decisions and setting KPIs for the API team. In this chapter, we will enumerate the main API monetization models. [In brackets, we will provide examples of such models applicable to our coffee-machine API study.] -#### Developers = end users +##### Developers = end users -The easiest and the most understandable case is that of providing a service for developers, with no end users involved. First of all, we talk about different software engineering tools: APIs of programming languages, frameworks, operating systems, UI libraries, game engines, etc; in other words, general-purpose interfaces. [In our coffee API case, it means the following: we've developed a library for ordering a cup of coffee, possibly furnished with UI components, and now selling it to coffeeshop chains owners whoever willing to buy it to ease the development of their own applications.] In this case, the answer to the “why have an API” question is self-evident. +The easiest and the most understandable case is that of providing a service for developers, with no end users involved. First of all, we talk about software engineering tools: APIs of programming languages, frameworks, operating systems, UI libraries, game engines, etc. — general-purpose interfaces, in other words. [In our coffee API case, it means the following: we've developed a library for ordering a cup of coffee, possibly furnished with UI components, and now selling it to coffeeshop chains owners whoever willing to buy it to ease the development of their own applications.] In this case, the answer to the “why have an API” question is self-evident. There is also a plethora of monetizing techniques; in fact, we're just talking about monetizing software for developers. - 1. The framework / library / platform might be paid per se, e.g. distributed under a commercial license. Nowadays such models are becoming less and less popular with the rise of free and open source software but are still quite common. + 1. The framework / library / platform might be paid per se, e.g. distributed under a commercial license. Nowadays such models are becoming less and less popular with the rise of free and open-source software but are still quite common. - 2. The API may be licensed under an open license with some restrictions that might be lifted by buying an extended license. It might be either functional limitations (an inability to publish the app in the app store or an incapacity to build the app in the production mode) or usage restrictions (for example, an open license might be “contagious,” e.g. require publishing the derived code under the same license, or using the API for some purposes might be prohibited). + 2. The API may be licensed under an open license with some restrictions that might be lifted by buying an extended license. It might be either functional limitations (an inability to publish the app in the app store or an incapacity to build the app in the production mode) or usage restrictions (for example, using the API for some purposes might be prohibited or an open license might be “contagious,” e.g. require publishing the derived code under the same license). - 3. The API itself might be free, but the developer company might provide additional paid services (for example, consulting or integrating ones), or just sell the extended technical support. + 3. The API itself might be free, but the API vendor might provide additional paid services (for example, consulting or integrating ones), or just sell the extended technical support. 4. The API development might be sponsored (explicitly or implicitly) by the platform or operating system owners [in our coffee case — by the vendors of smart coffee machines] who are interested in providing a wide range of convenient tools for developers to work with the platform. - 5. Finally, the API developer company might be attracting attention to other related programming tools and hoping to increase sales by publishing the API under a free license. + 5. Finally, by publishing the API under a free license, the API vendor might be attracting attention to other programming tools it makes to increase sales. -Remarkably, such APIs are probably the only “pure” case when developers choose the solution solely because of its clean design, elaborate documentation, thought-out use-cases, etc. There are examples of copying the API design (which is the sincerest form of flattery, as we all know!) by other companies or even enthusiastic communities — that happened, for example, with the Java language API (an alternate implementation by Google) and the C# one (the Mono project) — or just borrowing apt solutions — as it happened with the concept of selecting DOM elements with CSS selectors, initially implemented in the cssQuery project, then adopted by jQuery, and after the latter became popular, incorporated as a part of the DOM standard itself. +Remarkably, such APIs are probably the only “pure” case when developers choose the solution solely because of its clean design, elaborate documentation, thought-out use cases, etc. There are examples of copying the API design (which is the sincerest form of flattery, as we all know!) by other companies or even enthusiastic communities — that happened, for example, with the Java language API (an alternate implementation by Google) and the C# one (the Mono project) — or just borrowing apt solutions — as it happened with the concept of selecting DOM elements with CSS selectors, initially implemented in the *cssQuery* project, then adopted by *jQuery*, and after the latter became popular, incorporated as a part of the DOM standard itself. ##### API = the main and/or the only mean of accessing the service @@ -28,46 +28,46 @@ Usually, customers pay for the service usage, not for the API itself, though fre ##### API = a partner program -Many commercial services provide the access to their platforms for third-party developers to increase sales or attract additional audiences. Examples include the Google Books partner program, Skyscanner Travel APIs, and Uber API. [In our case study, it might be the following model: we are a large chain of coffeeshops, and we encourage partners to sell our coffee through their websites or applications.] Such partnerships are fully commercial: partners monetize their own audience, and the API provider company yearns to get access to extended auditory and additional advertisement channels. As a rule, the API provider company pays for users reaching target goals and sets requirements for the integration performance level (for example, in a form of a minimum acceptable click-target ratio) to avoid misusing the API. +Many commercial services provide the access to their platforms for third-party developers to increase sales or attract additional audiences. Examples include the Google Books partner program, Skyscanner Travel APIs, and Uber API. [In our case study, it might be the following model: we are a large chain of coffee shops, and we encourage partners to sell our coffee through their websites or applications.] Such partnerships are fully commercial: partners monetize their own audience, and the API provider company yearns to get access to extended auditory and additional advertisement channels. As a rule, the API provider company pays for users reaching target goals and sets requirements for the integration performance level (for example, in a form of a minimum acceptable click-target ratio) to avoid misusing the API. ##### API = additional access to the service -If a company possesses some unique expertise, usually in a form of some dataset that couldn't be easily gathered if needed, quite logically a demand for the API exposing this expertise arises. The most classical examples of such APIs are cartographical APIs: collecting detailed and precise geodata and maintaining its freshness is extremely expensive, and a wide range of services would become much more useful if they featured an integrated map. [Our coffee example hardly matches this pattern as the data we accumulate — coffee machines locations, beverages types — is something useless in any other context but ordering a cup of coffee.] +If a company possesses some unique expertise, usually in a form of some dataset that couldn't be easily gathered if needed, quite logically a demand for the API exposing this expertise arises. The most classical examples of such APIs are cartographical APIs: collecting detailed and precise geodata and keeping it up-to-date are extremely expensive, while a wide range of services would become much more useful if they featured an integrated map. [Our coffee example hardly matches this pattern as the data we accumulate — coffee machines locations, beverages types — is something useless in any other context but ordering a cup of coffee.] -This case is the most interesting one from the API developers' point of view as the existence of the API does really serve as a multiplier to the opportunities: the expertise owner could not physically develop all imaginable services utilizing the expertise. Providing the API is a win-win: third-party services got their functionality improved, and the API provider got some profits. +This case is the most interesting one from the API developers' point of view as the existence of the API does really serve as a multiplier to the opportunities as the expertise owner could not physically develop all imaginable services utilizing the expertise but might help others to do it. Providing the API is a win-win: third-party services got their functionality improved, and the API provider got some profits. -Access to the API might be unconditionally paid. However, hybrid models are more common: the API is free till some conditions are met, such as usage limits or constraints (for example, only non-commercial projects are allowed). Sometimes the API is provided for free with minimal restrictions to popularize the platform (for example, Apple Maps). +Access to the API might be unconditionally paid. However, hybrid models are more common: the API is free until some threshold is reached, such as usage limits or constraints (for example, only non-commercial projects are allowed). Sometimes the API is provided for free with minimal restrictions to popularize the platform (for example, Apple Maps). -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. +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 and more frequently need APIs to connect them to 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 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: +**NB**: we rather disapprove the practice of providing an external API merely as a byproduct of the internal one without making any changes to bring value to the market. The main problem with 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; + * documentation only covers 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 later usually doesn't exist. * Any resources spent are directed to covering internal customer needs first. It means the following: - * API development plans are totally opaque to partners, and sometimes look just absurdly with obvious problems being neglected for years; + * API development plans are totally opaque to partners, and sometimes look just absurd with obvious problems being neglected for years; * technical support of external customers is financed on leftovers. All those problems lead to having an external API that actually hurts the company's reputation, not improves it. In fact, you're providing a very bad service for a very critical and skeptical auditory. If you don't have a resource to develop the API as a product for external customers, better don't even start. -##### API = an advertisment site +##### API = an advertisement site -In this case, we talk mostly about widgets and search engines; to display commercials direct access to end users is a must. The most typical examples of such APIs are advertisement networks APIs. However, mixed approaches do exist either — meaning that some API, usually a searching one, goes with commercial incuts. [In our coffee example, it means that the offer searching function will start promoting paid results on the search results page.] +In this case, we talk mostly about widgets and search engines; to display commercials direct access to end users is a must. The most typical examples of such APIs are advertisement networks APIs. However, mixed approaches do exist either — meaning that some API, usually a searching one, goes with commercial insets. [In our coffee example, it means that the offer searching function will start promoting paid results on the search results page.] ##### API = self-advertisement and self-PR -If an API has neither explicit nor implicit monetization, it might still generate some income, increasing the company's brand awareness through displaying logos and other recognizable elements while working with the API, either native (if the API goes with UI elements) or agreed-upon ones (if partners are obliged to embed specifical branding in those places where the API functionality is used or the data acquired through API is displayed). The API provider company's goals in this case are either attracting users to the company's services or just increasing brand awareness in general. [In the case of our coffee API, let's imagine that we're providing some totally unrelated service, like selling tires, and by providing the API we hope to increase brand recognition and get a reputation as an IT company.] +If an API has neither explicit nor implicit monetization, it might still generate some income, increasing the company's brand awareness through displaying logos and other recognizable elements while working with the API, either native (if the API goes with UI elements) or agreed-upon ones (if partners are obliged to embed specific branding in those places where the API functionality is used or the data acquired through API is displayed). The API provider company's goals in this case are either attracting users to the company's services or just increasing brand awareness in general. [In the case of our coffee API, let's imagine that we're providing some totally unrelated service, like selling tires, and by providing the API we hope to increase brand recognition and get a reputation as an IT company.] The target audiences of such self-promotion might also differ: - * you might seek to increase the brand awareness among end users (by embedding logos and links to your services on partner's websites and applications), and even build the brand exclusively through such integrations [for example, if our coffee API provides coffeeshop ratings, and we're working hard on making consumers demand the coffeeshops to publish the ratings]; - * you might concentrate efforts on increasing awareness among business owners [for example, for partners integrating coffee ordering widget on their websites to also pay attention to your tires catalogue]; + * you might seek to increase brand awareness among end users (by embedding logos and links to your services on partner's websites and applications), and even build the brand exclusively through such integrations [for example if our coffee API provides coffeeshop ratings, and we're working hard on making consumers demand the coffeeshops to publish the ratings]; + * you might concentrate efforts on increasing awareness among business owners [for example, for partners integrating a coffee ordering widget on their websites to also pay attention to your tires catalog]; * finally, you might provide APIs only to make developers know your company's name to increase their knowledge of your other products or just to improve your reputation as an employer (this activity is sometimes called “tech-PR”). Additionally, we might talk about forming a community, e.g. the network of developers (or customers, or business owners) who are loyal to the product. The benefits of having such a community might be substantial: lowering the technical support costs, getting a convenient channel for publishing announcements regarding new services and new releases, and obtaining beta users for upcoming products. -##### API = a feedback and UGC tool +##### API = feedback and UGC tool If a company possesses some big data, it might be useful to provide a public API for users to make corrections in the data or otherwise get involved in working with it. For example, cartographical API providers usually allow to post feedback or correct a mistake right on partners' websites and applications. [In the case of our coffee API, we might be collecting feedback to improve the service, both passively through building coffeeshops ratings or actively through contacting business owners to convey users' requests or through finding new coffee shops that are still not integrated with the platform.] @@ -81,12 +81,12 @@ One additional source of income for the API provider is the analysis of the requ #### The API-first approach -Last several years we see the trend of providing some functionality as an API (e.g. as a product for developers) instead of developing the service for end users. This approach, dubbed “API-first,” reflects the growing specialization in the IT world: developing APIs becomes a separate area of expertise that business is ready to out-source instead of spending resources to develop internal APIs for the applications by in-house IT department. However, this approach is not universally accepted (yet), and you should keep in mind the factors that affect the decision of launching a service in the API-first paradigm. +Last several years we see the trend of providing some functionality as an API (e.g. as a product for developers) instead of developing the service for end users. This approach, dubbed “API-first,” reflects the growing specialization in the IT world: developing APIs becomes a separate area of expertise that businesses are ready to outsource instead of spending resources to develop internal APIs for their applications by the in-house IT department. However, this approach is not universally accepted (yet), and you should keep in mind the factors that affect the decision of launching a service in the API-first paradigm. 1. The target market must be sufficiently heated up: there must be companies there that possess enough resources to develop services atop third-party APIs and pay for it (unless your aim is terraforming). 2. The quality of the service must not suffer if the service is provided only through the API. - 3. You must really possess the expertise in API development; otherwise, there are high chances to make too many design mistakes. + 3. You must really possess expertise in API development; otherwise, there are high chances to make too many design mistakes. - Sometimes providing APIs is a method to “probe the ground,” e.g. to evaluate the market and decide whether it's worth having a full-scale user service there. (We rather condemn this practice as it inevitably leads to discontinuing the API or limiting its functionality, either because the market turns out to be not so profitable as expected, or because the API eventually becomes a competitor to the main service.) \ No newline at end of file + Sometimes providing APIs is a method to “probe the ground,” e.g. to evaluate the market and decide whether it's worth having a full-scale user service there. (We rather condemn this practice as it inevitably leads to discontinuing the API or limiting its functionality, either because the market turns out to be not as profitable as expected, or because the API eventually becomes a competitor to the main service.) \ No newline at end of file diff --git a/src/en/clean-copy/04-Section III. The API Product/03.md b/src/en/clean-copy/04-Section III. The API Product/03.md index 0f7873c..527231e 100644 --- a/src/en/clean-copy/04-Section III. The API Product/03.md +++ b/src/en/clean-copy/04-Section III. The API Product/03.md @@ -1,32 +1,32 @@ ### [Developing a Product Vision][api-product-vision] -The above-mentioned fragmentation of the API target audience, e.g. the “developers — business — end users” triad, makes API product management quite a non-trivial problem. Yes, the basics are the same: find your auditory's needs and satisfy them; the problem is that your product has several different auditories, and their interests sometimes diverge. The end users' request for an affordable cup of coffee does not automatically imply business demand for a coffee machine API. +The above-mentioned fragmentation of the API target audience, e.g. the “developers — business — end users” triad, makes API product management quite a non-trivial problem. Yes, the basics are the same: find your auditory's needs and satisfy them; the problem is that your product has several different audiences, and their interests sometimes diverge. The end users' request for an affordable cup of coffee does not automatically imply business demand for a coffee machine API. Generally speaking, the API product vision must include the same three elements: * grasping how end users would like to have their problems solved; * projecting how the business would solve those problems if appropriate tools existed; * understanding what technical solutions might exist and where are the boundaries of their applicability. -In different markets and different situations, the “weight” of each element differs. If you're creating an API-first product for developers with no UI components, you might the skip end users' problems analysis; and, by contrast, if you're providing an API to extremely valuable functionality and you're holding a close-to-monopolistic position on the market, you might actually never care about how developers love your software architecture or how convenient your interfaces are for them, as they simply have no other choice. +In different markets and different situations, the “weight” of each element differs. If you're creating an API-first product for developers with no UI components, you might skip the end users' problems analysis; and, by contrast, if you're providing an API to extremely valuable functionality and you're holding a close-to-monopolistic position on the market, you might actually never care about how developers love your software architecture or how convenient your interfaces are for them, as they simply have no other choice. In the majority of cases, we still have to deal with two-step heuristics based on either technical capabilities or business demands: * you might first form the vision of how you might help business owners given the technical capabilities you have (heuristics step one); then, the general vision of how your API will be used to satisfy end users' needs (heuristics step two); - * or, given your understanding of business owners' problems, you might make one heuristical “step right” to outline future functionality for end users and one “step left” to evaluate possible technical solutions. + * or, given your understanding of business owners' problems, you might make one heuristic “step right” to outline future functionality for end users and one “step left” to evaluate possible technical solutions. -As both approaches are still heuristic, the API product vision is inevitably fuzzy, and it's rather normal: if you could have got a full and clear understanding of what end-user products might be developed on top of your API, you might have developed them on your own behalf, skipping intermediary agents. It is also important to keep in mind that many APIs pass the “terraforming” stage (see the previous chapter) thus preparing the ground for new markets and new types of services — so your idealistic vision of a nearby future where delivering freshly brewed coffee by drones will be a norm of life is to be refined and clarified while new companies providing new kinds of services are coming to the market. (Which in its turn will make an impact on the monetization model: detalizing the countenance of the forthcoming will make your abstract KPIs and theoretical benefits of having an API more and more concrete.) +As both approaches are still heuristic, the API product vision is inevitably fuzzy, and it's rather normal: if you could have got a full and clear understanding of what end-user products might be developed on top of your API, you might have developed them on your own behalf, skipping intermediary agents. It is also important to keep in mind that many APIs pass the “terraforming” stage (see the previous chapter) thus preparing the ground for new markets and new types of services — so your idealistic vision of a nearby future where delivering freshly brewed coffee by drones will be a norm of life is to be refined and clarified while new companies providing new kinds of services are coming to the market. (Which in its turn will make an impact on the monetization model: detailing the countenance of the forthcoming will make your abstract KPIs and theoretical benefits of having an API more and more concrete.) -The same fuzziness should be kept in mind while making interviews and getting feedback. Software engineers will mainly report the problems they've got with the technical integrations, and rarely speak of business-related issues; meanwhile, business owners care little about code-writing inconvenience. Both will have some knowledge regarding the end users problems, but it's usually limited to the market segment the partner operates on. +The same fuzziness should be kept in mind while making interviews and getting feedback. Software engineers will mainly report the problems they've got with the technical integrations, and rarely speak of business-related issues; meanwhile, business owners care little about the inconvenience of writing code. Both will have some knowledge regarding the end users' problems, but it's usually limited to the market segment the partner operates on. -If you do have an access to end users actions monitoring (see [“The API Key Performance Indicators”](#api-product-kpi) chapter), then you might try to analyze the typical user behavior through these logs, and understand how users interact with the partners' applications. But you will need to make this analysis on a per-application basis, and try to clusterize the most common scenarios. +If you do have an access to end users' actions monitoring (see [“The API Key Performance Indicators”](#api-product-kpi) chapter), then you might try to analyze the typical user behavior through these logs and understand how users interact with the partners' applications. But you will need to make this analysis on a per-application basis and try to clusterize the most common scenarios. #### Checking product hypotheses Apart from the general complexity of formulating the product vision, there are also tactical issues with checking product hypotheses. “The Holy Grail” of product management — that is, creating a cheap (in terms of resource spent) minimal viable product (MVP) — is normally unavailable for an API product manager. The thing is that you can't easily *test* the solution even if you managed to develop an API MVP: to do so, partners are to *develop some code*, e.g. invest their money; and if the outcome of the experiment is negative (e.g. the further development looks unpromising), this money will be wasted. Of course, partners will be a little bit skeptical towards such proposals. Thus a “cheap” MVP should include either the compensation for partners' expenses or the budget to develop a reference implementation (e.g. a complementary application must be developed alongside the API MVP). -You might partially solve the problem by making some third-party company release the MVP (for example, in a form of an open source module published in some developer's personal repository) but then you will struggle with hypothesis validation issues as such modules might easily go unnoticed. +You might partially solve the problem by making some third-party company release the MVP (for example, in a form of an open-source module published in some developer's personal repository) but then you will struggle with hypothesis validation issues as such modules might easily go unnoticed. Another option for checking conjectures is recruiting an API provider company's services developers if they exist. Internal customers are usually much more loyal towards spending some effort to check a hypothesis, and it's much easier to negotiate MVP curtailing or freezing with them. The problem is that you can check only those ideas that are relevant to internal services needs. Generally speaking, the “eat your own dogfood” concept applied to APIs means that the product team should have their own test applications (i.e. “pet project”) on top of the APIs. Given the complexity of developing such applications, it makes sense to encourage having them, e.g. giving free API quotas to team members and providing sufficient free computational resources. -Such pet projects are also valuable because of the unique experience they allow to gain: everyone might try a new role. Developers will learn product managers' typical problems: it's not enough to write a fine code, you also need to know your customer, understand their demands, formulate an attractive concept, and communicate it. In their turn, product managers will get some understanding of how exactly easy or hard is to render their product vision into life, and what problems the implementation will bring. Finally, both will benefit from taking a fresh look at the API documentation and putting themselves in the shoes of a developer who had heard about the API product for the first time and is now struggling with grasping the basics. \ No newline at end of file +Such pet projects are also valuable because of the unique experience they allow to gain: everyone might try a new role. Developers will learn product managers' typical problems: it's not enough to write fine code, you also need to know your customer, understand their demands, formulate an attractive concept, and communicate it. In their turn, product managers will get some understanding of how exactly easy or hard is to render their product vision into life, and what problems the implementation will bring. Finally, both will benefit from taking a fresh look at the API documentation and putting themselves in the shoes of a developer who had heard about the API product for the first time and is now struggling with grasping the basics. \ No newline at end of file diff --git a/src/en/clean-copy/04-Section III. The API Product/04.md b/src/en/clean-copy/04-Section III. The API Product/04.md index 53a438f..27686b8 100644 --- a/src/en/clean-copy/04-Section III. The API Product/04.md +++ b/src/en/clean-copy/04-Section III. The API Product/04.md @@ -6,7 +6,7 @@ Let's start with developers. The specifics of software engineers as an auditory * developers are highly-educated individuals with practical thinking; as a rule, they choose technical products with extreme rationality (unless you're giving them cool backpacks with fancy prints for free); - * this doesn't prevent them from having certain aptitude towards, let's say, specific programming languages or mobile OS vendors; however, *affecting* those aptitudes is extremely hard and is normally not in the API vendor's power; + * this doesn't prevent them from having a certain aptitude towards, let's say, specific programming languages or mobile OS vendors; however, *affecting* those aptitudes is extremely hard and is normally not in the API vendor's power; * in particular, developers are quite skeptical towards promotional materials and overstatements and are ready to actually check whether your claims are true; @@ -46,10 +46,10 @@ Finally, just the preparations to make the code open might be very expensive: yo #### The auditory fragmentation -There is one very important addition to the discourse: as informational technologies are universally in great demand, a significant percentage of your customers will not be professional software engineers. A huge number of people are somewhere on a track of mastering the occupation: someone is trying to write code in addition to the basic duties, another one is being retrained now, and the third one is studying the basics of computer science on their own. Many of those non-professional developers make a direct impact on the process of selecting an API vendor — for example, small business owners who additionally seek to automate some routine tasks programmatically. +There is one very important addition to the discourse: as informational technologies are universally in great demand, a significant percentage of your customers will not be professional software engineers. A huge number of people are somewhere on the track of mastering the occupation: someone is trying to write code in addition to the basic duties, another one is being retrained now, and the third one is studying the basics of computer science on their own. Many of those non-professional developers make a direct impact on the process of selecting an API vendor — for example, small business owners who additionally seek to automate some routine tasks programmatically. -It will be more correct if we say that you're actually working for two main auditories: - * professional developers who possess a vast experience in integrating different third-party systems; +It will be more correct if we say that you're actually working for two main types of audiences: + * professional developers who possess vast experience in integrating different third-party systems; * beginners and amateurs, for whom each of those integration tasks would be completely new and unexplored territory. -This fact greatly affects everything we had discussed previously (except for, maybe, open-sourcing, as amateur developers pay little attention to it). Your pitches, webinars, lectures, etc., must somehow fit both professional and semi-professional auditories. There is a popular opinion that you actually can't satisfy both: the former seeks extensive customization capabilities (as they usually work in big IT companies that have a specific mindset regarding those integrations) while the latter just needs the gentlest possible learning curve. We would rather disagree with that, the reasons to be discussed in [“The API Services Range”](#api-product-range) chapter. +This fact greatly affects everything we had discussed previously (except for, maybe, open-sourcing, as amateur developers pay little attention to it). Your pitches, webinars, lectures, etc., must somehow fit both professional and semi-professional audiences. There is a popular opinion that you actually can't satisfy both: the former seeks extensive customization capabilities (as they usually work in big IT companies that have a specific mindset regarding those integrations) while the latter just needs the gentlest possible learning curve. We would rather disagree with that, the reasons to be discussed in [“The API Services Range”](#api-product-range) chapter. diff --git a/src/en/clean-copy/04-Section III. The API Product/05.md b/src/en/clean-copy/04-Section III. The API Product/05.md index 00a38ea..66f7d57 100644 --- a/src/en/clean-copy/04-Section III. The API Product/05.md +++ b/src/en/clean-copy/04-Section III. The API Product/05.md @@ -6,6 +6,6 @@ The basics of interacting with business partners are to some extent paradoxicall After all, working with business auditory essentially means lucidly explaining the characteristics and the advantages of the product. In that sense, API “sells” just like any other kind of software. -As a rule, the farther some industry sector is from information technologies, the more enthusiastic its representatives are about your API features, and the less the chance is that this enthusiasm will be converted into a real integration. The one thing that should help the case is extensive work with the developer community (see the previous chapter) that will result in establishing a circle of freelances and outsourcers eager to help non-IT businesses with integrations. You might help develop this market by the creation of educational courses and issuing certificates proving the bearer's skills of working with your API (or some broader layer of technology). +As a rule, the farther some industry sector is from information technologies, the more enthusiastic its representatives are about your API features, and the less the chance is that this enthusiasm will be converted into a real integration. The one thing that should help the case is extensive work with the developer community (see the previous chapter) that will result in establishing a circle of freelancers and outsourcers eager to help non-IT businesses with integrations. You might help develop this market by creating educational courses and issuing certificates proving the bearer's skills in working with your API (or some broader layer of technology). Market research and getting feedback from business owners work similarly. Those businesses that are far from IT usually can't formulate their demands, so you should be rather creative (and critical-minded) while analyzing the gathered data. \ No newline at end of file diff --git a/src/en/clean-copy/04-Section III. The API Product/06.md b/src/en/clean-copy/04-Section III. The API Product/06.md index 00a4353..2f0f699 100644 --- a/src/en/clean-copy/04-Section III. The API Product/06.md +++ b/src/en/clean-copy/04-Section III. The API Product/06.md @@ -1,6 +1,6 @@ ### [The API Services Range][api-product-range] -The important rule of API product management that any major API provider will soon learn formulates like that: there is no sense to ship one specific API; there is always a 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 formulates like that: there is no sense to ship one specific API; there is always room for a range of products, and this range is two-dimensional. #### Horizontal scaling of API services @@ -17,8 +17,8 @@ Different companies employ different approaches to determining the granularity o #### Vertical scaling of API services However, frequently it makes sense to provide several API services manipulating the same functionality. The thing is that the fragmentation of API customers across their professional skill levels results in several important implications: - * it's almost impossible in a course of a single product to create an API that will fit well both amateur developers and professional ones: the former need the maximum simplicity of implementing basic use cases, while the latter seek the ability to adapt the API to match technological stack and development paradigms, and the problems they solve usually require deep customization; - * the huge share of customers' inquiries to your customer support service will be generated by the first category of developers: it's much harder for amateurs or beginners to find answers to their questions by themselves, and they will address them to you; + * it's almost impossible in a course of a single product to create an API that will fit well both amateur and professional developers: the former need the maximum simplicity of implementing basic use cases, while the latter seek the ability to adapt the API to match technological stack and development paradigms, and the problems they solve usually require deep customization; + * a huge share of customers' inquiries to your customer support service will be generated by the first category of developers: it's much harder for amateurs or beginners to find answers to their questions by themselves, and they will address them to you; * 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. The most important conclusion here is that the only way to cover the needs of all categories of customers is to develop a range of products with different entry thresholds and requirements for developers' professional level. We might name several API sub-types, ordered from the most technically demanding to less complex ones. @@ -30,12 +30,12 @@ The most important conclusion here is that the only way to cover the needs of al Ultimately, we will end up with a concept of meta-API, e.g. those high-level components will have an API of their own built on top of the basic API. -The most 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 apps. +The most 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 apps. 1. The apps 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. The apps that operate base APIs will let you manipulate underlying abstraction levels — move to newer technologies or alter the way search results are presented to an end user. 3. SDKs, especially those proving UI components, provide a higher degree of control over the look and feel of partners' applications, which allows you to evolve the UI, adding new interactive elements and enriching the functionality of existing ones. [For example, if our coffee SDK contains the 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 a number of searches performed through the API, we might alter the generated code so it will show the search panel in the most convenient position in the app; as partners using code-generation services rarely make any changes in the resulting code, this will help you in reaching the goal. - 5. Finally, ready-to-use components and widgets are under your full control, and you might experiment with functionality exposed through them in partners' applications just like if it was your own service. (However, it doesn't automatically mean that you might draw some profits from having this control; for example, if you're allowing inserting pictures by their direct URL, your control over this integration is rather negligible, so it's generally better to provide those kinds of integration that allow having more control over the functionality in partners' apps.) + 5. Finally, ready-to-use components and widgets are under your full control, and you might experiment with functionality exposed through them in partners' applications just as if it was your own service. (However, it doesn't automatically mean that you might draw some profits from having this control; for example, if you're allowing inserting pictures by their direct URL, your control over this integration is rather negligible, so it's generally better to provide those kinds of integration that allow having more control over the functionality in partners' apps.) **NB**. While developing a “vertical” range of APIs, following the principles stated in the [“On the Waterline of the Iceberg”](#back-compat-iceberg-waterline) chapter is crucial. You might manipulate widget content and behavior if, and only if, developers can't “escape the sandbox,” e.g. have direct access to low-level objects encapsulated within the widget. diff --git a/src/en/clean-copy/04-Section III. The API Product/07.md b/src/en/clean-copy/04-Section III. The API Product/07.md index 4ddb96e..be52e9c 100644 --- a/src/en/clean-copy/04-Section III. The API Product/07.md +++ b/src/en/clean-copy/04-Section III. The API Product/07.md @@ -1,6 +1,6 @@ ### [The API Key Performance Indicators][api-product-kpi] -As we described in the previous chapters, there are many API monetization models, both direct and indirect. Importantly, most of them are fully or conditionally free for partners, and the direct to indirect benefits ratio tends to change during the API lifecycle. That naturally leads us to the question of how exactly shall we measure the API success and what goals are to be set for the product team. +As we described in the previous chapters, there are many API monetization models, both direct and indirect. Importantly, most of them are fully or conditionally free for partners, and the direct-to-indirect benefits ratio tends to change during the API lifecycle. That naturally leads us to the question of how exactly shall we measure the API success and what goals are to be set for the product team. Of course, the most explicit metric is money: if your API is monetized directly or attracts visitors to a monetized service, the rest of the chapter will be of little interest to you, maybe just as a case study. If, however, the contribution of the API to the company's income cannot be simply measured, you have to stick to other, synthetic, indicators. @@ -16,7 +16,7 @@ However, sheer numbers might be deceiving, especially if we talk about free-to-u * put widgets somewhere deep in the page / screen footer, or hide it behind spoilers; * initialize a broad range of API modules, but use only a limited subset of them; - * the greater the API auditory is, the less the number of unique visitors means, as at some moment the penetration will be close to 100%; for example, a regular Internet user interacts with Google or Facebook counters, well, every minute, so the daily audience of those API fundamentally cannot be increased further. + * the greater the API auditory is, the less the number of unique visitors means as at some moment the penetration will be close to 100%; for example, a regular Internet user interacts with Google or Facebook counters, well, every minute, so the daily audience of those API fundamentally cannot be increased further. All the abovementioned problems naturally lead us to a very simple conclusion: not only the raw numbers of users and partners are to be gauged, but their engagement as well, e.g. the target actions (such as searching, observing some data, interacting with widgets) shall be determined and counted. Ideally, these target actions must correlate with the API monetization model: @@ -26,7 +26,7 @@ All the abovementioned problems naturally lead us to a very simple conclusion: n Additionally, the functional KPIs are often employed: how frequently some API features are used. (Also, it helps with prioritizing further API improvements.) In fact, that's still measuring target actions, but those that are made by developers, not end users. It's rather complicated to gather the usage data for software libraries and frameworks, though still doable (however, you must be extremely cautious with that, as any auditory rather nervously reacts to finding that some statistic is gathered automatically). -The most complicated case is that of API being a tool for (tech)PR and (tech)marketing. In this case, there is a cumulative effect: increasing the API audience doesn't momentarily bring any profit to the company. *First* you got a loyal developer community, *then* this reputation helps you to hire people. *First* your company's logo flashes on the third-party webpages and applications, *then* the top-of-mind brand knowledge increases. There is no direct method of evaluating how some action (let's say, a new release or an event for developers) affects the target metrics. In this case, you have to operate indirect metrics, such as the audience of the documentation site, the number of mentions in the relevant communication channels, the popularity of your blogs and seminars, etc. +The most complicated case is that of API being a tool for (tech)PR and (tech)marketing. In this case, there is a cumulative effect: increasing the API audience doesn't momentarily bring any profit to the company. *First*, you got a loyal developer community, *then* this reputation helps you to hire people. *First*, your company's logo flashes on third-party webpages and applications, *then* the top-of-mind brand knowledge increases. There is no direct method of evaluating how some action (let's say, a new release or an event for developers) affects the target metrics. In this case, you have to operate indirect metrics, such as the audience of the documentation site, the number of mentions in the relevant communication channels, the popularity of your blogs and seminars, etc. Let us summarize the paragraph: * counting direct metrics such as the total number of users and partners is a must and is totally necessary for moving further, but that's not a proper KPI; @@ -37,7 +37,7 @@ Let us summarize the paragraph: This chapter would be incomplete if we didn't mention the “hygienic” KPI — the service level and the service availability. We won't be describing the concept in detail, as the API SLA isn't any different from any other digital services SLAs. Let us point out that this metric must be tracked, especially if we talk about pay-to-use APIs. However, in many cases, API vendors prefer to offer rather loose SLAs, treating the provided functionality as data access or a content licensing service. -Still, let us re-iterate once more: any problems with your API are automatically multiplied by the number of partners you have, especially if the API is vital for them, e.g. the API outage makes the main functionality of their services unavailable. (And actually, because of the above-mentioned reasons, the average quality of integrations implies that partners' services will suffer even if the API is not formally speaking critical for them, but because developers use it excessively and do not bother with proper error handling.) +Still, let us re-iterate once more: any problems with your API are automatically multiplied by the number of partners you have, especially if the API is vital for them, e.g. the API outage makes the main functionality of their services unavailable. (And actually, because of the above-mentioned reasons, the average quality of integrations implies that partners' services will suffer even if the availability of the API is not formally speaking critical for them, but because developers use it excessively and do not bother with proper error handling.) It is important to mention that predicting the workload for the API service is rather complicated. Sub-optimal API usage, e.g. initializing the API in those application and website parts where it's not actually needed, might lead to a colossal increase in the number of requests after changing a single line of partner's code. The safety margin for an API service must be much higher than for a regular service for end users — it must survive the situation of the largest partner suddenly starting querying the API on every page and every application screen. (If the partner is already doing that, then the API must survive doubling the load: imagine the partner accidentally starts initializing the API twice on each page / screen.) @@ -47,7 +47,7 @@ Another extremely important hygienic minimum is the informational security of th While measuring KPIs of any service, it's important not only to evaluate your own numbers but also to match them against the state of the market: * what is your market share, and how is it evolving over time? - * is your service growing faster than the market itself, or is the rate the same, or is it even less? + * is your service growing faster than the market itself or is the rate the same, or is it even less? * what proportion of the growth is caused by the growth of the market, and what is related to your efforts? -Getting answers to those questions might be quite non-trivial in the case of the API services. Indeed, how could you learn how many integrations have your competitor had during the same period of time, and what number of target actions had happened on their platform? Sometimes, the providers of popular analytical tools might help you with this, but usually, you have to monitor the potential partners' apps and websites and gather the statistics regarding APIs they're using. The same applies to market research: unless your niche is significant enough for some analytical company to conduct market research, you will have to either commission such a study or make your own estimations — conversely, through interviewing potential customers. \ No newline at end of file +Getting answers to those questions might be quite non-trivial in the case of API services. Indeed, how could you learn how many integrations has your competitor had during the same period of time, and what number of target actions had happened on their platform? Sometimes, the providers of popular analytical tools might help you with this, but usually, you have to monitor the potential partners' apps and websites and gather the statistics regarding APIs they're using. The same applies to market research: unless your niche is significant enough for some analytical company to conduct market research, you will have to either commission such a study or make your own estimations — conversely, through interviewing potential customers. \ No newline at end of file diff --git a/src/en/clean-copy/04-Section III. The API Product/08.md b/src/en/clean-copy/04-Section III. The API Product/08.md index d05730a..caddde9 100644 --- a/src/en/clean-copy/04-Section III. The API Product/08.md +++ b/src/en/clean-copy/04-Section III. The API Product/08.md @@ -7,17 +7,17 @@ In the context of working with an API, we talk about two kinds of users of the s In most cases, you need to have both of them identified (in a technical sense: discern one unique visitor from another) to have answers to the following questions: - * how many users are interacting with the system (simultaneously, daily, monthly, and yearly); - * how many actions does each user make. + * how many users are interacting with the system (simultaneously, daily, monthly, and yearly)? + * how many actions does each user make? **NB**. Sometimes, when an API is very large and/or abstract, the chain linking the API vendor to end users might comprise more than one developer as large partners provide services implemented atop of the API to the smaller ones. You need to count both direct and “derivative” partners. Gathering this data is crucial because of two reasons: * to understand the system's limits and to be capable of planning its growth; - * to understand the amount of resources (ultimately, money) that are spent (and gained) on each user. + * to understand the number of resources (ultimately, money) that are spent (and gained) on each user. -In the case of commercial APIs, the quality and timeliness of gathering this data are twice that important as the tariff plans (and therefore the entire business model) depend on it. Therefore, the question of *how exactly* we're identifying users is crucial. +In the case of commercial APIs, the quality and timeliness of gathering this data are twice that important, as the tariff plans (and therefore the entire business model) depend on it. Therefore, the question of *how exactly* we're identifying users is crucial. #### Identifying applications and their owners @@ -29,11 +29,11 @@ In the modern world, the factual standard for identifying both entities is using Though this practice is universally widespread we can't but notice that in most cases it's useless, and sometimes just destructive. -Its general advantage is the necessity to supply actual contact info to get a key, which theoretically allows for contacting the application owner if needed. (In the real world, it doesn't work: key owners often don't read mailboxes they provided upon registration; and if the owner is a company, it easily might be a no one's mailbox or a personal email of some employee that left the company a couple of years ago.) +Its general advantage is the necessity to supply actual contact info to get a key, which theoretically allows for contacting the application owner if needed. (In the real world, it doesn't work: key owners often don't read mailboxes they provided upon registration; and if the owner is a company, it easily might be a no-one's mailbox or a personal email of some employee that left the company a couple of years ago.) The main disadvantage of using API keys is that they *don't* allow for reliably identifying both applications and their owners. -If there are free limits to the API usage, there is a temptation to obtain many API keys bound to different owners to fit those free limits. You may raise the bar of having such multi-accounts by requiring, let's say, providing a phone number or a bank card data, but there are popular services for automatically issuing both. Paying for a virtual SIM or credit card (to say nothing about buying the stolen ones) will always be cheaper than paying the proper API tariff — unless it's the API for creating those cards. Therefore, API key-based user identification (if you're not requiring the physical contract to be signed) does not mean you don't need to double-check whether users comply with the terms of service and do not issue several keys for one app. +If there are free limits to API usage, there is a temptation to obtain many API keys bound to different owners to fit those free limits. You may raise the bar of having such multi-accounts by requiring, let's say, providing a phone number or bank card data, but there are popular services for automatically issuing both. Paying for a virtual SIM or credit card (to say nothing about buying the stolen ones) will always be cheaper than paying the proper API tariff — unless it's the API for creating those cards. Therefore, API key-based user identification (if you're not requiring the physical contract to be signed) does not mean you don't need to double-check whether users comply with the terms of service and do not issue several keys for one app. Another problem is that an API key might be simply stolen from a lawful partner; in the case of client or web applications, that's quite trivial. @@ -55,13 +55,13 @@ The general conclusion is: #### Identifying end users -Usually, you can put forward some requirements for self-identifying of partners, but asking end users to reveal contact information is impossible in the most cases. All the methods of measuring the audience described below are imprecise and often heuristic. (Even if partner application functionality is only available after registration and you do have access to that profile data, it's still a game of assumptions, as an individual account is not the same as an individual user: several different persons might use a single account, or, vice versa, one person might register many accounts.) Also, note that gathering this sort of data might be legally regulated (though we will be mostly speaking about anonymized data, there might still be some applicable law). +Usually, you can put forward some requirements for self-identifying of partners, but asking end users to reveal contact information is impossible in most cases. All the methods of measuring the audience described below are imprecise and often heuristic. (Even if partner application functionality is only available after registration and you do have access to that profile data, it's still a game of assumptions, as an individual account is not the same as an individual user: several different persons might use a single account, or, vice versa, one person might register many accounts.) Also, note that gathering this sort of data might be legally regulated (though we will be mostly speaking about anonymized data, there might still be some applicable law). 1. The most simple and obvious indicator is an IP address. It's very hard to counterfeit them (e.g. the API server always knows the remote address), and the IP address statistics are reasonably demonstrative. If the API is provided as a server-to-server one, there will be no access to the end user's IP address. However, it makes sense to require partners to propagate the IP address (for example, in a form of the `X-Forwarder-For` header) — among other things, to help partners fight fraud and unintended usage of the API. - Until recently, IP addresses were also a convenient statistics indicator because it was quite expensive to get a large pool of unique addresses. However, with ipv6 advancement this restriction is no longer actual; ipv6 rather put the light on a fact that you can't just count unique addresses — the aggregates are to be tracked: + Until recently, IP addresses were also a convenient statistics indicator because it was quite expensive to get a large pool of unique addresses. However, with ipv6 advancement this restriction is no longer actual; ipv6 rather put the light on the fact that you can't just count unique addresses — the aggregates are to be tracked: * the cumulative number of requests by networks, e.g. the hierarchical calculations (the number of /8, /16, /24, etc. networks) * the cumulative statistics by autonomous networks (AS); * the API requests through known public proxies and TOR network. @@ -70,6 +70,6 @@ Usually, you can put forward some requirements for self-identifying of partners, 2. Additional means of tracking are users' unique identifiers, most notably cookies. However, most recently this method of gathering data is under attack from several sides: browser makers restrict third-party cookies, users are employing anti-tracker software, and lawmakers started to roll out legal requirements against data collection. In the current situation, it's much easier to drop cookie usage than to be compliant with all the regulations. - All this leads to a situation when public APIs (especially those installed on free-to-use sites and applications) are very limited in the means of collecting the statistics and analyzing user behavior. And that impacts not only fighting all kinds of fraud but analyzing use cases as well. That's the way. + All this leads to a situation when public APIs (especially those installed on free-to-use sites and applications) are very limited in the means of collecting statistics and analyzing user behavior. And that impacts not only fighting all kinds of fraud but analyzing use cases as well. That's the way. **NB**. In some jurisdictions, IP addresses are considered personal data, and collecting them is prohibited as well. We don't dare to advise on how an API vendor might at the same time be able to fight prohibited content on the platform and don't have access to users' IP addresses. We presume that complying with such legislation implies storing statistics by IP address hashes. (And just in case we won't mention that building a rainbow table for SHA-256 hashes covering the entire 4-billion range of IPv4 addresses would take several hours on a regular office-grade computer.) diff --git a/src/en/clean-copy/04-Section III. The API Product/09.md b/src/en/clean-copy/04-Section III. The API Product/09.md index 1d76337..ec8c100 100644 --- a/src/en/clean-copy/04-Section III. The API Product/09.md +++ b/src/en/clean-copy/04-Section III. The API Product/09.md @@ -5,13 +5,13 @@ Implementing the paradigm of a centralized system of preventing partner endpoint The task of filtering out illicit API requests generally comprises three steps: * identifying suspicious users; * optionally, asking for an additional authentication factor; - * making decisions and applying the access restrictions. + * making decisions and applying access restrictions. ##### Identifying suspicious users Generally speaking, there are two approaches we might take, the static one and the dynamic (behavioral) one. -*Statically* we monitor suspicions activity surges, as described in the previous chapter, marking an unusually high density of requests coming from specific networks or Referers (actually, *any* piece of information suits if it divides users into more or less independent groups: for example, OS version or system language if you can gather those). +*Statically* we monitor suspicions activity surges, as described in the previous chapter, marking an unusually high density of requests coming from specific networks or Referer's (actually, *any* piece of information suits if it divides users into more or less independent groups: for example, OS version or system language if you can gather those). *Behavioral* analysis means we're examining the history of requests made by a specific user, searching for non-typical patterns, such as “unhuman” order of traversing endpoints or too small pauses between requests. @@ -19,11 +19,11 @@ Generally speaking, there are two approaches we might take, the static one and t ##### Requesting an additional authentication factor -As both static and behavioral analyses are heuristic, it's highly desirable to not make decisions based solely on their outcome, but rather ask the suspicious users to additionally prove they're making legitimate requests. If such a mechanism is in place, the quality of an anti-fraud system will be dramatically improved, as it allows for increasing system sensitivity and enabling pro-active defense, e.g. asking users to pass the tests in advance. +As both static and behavioral analyses are heuristic, it's highly desirable to not make decisions based solely on their outcome but rather ask the suspicious users to additionally prove they're making legitimate requests. If such a mechanism is in place, the quality of an anti-fraud system will be dramatically improved, as it allows for increasing system sensitivity and enabling pro-active defense, e.g. asking users to pass the tests in advance. -In the case of services for end users, the main method of acquiring the second factor is redirecting to a captcha page. In the case of the API it might be problematic, especially if you initially neglected the “Stipulate Restrictions” rule we've given in the [“Describing Final Interfaces”](#api-design-describing-interfaces) chapter. In many cases, you will have to impose this responsibility on partners (e.g. it will be partners who show captchas and identify users based on the signals received from the API endpoints). This will, of course, significantly impair the convenience of working with the API. +In the case of services for end users, the main method of acquiring the second factor is redirecting to a captcha page. In the case of APIs it might be problematic, especially if you initially neglected the “Stipulate Restrictions” rule we've given in the [“Describing Final Interfaces”](#api-design-describing-interfaces) chapter. In many cases, you will have to impose this responsibility on partners (e.g. it will be partners who show captchas and identify users based on the signals received from the API endpoints). This will, of course, significantly impair the convenience of working with the API. -**NB**. Instead of captcha, there might be any other actions introducing additional authentication factors. It might be the phone number confirmation or the second step of the 3D-Secure protocol. The important part is that requesting an additional authentication step must be stipulated in the program interface, as it can't be added later in a backwards-compatible manner. +**NB**. Instead of captcha, there might be other actions introducing additional authentication factors. It might be the phone number confirmation or the second step of the 3D-Secure protocol. The important part is that requesting an additional authentication step must be stipulated in the program interface, as it can't be added later in a backwards-compatible manner. Other popular mechanics of identifying robots include offering a bait (“honeypot”) or employing the execution environment checks (starting from rather trivial like executing JavaScript on the webpage and ending with sophisticated techniques of checking application integrity). @@ -45,7 +45,7 @@ Thereby, you have only one method that really works: filing complaints to hostin In most cases, you're not fighting fraud — you're actually increasing the cost of the attack, simultaneously buying yourself enough time to make administrative moves against the perpetrator. Preventing API misusage completely is impossible as malefactors might ultimately employ the expensive but bulletproof solution — to hire real people to make the requests to the API on real devices through legal applications. -An opinion exists, which the author of this book shares, that engaging into this sword-against-shield confrontation must be carefully thought out, and advanced technical solutions are to be enabled only if you are one hundred percent sure it is worth it (e.g. if they steal real money or data). By introducing elaborate algorithms, you rather conduct an evolutional selection of the smartest and most cunning cybercriminals, counteracting whom will be way harder than those who just naively call API endpoints with `curl`. What is even more important, in the final phase — e.g. when filing the complaint to authorities — you will have to prove the alleged ToS violation, and doing so against an advanced fraudster will be problematic. So it's rather better to have all the malefactors monitored (and regularly complained against), and escalate the situation (e.g. enable the technical protection and start legal actions) only if the threat passes a certain threshold. That also implies that you must have all the tools ready, and just keep them below fraudsters' radars. +An opinion exists, which the author of this book shares, that engaging in this sword-against-shield confrontation must be carefully thought out, and advanced technical solutions are to be enabled only if you are one hundred percent sure it is worth it (e.g. if they steal real money or data). By introducing elaborate algorithms, you rather conduct an evolutional selection of the smartest and most cunning cybercriminals, counteracting who will be way harder than those who just naively call API endpoints with `curl`. What is even more important, in the final phase — e.g. when filing the complaint to authorities — you will have to prove the alleged ToS violation, and doing so against an advanced fraudster will be problematic. So it's rather better to have all the malefactors monitored (and regularly complained against), and escalate the situation (e.g. enable the technical protection and start legal actions) only if the threat passes a certain threshold. That also implies that you must have all the tools ready, and just keep them below fraudsters' radars. Out of the author of this book's experience, the mind games with malefactors, when you respond to any improvement of their script with the smallest possible effort that is enough to break it, might continue indefinitely. This strategy, e.g. making fraudsters guess which traits were used to ban them this time (instead of unleashing the whole heavy artillery potential), annoys amateur “hackers” greatly as they lack hard engineering skills and just give up eventually. @@ -57,17 +57,17 @@ Let's now move to the second type of unlawful API usage, namely using in the mal 2. Allowing partners to restrict the functionality available under specific API keys: - * setting the allowed IP address range for server-to-server APIs, allowed Referers and application ids for client APIs; + * setting the allowed IP address range for server-to-server APIs, allowed Referer's and application ids for client APIs; * white-listing only allowed API functions for a specific key; - * other restrictions that make sense in your case (in our coffee API example, it's convenient to allow partners prohibiting API calls outside of countries and cities they work in). + * other restrictions that make sense in your case (in our coffee API example, it's convenient to allow partners to prohibit API calls outside of countries and cities they work in). 3. Introducing additional request signing: - * for example, if on the partner's website there is a form displaying the best lungo offers, for which the partners call the API endpoint like `/v1/search?recipe=lungo&api_key={apiKey}`, then the API key might be replaced with a signature like `sign = HMAC("recipe=lungo", apiKey)`; the signature might be stolen as well, but it will be useless for malefactors as they will be able to find only lungo with it; + * for example, if on the partner's website, there is a form displaying the best lungo offers, for which the partners call the API endpoint like `/v1/search?recipe=lungo&api_key={apiKey}`, then the API key might be replaced with a signature like `sign = HMAC("recipe=lungo", apiKey)`; the signature might be stolen as well, but it will be useless for malefactors as they will be able to find only lungo with it; - * instead of API keys, time-based one-time passwords (TOTP) might be used; these tokens are valid during a short period of time (usually, one minute) only, which makes working with stealing keys much more sophisticated. + * instead of API keys, time-based one-time passwords (TOTP) might be used; these tokens are valid for a short period of time (usually, one minute) only, which makes working with stealing keys much more sophisticated. 4. Filing complaints to the administration (hosting providers, app store owners) in case the malefactor distributes their application through stores or uses a diligent hosting service that investigates abuse filings. Legal actions are also an option, and even much so compared to countering user fraud, as illegal access to the system using stolen credentials is unambiguously outlawed in most jurisdictions. diff --git a/src/en/clean-copy/04-Section III. The API Product/10.md b/src/en/clean-copy/04-Section III. The API Product/10.md index 7c9dd19..9908075 100644 --- a/src/en/clean-copy/04-Section III. The API Product/10.md +++ b/src/en/clean-copy/04-Section III. The API Product/10.md @@ -11,7 +11,7 @@ First of all, an important remark: when we talk about supporting API customers, The first two cases are actually consequences of product-wise or technical flaws in the API development, and they should be avoided. The third case differs little from supporting end users of the UGC service itself. If we talk about supporting partners, it's revolving around two major topics: - * legal and administrative support with regards to the terms of service and the SLA (and that's usually about responding to business owners' inquiries); + * legal and administrative support with regard to the terms of service and the SLA (and that's usually about responding to business owners' inquiries); * helping developers with technical issues. The former is of course extremely important for any healthy service (including APIs) but again bears little API-related specifics. In the context of this book, we are much more interested in the latter. @@ -28,9 +28,9 @@ There are several options for tackling these issues. Importantly, whatever options you choose, it's still the API developers in the second line of support simply because only they can fully understand the problem and the partners' code. That implies two important consequences. - 1. You must consider the time needed to investigate inquiries while planning the API development team work. Reading unfamiliar code and remote debugging are very hard and exhausting tasks. The more functionality you expose and the more platforms you support, the more load is put on the team in terms of dealing with support tickets. + 1. You must consider the time needed to investigate inquiries while planning the API development team time. Reading unfamiliar code and remote debugging are very hard and exhausting tasks. The more functionality you expose and the more platforms you support, the more load is put on the team in terms of dealing with support tickets. - 2. As a rule, developers are totally not happy with a perspective of sorting the incoming requests and answering them. The first line of support will still let through a lot of dilettante or badly formulated questions, and that will annoy on-duty API developers. There are several approaches to mitigate the problem: + 2. As a rule, developers are totally not happy with the perspective of sorting the incoming requests and answering them. The first line of support will still let through a lot of dilettante or badly formulated questions, and that will annoy on-duty API developers. There are several approaches to mitigate the problem: * try to find people with a customer-oriented mindset, who like this activity, and encourage them (including financial stimulus) to deal with support; it might be someone on the team (and not necessarily a developer) or some active community member; diff --git a/src/en/clean-copy/04-Section III. The API Product/11.md b/src/en/clean-copy/04-Section III. The API Product/11.md index c4323f2..3a8ffc9 100644 --- a/src/en/clean-copy/04-Section III. The API Product/11.md +++ b/src/en/clean-copy/04-Section III. The API Product/11.md @@ -1,6 +1,6 @@ ### [The Documentation][api-product-documentation] -Regretfully, many API providers pay miserable attention to the documentation quality. Meanwhile, the documentation is the face of the product and the entry point to it. The problem becomes even worse if we acknowledge that it's almost impossible to write the help docs the developers will consider at least satisfactory. +Regretfully, many API providers pay miserable attention to the quality of documentation. Meanwhile, the documentation is the face of the product and the entry point to it. The problem becomes even worse if we acknowledge that it's almost impossible to write the help docs the developers will consider at least satisfactory. Before we start describing documentation types and formats, we should stress one important statement: developers interact with your help articles totally unlike you expect them to. Remember yourself working on the project: you make quite specific actions. @@ -11,7 +11,7 @@ In fact, newcomers (e.g. those developers who are not familiar with the API) usu #### Introductory notes -Documentation frequently suffers from being excessively clerical; it's being written using formal terminology (which often requires reading the glossary before the actual docs) and being unreasonably inflated. So instead of a two-word answer to the user's question a couple of paragraphs is conceived — a practice we strongly disapprove of. The perfect documentation must be simple and laconic, and all the terms must be either explained in the text or given a reference to such an explanation. However, “simple” doesn't mean “illiterate”: remember, the documentation is the face of your product, so grammar errors and improper usage of terms are unacceptable. +Documentation frequently suffers from being excessively clerical; it's being written using formal terminology (which often requires reading the glossary before the actual docs) and being unreasonably inflated. So instead of a two-word answer to the user's question a couple of paragraphs are conceived — a practice we strongly disapprove of. The perfect documentation must be simple and laconic, and all the terms must be either explained in the text or given a reference to such an explanation. However, “simple” doesn't mean “illiterate”: remember, the documentation is the face of your product, so grammar errors and improper usage of terms are unacceptable. Also, keep in mind that documentation will be used for searching as well, so every page should contain all the keywords required to be properly ranked by search engines. This requirement somehow contradicts the simple-and-laconic principle; that's the way. @@ -31,16 +31,16 @@ Today, the method nomenclature descriptions are frequently additionally exposed ##### Code Samples -From the above mentioned, it's obvious that code samples are the crucial tool to acquire and retain new API users. It's extremely important to choose the examples that help newcomers to start working with the API. Improper example selection will greatly reduce the quality of your documentation. While assembling the set of code samples, it is important to follow the rules: +From the above-mentioned, it's obvious that code samples are a crucial tool to acquire and retain new API users. It's extremely important to choose examples that help newcomers to start working with the API. Improper example selection will greatly reduce the quality of your documentation. While assembling the set of code samples, it is important to follow the rules: * examples must cover actual API use cases: the better you guess the most frequent developers' needs, the more friendly and straightforward your API will look to them; * examples must be laconic and atomic: mixing a bunch of tricks in one code sample dramatically reduces its readability and applicability; - * examples must be close to real-world app code; the author of this book once faced the situation when a synthetic code sample, totally meaningless in the real world, was mindlessly replicated by developers in abundance. + * examples must be close to real-world app code; the author of this book once faced a situation when a synthetic code sample, totally meaningless in the real world, was mindlessly replicated by developers in abundance. Ideally, examples should be linked to all other kinds of documentation, i.e. the reference should contain code samples relevant to the entity being described. ##### Sandboxes -Code samples will be much more useful to developers if they are “live,” e.g. provided as live pieces of code that might be modified and executed. In the case of library APIs, the online sandbox featuring a selection of code samples will suffice, and existing online services like JSFiddle might be used. With other types of APIs, developing sandboxes might be much more complicated: +Code samples will be much more useful to developers if they are “live,” e.g. provided as editable pieces of code that might be modified and executed. In the case of library APIs, the online sandbox featuring a selection of code samples will suffice, and existing online services like JSFiddle might be used. With other types of APIs, developing sandboxes might be much more complicated: * if the API provides access to some data, then the sandbox must allow working with a real dataset, either a developer's own one (e.g. bound to their user profile) or some test data; * if the API provides an interface, visual or programmatic, to some non-online environment, like UI libs for mobile devices do, then the sandbox itself must be an emulator or a simulator of that environment, in a form of an online service or a standalone app. @@ -48,19 +48,19 @@ Code samples will be much more useful to developers if they are “live,” e.g. A tutorial is a specifically written human-readable text describing some concepts of working with the API. A tutorial is something in-between a reference and examples. It implies some learning, more thorough than copy-pasting code samples, but requires less time investment than reading the whole reference. -A tutorial is a sort of a “book” that you write to explain to the reader how to work with your API. So, a proper tutorial must follow book-writing patterns, e.g. explain the concepts coherently and consecutively chapter after chapter. Also, a tutorial must provide: - * general knowledge of the subject area; for example, a tutorial for some cartographical API must explain trivia regarding geographical coordinates and working with them; +A tutorial is a sort of “book” that you write to explain to the reader how to work with your API. So, a proper tutorial must follow book-writing patterns, e.g. explain the concepts coherently and consecutively chapter after chapter. Also, a tutorial must provide: + * general knowledge of the subject area; for example, a tutorial for cartographical APIs must explain trivia regarding geographical coordinates and working with them; * proper API usage scenarios, e.g. the “happy paths”; * proper reactions to program errors that could happen; * detailed studies on advanced API functionality (with detailed examples). As usual, a tutorial comprises a common section (basic terms and concepts, notation keys) and a set of sections regarding each functional domain exposed via the API. -Usually, tutorials contain a “Quick Start” (“Hello, world!”) section: the smallest possible code sample that would allow developers to build a small app atop of the API. “Quick starts” aim to cover two needs: +Usually, tutorials contain a “Quick Start” (“Hello, world!”) section: the smallest possible code sample that would allow developers to build a small app atop the API. “Quick Starts” aim to cover two needs: * to provide a default entry-point, the easiest to understand and the most useful text for those who heard about your API for the first time; * to engage developers, to make them touch the service by a mean of a real-world example. -Also, “Quick starts” are a good indicator of how exactly well did you do your homework of identifying the most important use-cases and providing helper methods. If your Quick Start comprises more than ten lines of code, you have definitely done something wrong. +Also, “Quick starts” are a good indicator of how exactly well did you do your homework of identifying the most important use cases and providing helper methods. If your Quick Start comprises more than ten lines of code, you have definitely done something wrong. ##### Frequently asked questions and a knowledge base @@ -78,12 +78,12 @@ Though we live in the online world, an offline version of the documentation (in #### Content Duplication Problems -A significant problem that harms documentation clarity is the API versioning: articles describing the same entity across different API versions are usually quite similar. Organizing convenient searching capability over such datasets is a problem for internal and external search engines as well. To tackle this problem ensure that: +A significant problem that harms documentation clarity is API versioning: articles describing the same entity across different API versions are usually quite similar. Organizing convenient searching capability over such datasets is a problem for internal and external search engines as well. To tackle this problem ensure that: * the API version is highlighted on the documentation pages; * if a version of the current page exists for newer API versions, there is an explicit link to the actual version; * docs for deprecated API versions are pessimized or even excluded from indexing. -If you're strictly maintaining backwards compatibility, it is possible to create the single documentation for all API versions. To do so, each entity is to be marked with the API version it is supported from. However, there is an apparent problem with this approach: it's not that simple to get docs for a specific (outdated) API version (and, generally speaking, to understand which capabilities this API version provides). (Though the offline documentation we mentioned earlier will help.) +If you're strictly maintaining backwards compatibility, it is possible to create a single documentation for all API versions. To do so, each entity is to be marked with the API version it is supported from. However, there is an apparent problem with this approach: it's not that simple to get docs for a specific (outdated) API version (and, generally speaking, to understand which capabilities this API version provides). (Though the offline documentation we mentioned earlier will help.) The problem becomes worse if you're supporting not only different API versions but also different environments / platforms / programming languages; for example, if your UI lib supports both iOS and Android. Then both documentation versions are equal, and it's impossible to pessimize one of them. diff --git a/src/en/clean-copy/04-Section III. The API Product/12.md b/src/en/clean-copy/04-Section III. The API Product/12.md index d56bcbb..c142181 100644 --- a/src/en/clean-copy/04-Section III. The API Product/12.md +++ b/src/en/clean-copy/04-Section III. The API Product/12.md @@ -5,7 +5,7 @@ If the operations executed via the API imply consequences for end users or partn However, in many cases having a test version is not enough — like in our coffee-machine API example. If an order is created but not served, partners are not able to test the functionality of delivering the order or requesting a refund. To run the full cycle of testing, developers need the capability of pushing the order through stages, as this would happen in reality. A direct solution to this problem is providing a full set of testing APIs and administrative interfaces. It means that developers will need to run a second application in parallel — the one you're giving to coffee shops so they might get and serve orders (and if there is a delivery functionality, the third app as well: the courier's one) — and make all these actions that coffee shop staff normally does. Obviously, that's not an ideal solution, because of several reasons: - * end user application developers will need to additionally learn how coffee shop and courier apps work, which has nothing to do with the task they're solving; + * developers of end user applications will need to additionally learn how coffee shop and courier apps work, which has nothing to do with the task they're solving; * you will need to invent and implement some matching algorithm: an order made through a test application must be assigned to a specific virtual courier; this actually means creating an isolated virtual “sandbox” (meaning — a full set of services) for each specific partner; * executing a full “happy path” of an order will take minutes, maybe tens of minutes, and will require making a multitude of actions in several different interfaces. @@ -15,7 +15,7 @@ There are two main approaches to tackling these problems. The first option is providing a meta-API to the testing environment itself. Instead of running the coffee-shop app in a separate simulator, developers are provided with helper methods (like `simulateOrderPreparation`) or some visual interface that allows controlling the order execution pipeline with minimum effort. -Ideally, you should provide helper methods for any actions that are conducted by people in production environment. It makes sense to ship this meta-API complete with ready-to-use scripts or request collections that show the correct API call orders for standard scenarios. +Ideally, you should provide helper methods for any actions that are conducted by people in the production environment. It makes sense to ship this meta-API complete with ready-to-use scripts or request collections that show the correct API call orders for standard scenarios. The disadvantage of this approach is that client developers still need to know how the “flip side” of the system works, though in simplified terms. @@ -31,4 +31,4 @@ The main disadvantage is the necessity to create a separate scenario for each un Your final goal in implementing testing APIs, regardless of which option you choose, is allowing partners to automate the QA process for their products. The testing environment should be developed with this purpose in mind; for example, if an end user might be brought to a 3-D Secure page to pay for the order, the testing environment API must provide some way of simulating the successful (or not) passing of this step. Also, in both variants, it's possible (and desirable) to allow running the scenarios in a fast-forward manner that will allow making auto-testing much faster than manual testing. -Of course, not every partner will be able to employ this possibility (which also means that “manual” way of testing usage scenarios must also be supported alongside the programmatical one) simply because not every business might afford to hire a QA automation engineer. Nevertheless, the ability to write such auto-tests is your API's huge competitive advantage from a technically advanced partner's point of view. \ No newline at end of file +Of course, not every partner will be able to employ this possibility (which also means that a “manual” way of testing usage scenarios must also be supported alongside the programmatical one) simply because not every business might afford to hire a QA automation engineer. Nevertheless, the ability to write such auto-tests is your API's huge competitive advantage from a technically advanced partner's point of view. \ No newline at end of file diff --git a/src/en/clean-copy/04-Section III. The API Product/13.md b/src/en/clean-copy/04-Section III. The API Product/13.md index 508c337..30b1c7a 100644 --- a/src/en/clean-copy/04-Section III. The API Product/13.md +++ b/src/en/clean-copy/04-Section III. The API Product/13.md @@ -1,6 +1,6 @@ ### [Managing expectations][api-product-expectations] -Finally, the last aspect we would like to shed the light on is managing partners' expectations regarding the further development of the API. If we talk about the consumer qualities, APIs differ little from other B2B software products: in both cases, you need to form some understanding of SLA conditions, available features, interface responsiveness and other characteristics that are important for clients. Still, APIs have their specificities +Finally, the last aspect we would like to shed the light on is managing partners' expectations regarding the further development of the API. If we talk about consumer qualities, APIs differ little from other B2B software products: in both cases, you need to form some understanding of SLA conditions, available features, interface responsiveness and other characteristics that are important for clients. Still, APIs have their specificities #### Versioning and application lifecycle @@ -16,12 +16,12 @@ In this aspect, integrating with large companies that have a dedicated software Another aspect crucial to interacting with large integrators is supporting a zoo of platforms (browsers, programming languages, protocols, operating systems) and their versions. As usual, big companies have their own policies on which platforms they support, and these policies might sometimes contradict common sense. (Let's say, it's rather a time to abandon TLS 1.2, but many integrators continue working through this protocol, or even the earlier ones.) -Formally speaking, ceasing supporting a platform *is* a backwards-incompatible change, and might lead to breaking some integration for some end users. So it's highly important to have clearly formulated policies on which platforms are supported based on which criteria. In the case of mass public APIs, that's usually simple (like, API vendor promises to support platforms that have more than N% penetration, or, even easier, just last M versions of a platform); in the case of commercial APIs, it's always a bargain based on the estimations, how much will non-supporting a specific platform would cost to a company. And of course, the outcome of the bargain must be stated in the contracts — what exactly you're promising to support during which period of time. +Formally speaking, ceasing support of a platform *is* a backwards-incompatible change, and might lead to breaking some integration for some end users. So it's highly important to have clearly formulated policies on which platforms are supported based on which criteria. In the case of mass public APIs, that's usually simple (like, API vendor promises to support platforms that have more than N% penetration, or, even easier, just last M versions of a platform); in the case of commercial APIs, it's always a bargain based on the estimations, how much will non-supporting a specific platform would cost to a company. And of course, the outcome of the bargain must be stated in the contracts — what exactly you're promising to support during which period of time. #### Moving forward -Finally, apart from those specific issues, your customers must be caring about more general questions: could they trust you? could they rely on your API evolving, absorbing modern trends, or will they eventually find the integration with your API on the scrapyard of history? Let's be honest: given all the uncertainties of the API product vision, we are very much interested in the answers as well. Even the Roman viaduct, though remaining backwards-compatible for two thousand years, has been a very archaic and non-reliable way of solving customers' problems for quite a long time. +Finally, apart from those specific issues, your customers must be caring about more general questions: could they trust you? Could they rely on your API evolving, absorbing modern trends, or will they eventually find the integration with your API on the scrapyard of history? Let's be honest: given all the uncertainties of the API product vision, we are very much interested in the answers as well. Even the Roman viaduct, though remaining backwards-compatible for two thousand years, has been a very archaic and non-reliable way of solving customers' problems for quite a long time. -You might work with these customer expectations through publishing roadmaps. It's quite common that many companies avoid publicly announcing their concrete plans (for a reason, of course). Nevertheless, in the case of APIs, we strongly recommend providing the roadmaps, even if they are tentative and lack precise dates — *especially* if we talk about deprecating some functionality. Announcing these promises (given the company keeps them, of course) is a very important competitive advantage to every kind of consumer. +You might work with these customer expectations by publishing roadmaps. It's quite common that many companies avoid publicly announcing their concrete plans (for a reason, of course). Nevertheless, in the case of APIs, we strongly recommend providing the roadmaps, even if they are tentative and lack precise dates — *especially* if we talk about deprecating some functionality. Announcing these promises (given the company keeps them, of course) is a very important competitive advantage to every kind of consumer. With this, we would like to conclude this book. We hope that the principles and the concepts we have outlined will help you in creating APIs that fit all the developer', business', and end user's needs, and in expanding them (while maintaining the backwards compatibility) for the next two thousand years (or maybe more). \ No newline at end of file diff --git a/src/ru/clean-copy/03-Раздел II. Обратная совместимость/04.md b/src/ru/clean-copy/03-Раздел II. Обратная совместимость/04.md index 6b7f993..1946287 100644 --- a/src/ru/clean-copy/03-Раздел II. Обратная совместимость/04.md +++ b/src/ru/clean-copy/03-Раздел II. Обратная совместимость/04.md @@ -34,7 +34,7 @@ POST /v1/recipes "name", "description" }, /* другие языки и страны */ … ] -] +} ``` И здесь возникает первый большой вопрос — а что делать с `default_volume`? С одной стороны, это объективная величина, выраженная в стандартизированных единицах измерения, и она используется для запуска программы на исполнение. С другой стороны, для таких стран, как США, мы будем обязаны указать объём не в виде «300 мл», а в виде «10 унций». Мы можем предложить одно из двух решений: diff --git a/src/ru/clean-copy/04-Раздел III. API как продукт/02.md b/src/ru/clean-copy/04-Раздел III. API как продукт/02.md index d9fe04f..234eba1 100644 --- a/src/ru/clean-copy/04-Раздел III. API как продукт/02.md +++ b/src/ru/clean-copy/04-Раздел III. API как продукт/02.md @@ -1,6 +1,6 @@ ### [Бизнес-модели API][api-product-business-models] -Прежде, чем переходить непосредственно к принципам продуктового управления API, позволим себе заострить внимание читателя на вопросе, каким образом наличие API как продукта приносит пользу компании, а также соответствующие модели монетизации, прямой и косвенной. Вопрос этот, как мы покажем в следующих главах, далеко не праздный, так как напрямую влияет на KPI API и принятие решений. [В квадратных скобках будем приводить примеры подобных моделей применительно к нашему учебному примеру с API кофе-машин.] +Прежде, чем переходить непосредственно к принципам продуктового управления API, позволим себе заострить внимание читателя на вопросе, каким образом наличие API как продукта приносит пользу компании, а также соответствующие модели монетизации, прямой и косвенной. Вопрос этот, как мы покажем в следующих главах, далеко не праздный, так как напрямую влияет на KPI API и принятие решений. В этой главе мы разберём основные модели монетизации API. [В квадратных скобках будем приводить примеры подобных моделей применительно к нашему учебному примеру с API кофе-машин.] ##### Разработчик = конечный пользователь