1
0
mirror of https://github.com/twirl/The-API-Book.git synced 2025-05-25 22:08:06 +02:00

corrections

This commit is contained in:
Sergey Konstantinov 2023-06-17 16:41:05 +03:00
parent 3293fb8966
commit 4616abc3fb
36 changed files with 303 additions and 329 deletions

View File

@ -15,9 +15,8 @@ In other words, hundreds or even thousands of different APIs must work correctly
When I'm asked for an example of a well-designed API, I usually show a picture of a Roman aqueduct:
[![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,
* backward compatibility has not been broken even once in two thousand years.
* It interconnects two areas
* Backward compatibility has not been broken even once in two thousand years.
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 design APIs that serve their purposes as solidly as a Roman aqueduct does.

View File

@ -3,8 +3,8 @@
In the first three sections of this book, we aim to discuss API design in general, not bound to any specific technology. The concepts we describe are equally applicable to, let's say, web services and operating system (OS) APIs.
Still, two main scenarios dominate the stage when we talk about API development:
* developing client-server applications
* developing client SDKs.
* Developing client-server applications
* Developing client SDKs.
In the first case, we almost universally talk about APIs working atop the HTTP protocol. Today, the only notable examples of non-HTTP-based client-server interaction protocols are WebSocket (though it might, and frequently does, work in conjunction with HTTP), MQTT, and highly specialized APIs like media streaming and broadcasting formats.
@ -13,15 +13,15 @@ In the first case, we almost universally talk about APIs working atop the HTTP p
Although the technology looks homogeneous because of using the same application-level protocol, in reality, there is significant diversity regarding different approaches to realizing HTTP-based APIs.
**First**, implementations differ in terms of utilizing HTTP capabilities:
* either the client-server interaction heavily relies on the features described in the HTTP standard (or rather standards, as the functionality is split across several different RFCs),
* or HTTP is used as transport, and there is an additional abstraction level built upon it (i.e., the HTTP capabilities, such as the headers and status codes nomenclatures, are deliberately reduced to a bare minimum, and all the metadata is handled by the higher-level protocol).
* Either the client-server interaction heavily relies on the features described in the HTTP standard (or rather standards, as the functionality is split across several different RFCs), or
* HTTP is used as transport, and there is an additional abstraction level built upon it (i.e., the HTTP capabilities, such as the headers and status codes nomenclatures, are deliberately reduced to a bare minimum, and all the metadata is handled by the higher-level protocol).
The APIs that belong to the first category are usually denoted as “REST” or “RESTful” APIs. The second category comprises different RPC formats and some service protocols, for example, SSH.
**Second**, different HTTP APIs rely on different data formats:
* REST APIs and some RPCs ([JSON-RPC](https://www.jsonrpc.org/), [GraphQL](https://graphql.org/), etc.) use the [JSON](https://www.ecma-international.org/publications-and-standards/standards/ecma-404/) format (sometimes with some additional endpoints to transfer binary data)
* [gRPC](https://grpc.io/) and some specialized RPC protocols like [Apache Avro](https://avro.apache.org/docs/) utilize binary formats (such as [Protocol Buffers](https://protobuf.dev/), [FlatBuffers](https://flatbuffers.dev/), or Apache Avro's own format)
* finally, some RPC protocols (notably [SOAP](https://www.w3.org/TR/soap12/) and [XML-RPC](http://xmlrpc.com/)) employ the [XML](https://www.w3.org/TR/xml/) data format (which is considered a rather outdated practice by many developers).
* Finally, some RPC protocols (notably [SOAP](https://www.w3.org/TR/soap12/) and [XML-RPC](http://xmlrpc.com/)) employ the [XML](https://www.w3.org/TR/xml/) data format (which is considered a rather outdated practice by many developers).
All the above-mentioned technologies operate in significantly dissimilar paradigms, which give rise to rather hot “holy war” debates among software engineers. However, at the moment this book is being written we observe the choice for general-purpose APIs is reduced to the “REST API (in fact, JSON-over-HTTP) vs. gRPC vs. GraphQL” triad.

View File

@ -5,13 +5,13 @@ 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 primarily defined through its capability to solve developers' and users' problems. (One could reasonably argue that solving problems might not be the main purpose of offering an API to developers. However, manipulating public opinion is not of interest to the author of this book. Here we assume that APIs exist primarily to help people, not for some other covertly declared purposes.)
So, how might a “fine” API design 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 convenient manner. The distance from formulating a task to writing working code must be as short as possible. Among other things, this means that:
* it must be totally obvious from your API's structure how to solve a task
* ideally, developers should be able to understand at first glance, what entities are meant to solve their problem
* the API must be readable
* ideally, developers should write correct code after just looking at the methods' nomenclature, never bothering about details (especially API implementation details!)
* it is also essential to mention that not only should the problem solution (the “happy path”) be obvious, but also possible errors and exceptions (the “unhappy path”)
* the API must be consistent
* while developing new functionality (i.e., while using previously unknown API entities) developers may write new code similar to the code they have already written using the known API concepts, and this new code will work.
* It must be totally obvious from your API's structure how to solve a task:
* Ideally, developers should be able to understand at first glance, what entities are meant to solve their problem
* The API must be readable:
* Ideally, developers should be able to write correct code after just looking at the methods' nomenclature, never bothering about details (especially API implementation details!)
* It is also essential to mention that not only should the problem solution (the “happy path”) be obvious, but also possible errors and exceptions (the “unhappy path”)
* The API must be consistent:
* While developing new functionality (i.e., while using previously unknown API entities) developers may write new code similar to the code they have already written using the known API concepts, and this new code will work.
However, the static convenience and clarity of APIs are simple parts. After all, nobody seeks to make an API deliberately irrational and unreadable. When we develop an API, we always start with clear basic concepts. Providing you have some experience in APIs, it's quite hard to make an API core that fails to meet obviousness, readability, and consistency criteria.

View File

@ -7,15 +7,15 @@ However, we must differentiate between the product concept of the API-first appr
The former means that the first (and sometimes the only) step in developing a service is creating an API for it, and we will discuss it in “The API Product” section of this book.
If we talk about the API-first approach in a technical sense, we mean the following: **the contract, i.e. the obligation to connect two programmable contexts, precedes the implementation and defines it**. More specifically, two rules must be respected:
* the contract is developed and committed to in the form of a specification before the functionality is implemented
* if it turns out that the implementation and the contract differ, the implementation is to be fixed, not the contract.
* The contract is developed and committed to in the form of a specification before the functionality is implemented.
* If it turns out that the implementation and the contract differ, the implementation is to be fixed, not the contract.
The “specification” in this context is a formal machine-readable description of the contract in one of the interface definition languages (IDL) — for example, in the form of a Swagger/OpenAPI document or a `.proto` file.
Both rules assert that partner developers' interests are given the highest priority:
* rule \#1 allows partners to write code based on the specification without coordinating the process with the API provider
* the possibility of auto-generating code based on the specification emerges, which might make development significantly less complex and error-prone or even automate it
* the code might be developed without having access to the API
* rule \#2 means partners won't need to change their implementations should some inconsistencies between the specification and the API functionality arise.
* Rule \#1 allows partners to write code based on the specification without coordinating the process with the API provider:
* The possibility of auto-generating code based on the specification emerges, which might make development significantly less complex and error-prone or even automate it
* The code might be developed without having access to the API.
* Rule \#2 means partners won't need to change their implementations should some inconsistencies between the specification and the API functionality arise.
Therefore, for your API consumers, the API-first approach is a guarantee of a kind. However, it only works if the API was initially well-designed. If some irreparable flaws in the specification surface, we would have no other option but to break rule \#2.

View File

@ -33,14 +33,14 @@ Cache-Control: no-cache
```
It should be read like this:
* a client performs a `POST` request to a `/v1/bucket/{id}/some-resource` resource, where `{id}` is to be replaced with some `bucket`'s identifier (`{something}` notation refers to the nearest term from the left unless explicitly specified otherwise);
* a specific `X-Idempotency-Token` header is added to the request alongside standard headers (which we omit);
* terms in angle brackets (`<idempotency token>`) describe the semantics of an entity value (field, header, parameter);
* 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 `→`) the server returns a `404 Not Found` 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 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 on a single line, we will split it into several lines adding `⮠` to indicate it continues next line.
* A client performs a `POST` request to a `/v1/bucket/{id}/some-resource` resource, where `{id}` is to be replaced with some `bucket`'s identifier (`{something}` notation refers to the nearest term from the left unless explicitly specified otherwise).
* A specific `X-Idempotency-Token` header is added to the request alongside standard headers (which we omit).
* Terms in angle brackets (`<idempotency token>`) describe the semantics of an entity value (field, header, parameter).
* 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 `→`) the server returns a `404 Not Found` 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`. Absence of a value 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 on 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 with “client.”

View File

@ -1,10 +1,10 @@
### [The API Contexts Pyramid][api-design-context-pyramid]
The approach we use to design APIs comprises four steps:
* defining an application field
* separating abstraction levels
* isolating responsibility areas
* describing final interfaces.
* Defining an application field
* Separating abstraction levels
* Isolating responsibility areas
* Describing final interfaces.
This four-step algorithm actually builds an API from top to bottom, from common requirements and use case scenarios down to a refined nomenclature of entities. In fact, moving this way will eventually conclude with a ready-to-use API, and that's why we value this approach highly.

View File

@ -43,9 +43,9 @@ As for our speculative example, let us imagine that in the nearby future, some t
#### What and How
After finishing all these theoretical exercises, we should proceed directly to designing and developing the API, having a decent understanding of two things:
* *what* we're doing exactly
* *how* we're doing it exactly.
* *What* we're doing exactly
* *How* we're doing it exactly.
In our coffee case, we are:
* providing an API to services with a larger audience so that their users may order a cup of coffee in the most efficient and convenient manner
* abstracting access to coffee machines' “hardware” and developing generalized software methods to select a beverage kind and a location to make an order.
* Providing an API to services with a larger audience so that their users may order a cup of coffee in the most efficient and convenient manner
* Abstracting access to coffee machines' “hardware” and developing generalized software methods to select a beverage kind and a location to make an order.

View File

@ -42,8 +42,8 @@ GET /v1/orders/{id}
```
Let's consider a question: how exactly should developers determine whether the order is ready or not? Let's say we do the following:
* add a reference beverage volume to the lungo recipe;
* add the currently prepared volume of the beverage to the order state.
* Add a reference beverage volume to the lungo recipe
* Add the currently prepared volume of the beverage to the order state.
```
GET /v1/recipes/lungo
@ -84,8 +84,8 @@ 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:
* 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 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.
* 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 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:
@ -119,8 +119,8 @@ Abstraction levels separation should go in three directions:
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;
* on the other hand, a coffee machine should not store the data regarding order properties (and its API probably doesn't provide such functionality).
* On one hand, an “order” should not store the data regarding coffee machine sensors
* On the other hand, a coffee machine should not store the data regarding order properties (and its API probably doesn't provide such functionality).
A naïve approach to this situation is to design an interim abstraction level as a “connecting link,” which reformulates tasks from one abstraction level into another. For example, introduce a `task` entity like that:
@ -162,8 +162,8 @@ We call this approach “naïve” not because it's wrong; on the contrary, that
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 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.
* 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.
@ -211,7 +211,7 @@ To be more specific, let's assume those two kinds of coffee machines provide the
GET /execution/{id}/status
```
**NB**. Just in case: this API violates a number of design principles, starting with a lack of versioning; it's described in such a manner because of two reasons: (1) to demonstrate how to design a more convenient API, (2) in the real life, you will really get something like that from vendors, and this API is actually quite a sane one.
**NB**: this API violates a number of design principles, starting with a lack of versioning; it's described in such a manner because of two reasons: (1) to demonstrate how to design a more convenient API, (2) in the real life, you will really get something like that from vendors, and this API is actually quite a sane one.
* Coffee machines with built-in functions:
```
@ -285,17 +285,16 @@ The next step in abstraction level separating is determining what functionality
4. Finally, developers need to run atomic operations, like canceling orders.
Note, that the first-kind API is much closer to developers' needs than the second-kind API. An indivisible “program” is a way more convenient concept than working with raw commands and sensor data. There are only two problems we see in the first-kind API:
* absence of explicit “programs” to “recipes” relation; program identifier is of no use to developers since there is a “recipe” concept;
* absence of an explicit “ready” status.
* Absence of explicit “programs” to “recipes” relation. A program identifier is of no use to developers since there is a “recipe” concept.
* Absence of an explicit “ready” status.
But with the second-kind API, it's much worse. The main problem we foresee is the absence of “memory” for actions being executed. The 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 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.
* 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.
2. Program runtime level. For the first-kind API, it will provide just a wrapper for existing programs API; for the second-kind API, the entire “runtime” concept is to be developed from scratch by us.
@ -344,8 +343,8 @@ Please note that knowing the coffee machine API kind isn't required at all; that
* `POST /v1/{api_type}/programs/{id}/run`
This approach has some benefits, like the possibility to provide different sets of parameters, specific to the API kind. But we see no need for such fragmentation. The `run` method handler is capable of extracting all the program metadata and performing one of two actions:
* call the `POST /execute` physical API method, passing the internal program identifier for the first API kind;
* initiate runtime creation to proceed with the second API kind.
* Call the `POST /execute` physical API method, passing the internal program identifier for the first API kind
* Initiate runtime creation to proceed with the second API kind.
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” (i.e., a stateful execution context) to run a program and control its state.
@ -411,8 +410,8 @@ And the `state` like that:
```
**NB**: when implementing the `orders``match``run``runtimes` call sequence, we have two options:
* either `POST /orders` handler requests the data regarding the recipe, the coffee machine model, and the program on its own, and forms a stateless request that contains all necessary data (API kind, command sequence, etc.);
* or the request contains only data identifiers, and the next handler in the chain will request pieces of data it needs via some internal APIs.
* Either `POST /orders` handler requests the data regarding the recipe, the coffee machine model, and the program on its own, and forms a stateless request that contains all necessary data (API kind, command sequence, etc.)
* Or the request contains only data identifiers, and the next handler in the chain will request pieces of data it needs via some internal APIs.
Both variants are plausible and the selection between them depends on implementation details.
@ -424,35 +423,36 @@ Returning to our example, how would retrieving the order status work? To obtain
* A user initiates a call to the `GET /v1/orders` method.
* The `orders` handler completes operations on its level of responsibility (e.g., checks user authorization), finds the `program_run_id` identifier and performs a call to the `runs/{program_run_id}` endpoint.
* The `runs` endpoint completes operations corresponding to its level (e.g., checks the coffee machine API kind) and, depending on the API kind, proceeds with one of two possible execution branches:
* either calls the `GET /execution/status` method of the physical coffee machine API, gets the coffee volume, and compares it to the reference value
* or invokes the `GET /v1/runtimes/{runtime_id}` method to obtain the `state.status` and converts it to the order status.
* Either calls the `GET /execution/status` method of the physical coffee machine API, gets the coffee volume, and compares it to the reference value or
* Invokes the `GET /v1/runtimes/{runtime_id}` method to obtain the `state.status` and converts it to the order status.
* In the case of the second-kind API, the call chain continues: the `GET /runtimes` handler invokes the `GET /sensors` method of the physical coffee machine API and performs some manipulations with the data, like comparing the cup / ground coffee / shed water volumes with the reference ones, and changing the state and the status if needed.
**NB**: The term “call chain” shouldn't be taken literally. Each abstraction level may be organized differently in a technical sense. For example:
* there might be explicit proxying of calls down the hierarchy
* there might be a cache at each level, which is updated upon receiving a callback call or an event. In particular, a low-level runtime execution cycle obviously must be independent of upper levels, which implies renewing its state in the background and not waiting for an explicit call.
* There might be explicit proxying of calls down the hierarchy
* There might be a cache at each level, which is updated upon receiving a callback call or an event. In particular, a low-level runtime execution cycle obviously must be independent of upper levels, which implies renewing its state in the background and not waiting for an explicit call.
Note what happens here: each abstraction level wields its own status (i.e., order, runtime, and sensors status respectively) formulated in subject area terms corresponding to this level. Forbidding “jumping over” results in the necessity to spawn statuses at each level independently.
Now let's examine how the order cancel operation flows through our abstraction levels. In this case, the call chain will look like this:
* A user initiates a call to the `POST /v1/orders/{id}/cancel` method.
* The method handler completes operations on its level of responsibility:
* checks the authorization
* resolves money issues (e.g., whether a refund is needed)
* finds the `program_run_id` identifier and calls the `runs/{program_run_id}/cancel` method.
* Checks the authorization
* Resolves money issues (e.g., whether a refund is needed)
* Finds the `program_run_id` identifier and calls the `runs/{program_run_id}/cancel` method.
* 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:
* calls the `POST /execution/cancel` method of a physical coffee machine API, or
* invokes the `POST /v1/runtimes/{id}/terminate` method.
* 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:
* changes the `resolution` to `"terminated"`
* runs the `"discard_cup"` command.
* Changes the `resolution` to `"terminated"`
* Runs the `"discard_cup"` command.
Handling state-modifying operations like the `cancel` operation requires more advanced abstraction-level juggling skills compared to non-modifying calls like the `GET /status` method. There are two important moments to consider:
1. At each abstraction level the idea of “order canceling” is reformulated:
* at the `orders` level, this action splits into several “cancels” of other levels: you need to cancel money holding and cancel order execution
* 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.
* At the `orders` level, this action splits into several “cancels” of other levels: you need to cancel money holding and cancel order execution
* 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, processing continues until the cup is discarded, and then the machine is to be unlocked (i.e., new runtimes creation allowed). It's an execution control level's task to couple those two states, outer (the order is canceled) and inner (the execution continues).
@ -486,7 +486,7 @@ We may also traverse the tree backward.
3. At the runtime level, we read the target parameters (which operation to execute, and what the target volume is) and translate them into coffee machine API microcommands and statuses for each command.
Also, if we take a deeper look at the “bad” decision (forcing developers to determine the actual order status on their own), being discussed at the beginning of this chapter, we could notice a data flow collision there:
* on the one hand, in the order context “leaked” physical data (beverage volume prepared) is injected, 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 one hand, in the order context “leaked” physical data (beverage volume prepared) is injected, 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.
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 separate abstraction levels properly and check if our theoretical concepts work as intended.

View File

@ -1,10 +1,9 @@
### [Isolating Responsibility Areas][api-design-isolating-responsibility]
In the previous chapter, we concluded that the hierarchy of abstractions in our hypothetical project would comprise:
* the user level (the entities formulated in terms understandable by users and acted upon by them: orders, coffee recipes);
* 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).
* The user level (the entities formulated in terms understandable by users and acted upon by them: orders, coffee recipes)
* 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.
@ -13,24 +12,24 @@ To do so, we must iterate all over the API and formulate in subject area terms w
As for our fictional example, it would look as follows.
1. User-level entities.
* An `order` describes some logical unit in app-user interaction. An `order` might be:
* created
* checked for its status
* retrieved
* canceled.
* Created
* Checked for its status
* Retrieved
* Canceled.
* A `recipe` describes an “ideal model” of a coffee beverage type, i.e., its customer properties. A `recipe` is an immutable entity that can only be read.
* A `coffee-machine` is a model of a real-world device. We must be able to retrieve the coffee machine's geographical location and the options it supports from this model (which will be discussed below).
2. Program execution control-level entities.
* A `program` describes a general execution plan for a coffee machine. Programs can only be read.
* The `programs/matcher` entity couples a `recipe` and a `program`, which in fact means retrieving a dataset needed to prepare a specific recipe on a specific coffee machine.
* The `programs/run` entity describes a single fact of running a program on a coffee machine. A `run` might be:
* initialized (created)
* checked for its status
* canceled.
* Initialized (created)
* Checked for its status
* Canceled.
3. Runtime-level entities.
* A `runtime` describes a specific execution data context, i.e., the state of each variable. A `runtime` can be:
* initialized (created)
* checked for its status
* terminated.
* Initialized (created)
* Checked for its status
* Terminated.
If we look closely at the entities, we may notice that each entity turns out to be a composite. For example, a `program` operates high-level data (`recipe` and `coffee-machine`), enhancing them with its subject area terms (`program_run_id` for instance). This is totally fine as connecting contexts is what APIs do.
@ -41,9 +40,9 @@ At this point, when our API is in general clearly outlined and drafted, we must
So, let us imagine we've got a task to write an app for ordering coffee based on our API. What code would we write?
Obviously, the first step is to offer a choice to the user, to make them point out what they want. And this very first step reveals that our API is quite inconvenient. There are no methods allowing for choosing something. Developers have to implement these steps:
* retrieve all possible recipes from the `GET /v1/recipes` endpoint;
* retrieve a list of all available coffee machines from the `GET /v1/coffee-machines` endpoint;
* write code that traverses all this data.
* Retrieve all possible recipes from the `GET /v1/recipes` endpoint
* Retrieve a list of all available coffee machines from the `GET /v1/coffee-machines` endpoint
* Write code that traverses all this data.
If we try writing pseudocode, we will get something like this:
@ -75,8 +74,8 @@ 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 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;
* display nearby cafes where a user could order a particular type of coffee — for users seeking a certain beverage type.
* 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
* Display nearby cafes where a user could order a particular type of coffee — for users seeking a certain beverage type.
Then our new interface would look like this:
@ -128,10 +127,9 @@ app.display(offers);
Methods similar to the newly invented `offers/search` one are called helpers. The purpose of their existence is to generalize known API usage scenarios and facilitate their implementation. By “facilitating,” we mean not only reducing wordiness (getting rid of “boilerplates”) but also helping developers avoid common problems and mistakes.
For instance, let's consider the problem of the monetary value of an order. Our search function returns some “offers” with prices. However, the price is volatile; coffee could cost less during “happy hours,” for example. Developers could make a mistake three times 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 the previous point, call the search endpoint excessively just to actualize prices, thus overloading the network and the API servers;
* create an order with an invalid price (thereby deceiving a user, displaying one sum, and debiting another).
* Cache search results on a client device for too long (as a result, the price will always be outdated).
* Contrary to the previous point, call the search endpoint excessively just to actualize prices, thus overloading the network and the API servers.
* Create an order with an invalid price (thereby deceiving a user, displaying one sum, and debiting another).
To solve the third problem we could demand that the displayed price be included in the order creation request and return an error if it differs from the actual one. (In fact, any API working with money *must* do so.) However, this solution does not help with the first two problems, and also 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.
@ -261,14 +259,13 @@ Let's take a look at the coffee machine search function response in our API. To
This approach is regretfully quite common and could be found in almost every API. Fields are mixed into one single list and often prefixed to indicate the related ones.
In this situation, we need to split this structure into data domains by grouping fields that are logically related to a single subject area. In our case, we may identify at least 7 data clusters:
* data regarding the place where the coffee machine is located
* properties of the coffee machine itself
* route data
* recipe data
* order options
* offer data
* pricing data.
* Data regarding the place where the coffee machine is located
* Properties of the coffee machine itself
* Route data
* Recipe data
* Order options
* Offer data
* Pricing data.
Let's group them together:

View File

@ -179,9 +179,9 @@ str_replace(needle, replace, haystack)
```
Several rules are violated:
* the usage of an underscore is not consistent
* functionally close methods have different `needle`/`haystack` argument ordering
* the first function finds the first occurrence while the second one finds all occurrences, and there is no way to deduce that fact from the function signatures.
* The usage of an underscore is not consistent
* Functionally close methods have different `needle`/`haystack` argument ordering
* The first function finds the first occurrence while the second one finds all occurrences, and there is no way to deduce that fact from the function signatures.
Improving these function signatures is left as an exercise for the reader.
@ -325,9 +325,9 @@ Moreover, you can develop a reference implementation of the retry policy in your
Nowadays the amount of traffic is rarely taken into account as the Internet connection is considered unlimited almost universally. However, it is not entirely unlimited: with some degree of carelessness, it's always possible to design a system that generates an uncomfortable amount of traffic even for modern networks.
There are three obvious reasons for inflating network traffic:
* clients query the data too frequently or cache it too little
* no data pagination is provided
* no limits are set on the data fields, or too large binary data (graphics, audio, video, etc.) is transmitted.
* Clients query the data too frequently or cache it too little
* No data pagination is provided
* No limits are set on the data fields, or too large binary data (graphics, audio, video, etc.) is transmitted.
All these problems must be addressed by setting limitations on field sizes and properly decomposing endpoints. If an entity comprises both “lightweight” data (such as the name and description of a recipe) and “heavy” data (such as the promotional picture of a 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 (e.g., a link to the image). This will also allow for setting different cache policies for different kinds of data.

View File

@ -3,8 +3,8 @@
Let's proceed to the technical problems that API developers face. We begin with the last one described in the introductory chapter: the necessity to synchronize states. Let us imagine that a user creates a request to order coffee through our API. While this request travels from the client to the coffee house and back, many things might happen. Consider the following chain of events:
1. The client sends the order creation request
2. Because of network issues, the request propagates to the server very slowly, and the client gets a timeout;
* therefore, the client does not know whether the query was served or not.
2. Because of network issues, the request propagates to the server very slowly, and the client gets a timeout
* Therefore, the client does not know whether the query was served or not.
3. The client requests the current state of the system and gets an empty response as the initial request still hasn't reached the server:
```
const pendingOrders = await

View File

@ -54,19 +54,19 @@ const pendingOrders = await api.
```
Such a token might be:
* an identifier (or identifiers) of the last modifying operations carried out by the client;
* the last known resource version (modification date, ETag) known to the client.
* An identifier (or identifiers) of the last modifying operations carried out by the client
* The last known resource version (modification date, ETag) known to the client.
Upon getting the token, the server must check that the response (e.g., the list of ongoing operations it returns) matches the token, i.e., the eventual consistency converged. If it did not (the client passed the modification date / version / last order id newer than the one known to the server), one of the following policies or their combinations might be applied:
* the server might repeat the request to the underlying DB or to the other kind of data storage in order to get the newest version (eventually);
* the server might return an error that requires the client to try again later;
* the server queries the main node of the DB, if such a thing exists, or otherwise initiates retrieving the master data.
* The server might repeat the request to the underlying DB or to the other kind of data storage in order to get the newest version (eventually)
* The server might return an error that requires the client to try again later
* The server queries the main node of the DB, if such a thing exists, or otherwise initiates retrieving the master data.
The advantage of this approach is client development convenience (compared to the absence of any guarantees): by preserving the version token, client developers get rid of the possible inconsistency of the data got from API endpoints. There are two disadvantages, however:
* it is still a trade-off between system scalability and a constant inflow of background errors;
* if you're querying master data or repeating the request upon the version mismatch, the load on the master storage is increased in poorly a predictable manner;
* if you return a client error instead, the number of such errors might be considerable, and partners will need to write some additional code to deal with the errors;
* this approach is still probabilistic, and will only help in a limited number of use cases (to be discussed below).
* It is still a trade-off between system scalability and a constant inflow of background errors:
* If you're querying master data or repeating the request upon the version mismatch, the load on the master storage is increased in poorly a predictable manner
* If you return a client error instead, the number of such errors might be considerable, and partners will need to write some additional code to deal with the errors.
* This approach is still probabilistic, and will only help in a limited number of use cases (to be discussed below).
There is also an important question regarding the default behavior of the server if no version token was passed. Theoretically, in this case, master data should be returned, as the absence of the token might be the result of an app crash and subsequent restart or corrupted data storage. However, this implies an additional load on the master node.
@ -77,18 +77,18 @@ Let us state an important assertion: the methods of solving architectural proble
**NB**: the “typical usage profile” stipulation is important: an API implies the variability of client scenarios, and API usage cases might fall into several groups, each featuring quite different error profiles. The classical example is client APIs (where it's an end user who makes actions and waits for results) versus server APIs (where the execution time is per se not so important — but let's say mass parallel execution might be). If this happens, it's a strong signal to make a family of API products covering different usage scenarios, as we will discuss in “[The API Services Range](#api-product-range)” chapter of “The API Product” section of this book.
Let's return to the coffee example, and imagine we implemented the following scheme:
* optimistic concurrency control (through, let's say, the id of the last user's order)
* the “read-your-writes” policy of reading the order list (again with passing the last known order id as a token)
* retrieving master data in the case the token is absent.
* Optimistic concurrency control (through, let's say, the id of the last user's order)
* The “read-your-writes” policy of reading the order list (again with passing the last known order id as a token)
* Retrieving master data in the case the token is absent.
In this case, the order creation error might only happen in one of the two cases:
* the client works with the data incorrectly (does not preserve the identifier of the last order or the idempotency key while repeating the request)
* the client tries to create an order from two different instances of the app that do not share the common state.
* The client works with the data incorrectly (does not preserve the identifier of the last order or the idempotency key while repeating the request)
* The client tries to create an order from two different instances of the app that do not share the common state.
The first case means there is a bug in the partner's code; the second case means that the user is deliberately testing the system's stability — which is hardly a frequent case (or, let's say, the user's phone went off and they quickly switched to a tablet — rather rare case as well, we must admit).
Let's now imagine that we dropped the third requirement — i.e., returning the master data if the token was not provided by the client. We would get the third case when the client gets an error:
* the client application lost some data (restarted or corrupted), and the user tries to replicate the last request.
* The client application lost some data (restarted or corrupted), and the user tries to replicate the last request.
**NB**: the repeated request might happen without any automation involved if, let's say, the user got bored of waiting, killed the app and manually re-orders the coffee again.

View File

@ -54,10 +54,10 @@ Here we assume that task creation requires minimal checks and doesn't wait for a
Thus we naturally came to the pattern of organizing asynchronous APIs through task queues. Here we use the term “asynchronous” logically meaning the absence of mutual *logical* locks: the party that makes a request gets a response immediately and does not wait until the requested procedure is fully carried out being able to continue to interact with the API. *Technically* in modern application environments, locking (of both the client and server) almost universally doesn't happen during long-responding calls. However, *logically* allowing users to work with the API while waiting for a response from a modifying endpoint is error-prone and leads to collisions like the one we described above.
The asynchronous call pattern is useful for solving other practical tasks as well:
* caching operation results and providing links to them (implying that if the client needs to reread the operation result or share it with another client, it might use the task identifier to do so)
* ensuring operation idempotency (through introducing the task confirmation step we will actually get the draft-commit system as discussed in the “[Describing Final Interfaces](#api-design-describing-interfaces)” chapter)
* naturally improving resilience to peak loads on the service as the new tasks will be queuing up (possibly prioritized) in fact implementing the “[token bucket](https://en.wikipedia.org/wiki/Token_bucket)” technique
* organizing interaction in the cases of very long-lasting operations that require more time than typical timeouts (which are tens of seconds in the case of network calls) or can take unpredictable time.
* Caching operation results and providing links to them (implying that if the client needs to reread the operation result or share it with another client, it might use the task identifier to do so)
* Ensuring operation idempotency (through introducing the task confirmation step we will actually get the draft-commit system as discussed in the “[Describing Final Interfaces](#api-design-describing-interfaces)” chapter)
* Naturally improving resilience to peak loads on the service as the new tasks will be queuing up (possibly prioritized) in fact implementing the “[token bucket](https://en.wikipedia.org/wiki/Token_bucket)” technique
* Organizing interaction in the cases of very long-lasting operations that require more time than typical timeouts (which are tens of seconds in the case of network calls) or can take unpredictable time.
Also, asynchronous communication is more robust from a future API development point of view: request handling procedures might evolve towards prolonging and extending the asynchronous execution pipelines whereas synchronous handlers must retain reasonable execution times which puts certain restrictions on possible internal architecture.
@ -70,9 +70,9 @@ However, we must stress that excessive asynchronicity, though appealing to API d
1. If a single queue service is shared by all endpoints, it becomes a single point of failure for the system. If unpublished events are piling up and/or the event processing pipeline is overloaded, all the API endpoints start to suffer. Otherwise, if there is a separate queue service instance for every functional domain, the internal architecture becomes much more complex, making monitoring and troubleshooting increasingly costly.
2. For partners, writing code becomes more complicated. It is not only about the physical volume of code (creating a shared component to communicate with queues is not that complex of an engineering task) but also about anticipating every endpoint to possibly respond slowly. With synchronous endpoints, we assume by default that they respond within a reasonable time, less than a typical response timeout (which, for client applications, means that just a spinner might be shown to a user). With asynchronous endpoints, we don't have such a guarantee as it's simply impossible to provide one.
3. Employing task queues might lead to some problems specific to the queue technology itself, i.e., not related to the business logic of the request handler:
* tasks might be “lost” and never processed
* events might be received in the wrong order or processed twice, which might affect public interfaces
* under the task identifier, wrong data might be published (corresponding to some other task) or the data might be corrupted.
* Tasks might be “lost” and never processed
* Events might be received in the wrong order or processed twice, which might affect public interfaces
* Under the task identifier, wrong data might be published (corresponding to some other task) or the data might be corrupted.
These issues will be totally unexpected by developers and will lead to bugs in applications that are very hard to reproduce.
4. As a result of the above, the question of the viability of such an SLA level arises. With asynchronous tasks, it's rather easy to formally make the API uptime 100.00% — just some requests will be served in a couple of weeks when the maintenance team finds the root cause of the delay. Of course, that's not what API consumers want: their users need their problems solved *now* or at least *in a reasonable time*, not in two weeks.

View File

@ -326,7 +326,7 @@ The inverse approach to the problem is to never provide more than one page of da
* If the endpoint features a search algorithm that fetches the most relevant data. As we are well aware, nobody needs a second search result page.
* If the endpoint is needed to *modify* data. For example, the partner's service retrieves all “new” orders to transit them into the “accepted” status; then pagination is not needed at all as with each request the partner is *removing* items from the top of the list.
* The important case for such modifications is marking the received data as “read”.
* finally, if the endpoint is needed to access only real-time “raw” data while the processed and classified data are available through other interfaces.
* Finally, if the endpoint is needed to access only real-time “raw” data while the processed and classified data are available through other interfaces.
If none of the approaches above works, our only solution is changing the subject area itself. If we can't consistently enumerate list elements, we need to find a facet of the same data that we *can* enumerate. In our example with the ongoing orders we might make an ordered list of the *events* of creating new orders:

View File

@ -115,15 +115,15 @@ Obviously, we can't guarantee partners don't make any of these mistakes. The onl
1. The system state must be restorable. If the partner erroneously responded that messages are processed while they are not, there must be a possibility for them to redeem themselves and get the list of missed events and/or the full system state and fix all the issues
2. Help partners to write proper code by describing in the documentation all unobvious subtleties that inexperienced developers might be unaware of:
* idempotency keys for every operation
* delivery guarantees (“at least once,” “exactly ones,” etc.; see the [reference description](https://docs.confluent.io/kafka/design/delivery-semantics.html) on the example of Apache Kafka API)
* possibility of the server generating parallel requests and the maximum number of such requests at a time
* guarantees of message ordering (i.e., the notifications are always delivered ordered from the oldest one to the newest one) or the absence of such guarantees
* the sizes of all messages and message fields in bytes
* the retry policy in case an error is returned by the partner's server
* Idempotency keys for every operation
* Delivery guarantees (“at least once,” “exactly ones,” etc.; see the [reference description](https://docs.confluent.io/kafka/design/delivery-semantics.html) on the example of Apache Kafka API)
* Possibility of the server generating parallel requests and the maximum number of such requests at a time
* Guarantees of message ordering (i.e., the notifications are always delivered ordered from the oldest one to the newest one) or the absence of such guarantees
* The sizes of all messages and message fields in bytes
* The retry policy in case an error is returned by the partner's server
3. Implement a monitoring system to check the health of partners' endpoints:
* if a large number of errors or timeouts occurs, it must be escalated (including notifying the partner about the problem), probably with several escalation tiers
* if too many un-processed notifications are stuck, there must be a mechanism of controllable degradation (limiting the number of requests toward the partner, e.g. cutting the demand by disallowing some users to make an order) up to fully disconnecting the partner from the platform.
* If a large number of errors or timeouts occurs, it must be escalated (including notifying the partner about the problem), probably with several escalation tiers,
* If too many un-processed notifications are stuck, there must be a mechanism of controllable degradation (limiting the number of requests toward the partner, e.g. cutting the demand by disallowing some users to make an order) up to fully disconnecting the partner from the platform.
#### Message Queues
@ -132,9 +132,9 @@ As for internal APIs, the *webhook* technology (i.e., the possibility to program
To solve these problems, and also to ensure better horizontal scalability, [message queues](https://en.wikipedia.org/wiki/Message_queue) were developed, most notably numerous pub/sub pattern implementations. At present moment, pub/sub-based architectures are very popular in enterprise software development, up to switching any inter-service communication to message queues.
**NB**: let us note that everything comes with a price, and these delivery guarantees and horizontal scalability are not an exclusion:
* all communication becomes eventually consistent with all the implications
* decent horizontal scalability and cheap message queue usage are only achievable with at least once/at most once policies and no ordering guarantee
* queues might accumulate unprocessed events, introducing increasing delays, and solving this issue on the subscriber's side might be quite non-trivial.
* All communication becomes eventually consistent with all the implications
* Decent horizontal scalability and cheap message queue usage are only achievable with at least once/at most once policies and no ordering guarantee
* Queues might accumulate unprocessed events, introducing increasing delays, and solving this issue on the subscriber's side might be quite non-trivial.
Also, in public APIs both technologies are frequently used in conjunction: the API backend sends a task to call the *webhook* in the form of publishing an event which the specially designed internal service will try to process by making the call.

View File

@ -60,11 +60,11 @@ A full example of an API implementing the naïve approach would look like this:
```
// Partially rewrites the order:
// * resets the delivery address
// * Resets the delivery address
// to the default values
// * leaves the first beverage
// * Leaves the first beverage
// intact
// * removes the second beverage
// * Removes the second beverage.
PATCH /v1/orders/{id}
{
// “Special” value #1:
@ -98,11 +98,11 @@ The solution could be enhanced by introducing explicit control sequences instead
```
// Partially rewrites the order:
// * resets the delivery address
// * Resets the delivery address
// to the default values
// * leaves the first beverage
// * Leaves the first beverage
// intact
// * removes the second beverage
// * Removes the second beverage.
PATCH /v1/orders/{id}?⮠
// A meta filter: which fields
// are allowed to be modified
@ -205,8 +205,8 @@ Applying this pattern is typically sufficient for most APIs that manipulate comp
The idea of applying changes to a resource state through independent atomic idempotent operations looks attractive as a conflict resolution technique as well. As subcomponents of the resource are fully overwritten, it is guaranteed that the result of applying the changes will be exactly what the user saw on the screen of their device, even if they had observed an outdated version of the resource. However, this approach helps very little if we need a high granularity of data editing as it's implemented in modern services for collaborative document editing and version control systems (as we will need to implement endpoints with the same level of granularity, literally one for each symbol in the document).
To make true collaborative editing possible, a specifically designed format for describing changes needs to be implemented. It must allow for:
* ensuring the maximum granularity (each operation corresponds to one distinct user's action)
* implementing conflict resolution policies.
* Ensuring the maximum granularity (each operation corresponds to one distinct user's action)
* Implementing conflict resolution policies.
In our case, we might take this direction:

View File

@ -17,9 +17,8 @@ A rule of thumb is very simple: if some functionality might be withheld — then
Your obligations to maintain some functionality must be stated as clearly as possible, especially regarding those environments and platforms where no native capability to restrict access to undocumented functionality exists. Unfortunately, developers tend to consider some private features they found to be eligible for use, thus presuming the API vendor shall maintain them intact. The policy on such “findings” must be articulated explicitly. At the very least, in the case of such non-authorized usage of undocumented functionality, you might refer to the docs and be within your rights in the eyes of the community.
However, API developers often legitimize such gray zones themselves, for example, by:
* returning undocumented fields in endpoint responses;
* using private functionality in code samples — in the docs, while responding to support messages, in conference talks, etc.
* Returning undocumented fields in endpoint responses
* Using private functionality in code samples — in the docs, while responding to support messages, in conference talks, etc.
One cannot make a partial commitment. Either you guarantee this code will always work or do not slip the slightest note such functionality exists.

View File

@ -9,8 +9,8 @@ Therefore, in the following chapters, we will test the robustness [our study API
Let's start with the basics. Imagine that we haven't exposed any other functionality but searching for offers and making orders, thus providing an API of two methods: `POST /offers/search` and `POST /orders`.
Let us make the next logical step there and suppose that partners will wish to dynamically plug their own coffee machines (operating some previously unknown types of API) into our platform. To allow doing so, we have to negotiate a callback format that would allow us to call partners' APIs and expose two new endpoints providing the following capabilities:
* registering new API types in the system;
* providing the list of the coffee machines and their API types;
* Registering new API types in the system
* Providing the list of the coffee machines and their API types.
For example, we might provide a second API family (the partner-bound one) with the following methods:
@ -38,12 +38,12 @@ PUT /v1/partners/{partnerId}/coffee-machines
```
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.
* 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.
Now the partners might dynamically plug their coffee machines in and get the orders. But we now will do the following exercise:
* enumerate all the implicit assumptions we have made;
* enumerate all the implicit coupling mechanisms we need to have the platform functioning properly.
* Enumerate all the implicit assumptions we have made
* Enumerate all the implicit coupling mechanisms we need to have the platform functioning properly.
It may look like there are no such things in our API since it's quite simple and basically just describes making some HTTP call — but that's not true.
1. It is implied that every coffee machine supports every order option like varying the beverage volume.

View File

@ -38,9 +38,8 @@ The first problem is obvious to those who read the “[Describing Final Interfac
```
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.
* 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.
The flaw in the first option is that a partner might be willing to use the service in some new country or language — and will be unable to do so until the API supports them. The flaw in the second option is that it works with predefined volumes only, so you can't order an arbitrary beverage volume. So the very first step we've made effectively has us trapped.
@ -222,8 +221,8 @@ GET /v1/recipes/{id}/run-data/{api_type}
```
Then developers would have to make this trick to get coffee prepared:
* learn the API type of the specific coffee machine;
* get the execution description, as stated above;
* depending on the API type, run some specific commands.
* Learn the API type of the specific coffee machine.
* Get the execution description, as stated above.
* Depending on the API type, run some specific commands.
Obviously, such an interface is absolutely unacceptable, simply because in the majority of use cases developers don't care at all, which API type the specific coffee machine runs. To avoid the necessity of introducing such bad interfaces we created a new “program” entity, which constitutes merely a context identifier, just like a “recipe” entity does. A `program_run_id` entity is also organized in this manner, it also possesses no specific properties, being *just* a program run identifier.

View File

@ -44,8 +44,8 @@ We may easily disprove the \#2 statement, and that will uncover the implications
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.
Programmable takeout approval requires one more endpoint, let's say, `program_takeout_endpoint`. And so we've lost our way in a forest of five endpoints:
* to have vending machines integrated a partner must implement the `program_takeout_endpoint`, but doesn't need the `program_modify_endpoint`;
* to have regular coffee houses integrated a partner must implement the `program_modify_endpoint`, but doesn't need the `program_takeout_endpoint`.
* To have vending machines integrated a partner must implement the `program_takeout_endpoint`, but doesn't need the `program_modify_endpoint`.
* To have regular coffee houses integrated a partner must implement the `program_modify_endpoint`, but doesn't need the `program_takeout_endpoint`.
Furthermore, we have to describe both endpoints in the docs. It's quite natural that the `takeout` endpoint is very specific; unlike requesting contactless delivery, which we hid under the pretty general `modify` endpoint, operations like takeout approval will require introducing a new unique method every time. After several iterations, we would have a scrapyard, full of similarly looking methods, mostly optional — but developers would need to study the docs nonetheless to understand, which methods are needed in your specific situation, and which are not.
@ -56,14 +56,14 @@ We actually don't know, whether in the real world of coffee machine APIs this pr
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 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.
* 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.
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.
* 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:
@ -105,15 +105,15 @@ registerProgramRunHandler(
**NB**: In the case of HTTP API, a corresponding example would look rather bulky as it would require implementing several additional endpoints for the message exchange like `GET /program-run/events` and `GET /partner/{id}/execution/events`. We would leave this exercise to the reader. Also, it's worth mentioning that in real-world systems such event queues are usually organized using external event messaging systems like Apache Kafka or Amazon SNS/SQS.
At this point, a mindful reader might begin protesting because if we take a look at the nomenclature of the new entities, we will find that nothing changed in the problem statement. It actually became even more complicated:
* instead of calling the `takeout` method, we're now generating a pair of `takeout_requested` / `takeout_ready` events;
* instead of a long list of methods that shall be implemented to integrate the partner's API, we now have a long list of context entities and events they generate;
* and with regards to technological progress, we've changed nothing: now we have deprecated fields and events instead of deprecated methods.
* Instead of calling the `takeout` method, we're now generating a pair of `takeout_requested` / `takeout_ready` events
* Instead of a long list of methods that shall be implemented to integrate the partner's API, we now have a long list of context entities and events they generate
* And with regards to technological progress, we've changed nothing: now we have deprecated fields and events instead of deprecated methods.
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 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.
* 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.
It's ultimately possible that both sides would know nothing about each other and wouldn't interact at all, and this might happen with the evolution of underlying technologies.
@ -164,8 +164,8 @@ registerProgramRunHandler(
Again, this solution might look counter-intuitive, since we efficiently returned to strong coupling via strictly defined methods. But there is an important difference: we're bothering ourselves with weak coupling because we expect alternative implementations of the *lower* abstraction level to pop up. Situations with different realizations of *higher* abstraction levels emerging are, of course, possible, but quite rare. The tree of alternative implementations usually grows from root to leaves.
Another reason to justify this solution is that major changes occurring at different abstraction levels have different weights:
* if the technical level is under change, that must not affect product qualities and the code written by partners;
* if the product is changing, e.g., we start selling flight tickets instead of preparing coffee, there is literally no sense to preserve backward compatibility at technical abstraction levels. Ironically, we may actually make our API sell tickets instead of brewing coffee without breaking backward compatibility, but the partners' code will still become obsolete.
* If the technical level is under change, that must not affect product qualities and the code written by partners.
* If the product is changing, e.g., we start selling flight tickets instead of preparing coffee, there is literally no sense to preserve backward compatibility at technical abstraction levels. Ironically, we may actually make our API sell tickets instead of brewing coffee without breaking backward compatibility, but the partners' code will still become obsolete.
In conclusion, as higher-level APIs are evolving more slowly and much more consistently than low-level APIs, reverse strong coupling might often be acceptable or even desirable, at least from the price-quality ratio point of view.

View File

@ -13,13 +13,13 @@ However, there is an unanswered question: how should we design the entity nomenc
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 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}`,
or we can encode this data in the single `offer_id`;
or we can encode this data in the single `offer_id`.
* to have this search result displayed in the app, we need a different data set: `name`, `description`, and formatted and localized prices.
* To have this search result displayed in the app, we need a different data set: `name`, `description`, and formatted and localized prices.
So our interface (let us call it `ISearchResult`) is actually a composition of two other interfaces: `IOrderParameters` (an entity that allows for creating an order) and `ISearchItemViewParameters` (some abstract representation of the search result in the UI). This interface split should automatically lead us to additional questions:

View File

@ -5,9 +5,9 @@ Apart from the abovementioned abstract principles, let us give a list of concret
##### Remember the Iceberg's Waterline
If you haven't given any formal guarantee, it doesn't mean that you can violate informal ones. Often, just fixing bugs in APIs might render some developers' code inoperable. We might illustrate it with a real-life example that the author of this book has actually faced once:
* there was an API to place a button into a visual container; according to the docs, it was taking its position (offsets to the container's corner) as a mandatory argument;
* in reality, there was a bug: if the position was not supplied, no exception was thrown; buttons were simply stacked in the corner one after another;
* after the error had been fixed, we got a bunch of complaints: clients did really use this flaw to stack the buttons in the container's corner.
* There was an API to place a button into a visual container. According to the docs, it was taking its position (offsets to the container's corner) as a mandatory argument.
* In reality, there was a bug: if the position was not supplied, no exception was thrown. Buttons were simply stacked in the corner one after another.
* After the error had been fixed, we got a bunch of complaints: clients did really use this flaw to stack the buttons in the container's corner.
If fixing an error might somehow affect real customers, you have no other choice but to emulate this erroneous behavior until the next major release. This situation is quite common if you develop a large API with a huge audience. For example, operating systems developers literally have to transfer old bugs to new OS versions.
@ -21,22 +21,22 @@ 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 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 backward 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.
* Usually, you have no guarantees that the partner will maintain backward 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.
The best practice is quite the opposite: isolate the third-party API usage, i.e., develop an abstraction level that will allow for:
* keeping backward compatibility intact because of extension capabilities incorporated in the API design;
* negating partner's problems by technical means:
* limiting the partner's API usage in case of load surges;
* implementing the retry policies or other methods of recovering after failures;
* caching some data and states to have the ability to provide some (at least partial) functionality even if the partner's API is fully unreachable;
* finally, configuring an automatic fallback to another partner or alternative API.
* Keeping backward compatibility intact because of extension capabilities incorporated in the API design
* Negating partner's problems by technical means:
* Limiting the partner's API usage in case of load surges
* Implementing retry policies or other methods of recovering after failures
* Caching some data and states to have the ability to provide some (at least partial) functionality even if the partner's API is fully unreachable
* Finally, configuring an automatic fallback to another partner or alternative API.
##### Implement Your API Functionality Atop Public Interfaces
There is an antipattern that occurs frequently: API developers use some internal closed implementations of some methods which exist in the public API. It happens because of two reasons:
* often the public API is just an addition to the existing specialized software, and the functionality, exposed via the API, isn't being ported back to the closed part of the project, or the public API developers simply don't know the corresponding internal functionality exists;
* in the course of extending the API, some interfaces become abstract, but the existing functionality isn't affected; imagine that while implementing the `PUT /formatters` interface described in the “[Strong Coupling and Related Problems](#back-compat-strong-coupling)” chapter API developers have created a new, more general version of the volume formatter but hasn't changed the implementation of the existing one, so it continues working for pre-existing languages.
* Often the public API is just an addition to the existing specialized software, and the functionality, exposed via the API, isn't being ported back to the closed part of the project, or the public API developers simply don't know the corresponding internal functionality exists.
* In the course of extending the API, some interfaces become abstract, but the existing functionality isn't affected. Imagine that while implementing the `PUT /formatters` interface described in the “[Strong Coupling and Related Problems](#back-compat-strong-coupling)” chapter API developers have created a new, more general version of the volume formatter but hasn't changed the implementation of the existing one, so it continues working for pre-existing languages.
There are obvious local problems with this approach (like the inconsistency in functions' behavior, or the bugs which were not found while testing the code), but also a bigger one: your API might be simply unusable if a developer tries any non-mainstream approach, because of performance issues, bugs, instability, etc., as the API developers themselves never tried to use this public interface for anything important.

View File

@ -12,9 +12,9 @@ We need to apply these principles to an HTTP-based interface, adhering to the le
* Properties of the operation, such as safety, cacheability, idempotency, as well as the symmetry of `GET` / `PUT` / `DELETE` methods, request and response headers, response status codes, etc., must align with the specification.
**NB**: we're deliberately skipping many nuances of the standard:
* a caching key might be composite (i.e., include request headers) if the response contains the `Vary` header.
* an idempotency key might also be composite if the request contains the `Range` header.
* if there are no explicit cache control headers, the caching policy will not be defined by the HTTP verb alone. It will also depend on the response status code, other request and response headers, and platform policies.
* A caching key might be composite (i.e., include request headers) if the response contains the `Vary` header.
* An idempotency key might also be composite if the request contains the `Range` header.
* If there are no explicit cache control headers, the caching policy will not be defined by the HTTP verb alone. It will also depend on the response status code, other request and response headers, and platform policies.
To keep the chapter size reasonable, we will not discuss these details, but we highly recommend reading the standard thoroughly.

View File

@ -9,9 +9,9 @@ There are two important statements regarding APIs viewed as products.
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 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.
* 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.

View File

@ -42,13 +42,14 @@ B2B services are a special case. As B2B Service providers benefit from offering
**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 employ quite a specific technological stack, and the API is poorly optimized to work with other programming languages / operating systems / frameworks;
* for external customers, the learning curve will be pretty flat as they can't take a look at the source code or talk to the API developers directly, unlike internal customers that are much more familiar with the API concepts;
* documentation often covers only some subset of use cases needed by internal customers;
* the API services ecosystem which we will describe in “[The API Services Range](#api-product-range)” chapter usually doesn't exist.
* Internal customers employ quite a specific technological stack, and the API is poorly optimized to work with other programming languages / operating systems / frameworks.
* For external customers, the learning curve will be pretty flat as they can't take a look at the source code or talk to the API developers directly, unlike internal customers that are much more familiar with the API concepts.
* Documentation often covers only some subset of use cases needed by internal customers.
* The API services ecosystem which we will describe in “[The API Services Range](#api-product-range)” chapter usually doesn't exist.
* 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 absurd with obvious problems being neglected for years;
* technical support of external customers is financed on leftovers.
* 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.
* Often, developers of internal services break backward compatibility or issue new major versions whenever they need it and don't care about the consequences of these decisions for external API partners.
All those problems lead to having an external API that actually hurts the company's reputation, not improves it. 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.
@ -61,9 +62,9 @@ In this case, we talk mostly about things like widgets and search engines as dir
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 in partners' apps, 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 example, let's imagine that our main business is something totally unrelated to the coffee machine APIs, 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 for such self-promotion might also differ:
* 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”).
* 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, i.e., a network of developers (or customers, or business owners) who are loyal to the product. The benefits of having such a community might be substantial: lowering the technical support costs, getting a convenient channel for publishing announcements regarding new services and new releases, and obtaining beta users for upcoming products.

View File

@ -3,15 +3,15 @@
The above-mentioned fragmentation of the API target audience, i.e., 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 *businesses* would solve those problems if appropriate tools existed;
* understanding what technical solutions for *developers* might exist to help them implement the functionality businesses would ask for, and where are the boundaries of their applicability.
* Grasping how *end users* would like to have their problems solved
* Projecting how *businesses* would solve those problems if appropriate tools existed
* Understanding what technical solutions for *developers* might exist to help them implement the functionality businesses would ask for, 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 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.
Still, in the majority of cases, we 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 heuristic “step right” to outline future functionality for end users and one “step left” to evaluate possible technical solutions.
* 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 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: detailing the countenance of the forthcoming will make your abstract KPIs and theoretical benefits of having an API more and more concrete.)

View File

@ -3,30 +3,24 @@
As we have described in the previous chapters, managing an API product requires building relations with both business partners and developers. (Ideally, with end users as well; though this option is seldom available to API providers.)
Let's start with developers. The specifics of software engineers as an auditory are the following:
* 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 a certain aptitude towards, let's say, specific programming languages or frameworks; 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;
* it is very hard to communicate to them via regular marketing channels; they get information from highly specialized communities, and they stick to opinions proved by concrete numbers and examples (ideally, code samples);
* the “influencers” words are not very valuable to them, as no opinions are trusted if unsubstantiated;
* the Open Source and free software ideas are widespread among developers; if you try to make money out of things that must be free and/or open from their point of view (for example, by proclaiming interfaces an intellectual property), you will face resistance (and views on this “musts”… differ).
* 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 a certain aptitude towards, let's say, specific programming languages or frameworks; 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.
* It is very hard to communicate to them via regular marketing channels; they get information from highly specialized communities, and they stick to opinions proved by concrete numbers and examples (ideally, code samples).
* The “influencers” words are not very valuable to them, as no opinions are trusted if unsubstantiated.
* The Open Source and free software ideas are widespread among developers If you try to make money out of things that must be free and/or open from their point of view (for example, by proclaiming interfaces an intellectual property), you will face resistance (and views on this “musts”… differ).
Because of the above-mentioned specifics (first of all, the relative insignificance of influencers and the critical attitude towards promotions), you will have to communicate to developers via very specific media:
* collective blogs (like the “r/programming” subreddit or dev.to)
* Collective blogs (like the “r/programming” subreddit or dev.to)
* Q&A sites (StackOverflow, Experts Exchange)
* educational services (CodeAcademy, Udemy)
* technical conferences and webinars.
* Educational services (CodeAcademy, Udemy)
* Technical conferences and webinars.
In all these channels, the direct advertising of your API is either problematic or impossible. (Well, strictly speaking, you may buy the banner on one of the sites advertising the advantages of your API, but we hardly doubt it will improve your relations with developers.) You need to generate some valuable and/or interesting content for them, which will improve the knowledge of your API. And this is the job for your developers: writing articles, answering questions, recording webinars, and giving pitches.
Developers do like sharing the experience, and will probably be eager to do it — during their work hours. A proper conference talk, let alone an educational course, requires a lot of preparation time. Out of this book's author's experience, two things are crucial for tech-PR:
* incentives, even nominal ones — the job of promoting a product should be rewarded;
* methodicalness and quality standards — you might actually do the content review just like you do the code review.
* Incentives, even nominal ones — the job of promoting a product should be rewarded
* Methodicalness and quality standards — you might actually do the content review just like you do the code review.
Nothing could make the worse counter-advertising for your product than a poorly prepared pitch (as we said, the mistakes will be inevitably found and pointed to) or a badly camouflaged commercial in a form of a pitch (the reason is actually the same). Texts are to be worked upon: pay attention to the structure, logic, and tempo of the narration. Even a technical story must be finely constructed; after it's ended, the listeners must have a clear understanding of what idea you wanted to communicate (and it'd rather be somehow coupled with your API's fitness for their needs).
@ -35,12 +29,12 @@ A word on “evangelists” (those are people who have some credibility in the I
#### Open Source
The important question which sooner or later will stand in any API vendor's way is making the source code open. This decision has both advantages and disadvantages:
* you will improve the knowledge of the brand, and some respect will be paid to you by the IT community;
* that's given your code is finely written and commented;
* you will get some additional feedback — ideally, pull requests from third-party developers;
* and you will also get a number of inquiries and comments ranging from useless to obviously provocative ones, to which you will have to respond politely;
* donating code to open source makes developers trust the company more, and affects their readiness to rely on the platform;
* but it also increases risks, both from the information security point of view and from the product one, as a dissatisfied community might fork your repo and create a competing product.
* You will improve the knowledge of the brand, and some respect will be paid to you by the IT community.
* That's given your code is finely written and commented.
* You will get some additional feedback — ideally, pull requests from third-party developers
* And you will also get a number of inquiries and comments ranging from useless to obviously provocative ones, to which you will have to respond politely.
* Donating code to open source makes developers trust the company more, and affects their readiness to rely on the platform.
* But it also increases risks, both from the information security point of view and from the product one, as a dissatisfied community might fork your repo and create a competing product.
Finally, just the preparations to make the code open might be very expensive: you need to clean the code, switch to open building and testing tools, and remove all references to proprietary resources. This decision is to be made very cautiously, after having all pros and cons elaborated over. We might add that many companies try to reduce the risks by splitting the API code into two parts, the open one and the proprietary one, and also by selecting a license that disallows harming the company's interests by using the open-sourced code (for example, by prohibiting selling hosted solutions or by requiring the derivative works to be open-sourced as well).
@ -49,8 +43,8 @@ Finally, just the preparations to make the code open might be very expensive: yo
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 types of audiences:
* beginners and amateurs, for whom each of those integration tasks would be completely new and unexplored territory;
* 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
* Professional developers who possess vast experience in integrating different third-party systems.
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.

View File

@ -1,8 +1,8 @@
### [Communicating with Business Owners][api-product-business-comms]
The basics of interacting with business partners are to some extent paradoxically contrary to the basics of communicating with developers:
* on one hand, partners are much more loyal and sometimes even enthusiastic regarding opportunities you offer (especially free ones);
* on the other hand, communicating the meaning of your offer to the business owners is much more complicated than conveying it to developers, as it's generally hard to explain what are the advantages of integrating via APIs (as a concept).
* On one hand, partners are much more loyal and sometimes even enthusiastic regarding opportunities you offer (especially free ones).
* On the other hand, communicating the meaning of your offer to the business owners is much more complicated than conveying it to developers, as it's generally hard to explain what are the advantages of integrating via APIs (as a concept).
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.

View File

@ -7,10 +7,10 @@ The important rule of API product management any major API provider will soon le
Usually, any functionality available through an API might be split into independent units. For example, in our coffee API, there are offer search endpoints and order processing endpoints. Nothing could prevent us from either pronouncing those functional clusters different APIs or, vice versa, considering them as parts of one API.
Different companies employ different approaches to determining the granularity of API services, i.e., what is counted as a separate product and what is not. To some extent, this is a matter of convenience and taste judgment. Consider splitting an API into parts if:
* it makes sense for partners to integrate only one API part, i.e., there are some isolated subsets of the API that alone provide enough means to solve users' problems;
* API parts might be versioned separately and independently, and it is meaningful from the partners' point of view (this usually means that those “isolated” APIs are either fully independent or maintain strict backward compatibility and introduce new major versions only when it's absolutely necessary; otherwise, maintaining a matrix which API \#1 version is compatible with which API \#2 version will soon become a catastrophe);
* it makes sense to set tariffs and limits for each API service independently;
* the auditory of the API segments (either developers, business owners, or end users) is not overlapping, and “selling” granular API to customers is much easier than aggregated.
* It makes sense for partners to integrate only one API part, i.e., there are some isolated subsets of the API that alone provide enough means to solve users' problems.
* API parts might be versioned separately and independently, and it is meaningful from the partners' point of view (this usually means that those “isolated” APIs are either fully independent or maintain strict backward compatibility and introduce new major versions only when it's absolutely necessary; otherwise, maintaining a matrix which API \#1 version is compatible with which API \#2 version will soon become a catastrophe).
* It makes sense to set tariffs and limits for each API service independently.
* The auditory of the API segments (either developers, business owners, or end users) is not overlapping, and “selling” granular API to customers is much easier than aggregated.
**NB**: still, those split APIs might still be a part of a united SDK, to make programmers' lives easier.

View File

@ -7,33 +7,27 @@ Of course, the most explicit metric is money: if your API is monetized directly
The obvious key performance indicator (KPI) \#1 is the number of end users and the number of integrations (i.e., partners using the API). Normally, they are in some sense a business health barometer: if there is a normal competitive situation among the API suppliers, and all of them are more or less in the same position, then the figure of how many developers (and consequently, how many end users) are using the API is the main metric of success of the API product.
However, sheer numbers might be deceiving, especially if we talk about free-to-use integrations. There are several factors that make them less reliable:
* the high-level API services that are meant for point-and-click integration (see the previous chapter) are significantly distorting the statistics, especially if the competitors don't provide such services; typically, for one full-scale integration there will be tens, maybe hundreds, of those lightweight embedded widgets;
* thereby, it's crucial to have partners counted for each kind of the integration independently;
* partners tend to use the API in suboptimal ways:
* embed it at every website page / application screen instead of only those where end users can really interact with the API;
* 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 high-level API services that are meant for point-and-click integration (see the previous chapter) are significantly distorting the statistics, especially if the competitors don't provide such services; typically, for one full-scale integration there will be tens, maybe hundreds, of those lightweight embedded widgets.
* Thereby, it's crucial to have partners counted for each kind of the integration independently.
* Partners tend to use the API in suboptimal ways:
* Embed it at every website page / application screen instead of only those where end users can really interact with the API
* 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.
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, i.e., 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:
* if the API is monetized through displaying ads, then the user's activity towards those ads (e.g., clicks, interactions) is to be measured;
* if the API attracts customers to the core service, then count the transitions;
* if the API is needed for collecting feedback and gathering UGC, then calculate the number of reviews left and entities edited.
* If the API is monetized through displaying ads, then the user's activity towards those ads (e.g., clicks, interactions) is to be measured.
* If the API attracts customers to the core service, then count the transitions.
* If the API is needed for collecting feedback and gathering UGC, then calculate the number of reviews left and entities edited.
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 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;
* the proper KPI should be formulated based on the number of target actions that are made through the platform;
* the definition of target action depends on the monetization model and might be quite straightforward (like the number of paying partners, or the number of paid ad clicks) or, to the contrary, pretty implicit (like the growth of the company's developer blog auditory).
* 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.
* The proper KPI should be formulated based on the number of target actions that are made through the platform.
* The definition of target action depends on the monetization model and might be quite straightforward (like the number of paying partners, or the number of paid ad clicks) or, to the contrary, pretty implicit (like the growth of the company's developer blog auditory).
#### SLA
@ -48,8 +42,8 @@ Another extremely important hygienic minimum is the informational security of th
#### Comparing to Competitors
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?
* what proportion of the growth is caused by the growth of the market, and what is related to your efforts?
* 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?
* 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 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 a study, you will have to either commission such work or make your own estimations — conversely, through interviewing potential customers.

View File

@ -1,21 +1,18 @@
### [Identifying Users and Preventing Fraud][api-product-antifraud]
In the context of working with an API, we talk about two kinds of users of the system:
* users-developers, i.e., your partners writing code atop of the API;
* end users interacting with applications implemented by the users-developers.
* Users-developers, i.e., your partners writing code atop of the API
* End users interacting with applications implemented by the users-developers.
In most cases, you need to have both of them identified (in a technical sense: discern one unique customer 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 number of resources (ultimately, money) that are spent (and gained) on each user.
* To understand the system's limits and to be capable of planning its growth
* 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.
@ -44,14 +41,14 @@ One way or another, a problem of independent validation arises: how can we contr
Mobile applications might be conveniently tracked through their identifiers in the corresponding store (Google Play, App Store, etc.), so it makes sense to require this identifier to be passed by partners as an API initialization parameter. Websites with some degree of confidence might be identified by the `Referer` and `Origin` HTTP headers.
This data is not itself reliable, but it allows for making cross-checks:
* if a key was issued for one specific domain but requests are coming with a different `Referer`, it makes sense to investigate the situation and maybe ban the possibility to access the API with this `Referer` or this key;
* if an application initializes API by providing a key registered to another application, it makes sense to contact the store administration and ask for removing one of the apps.
* If a key was issued for one specific domain but requests are coming with a different `Referer`, it makes sense to investigate the situation and maybe ban the possibility to access the API with this `Referer` or this key.
* If an application initializes API by providing a key registered to another application, it makes sense to contact the store administration and ask for removing one of the apps.
**NB**: don't forget to set infinite limits for using the API with the `localhost`, `127.0.0.1` / `[::1]` `Referer`s, and also for your own sandbox if it exists. Yes, abusers will sooner or later learn this fact and will start exploiting it, but otherwise, you will ban local development and your own website much sooner than that.
The general conclusion is:
* it is highly desirable to have partners formally identified (either through obtaining API keys or by providing contact data such as website domain or application identifier in a store while initializing the API);
* this information shall not be trusted unconditionally; there must be double-checking mechanisms that identify suspicious requests.
* It is highly desirable to have partners formally identified (either through obtaining API keys or by providing contact data such as website domain or application identifier in a store while initializing the API).
* This information shall not be trusted unconditionally; there must be double-checking mechanisms that identify suspicious requests.
#### Identifying End Users
@ -63,9 +60,9 @@ Usually, you can put forward some requirements for self-identifying of partners,
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, i.e., 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.
* The cumulative number of requests by networks, i.e., 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.
An abnormal number of requests in one network might be evidence of the API being actively used inside some corporative environment (or NATs being widespread in the region).

View File

@ -3,9 +3,9 @@
Implementing the paradigm of a centralized system of preventing partner endpoints-bound fraud, which we described in the previous chapter, in practice faces non-trivial difficulties.
The task of filtering out illicit API requests comprises three steps:
* identifying suspicious users;
* optionally, asking for an additional authentication factor;
* making decisions and applying access restrictions.
* Identifying suspicious users
* Optionally, asking for an additional authentication factor
* Making decisions and applying access restrictions.
##### Identifying Suspicious Users
@ -30,10 +30,10 @@ Other popular mechanics of identifying robots include offering a bait (“honeyp
##### Restricting Access
The illusion of having a broad choice of technical means of identifying fraud users should not deceive you as you will soon discover the lack of effective methods of restricting those users. Banning them by cookie / `Referer` / `User-Agent` makes little to no impact as this data is supplied by clients, and might be easily forged. In the end, you have four mechanisms for suppressing illegal activities:
* banning users by IP (networks, autonomous systems)
* requiring mandatory user identification (maybe tiered: login / login with confirmed phone number / login with confirmed identity / login with confirmed identity and biometrics / etc.)
* returning fake responses
* filing administrative abuse reports.
* Banning users by IP (networks, autonomous systems)
* Requiring mandatory user identification (maybe tiered: login / login with confirmed phone number / login with confirmed identity / login with confirmed identity and biometrics / etc.)
* Returning fake responses
* Filing administrative abuse reports.
The problem with the first option is the collateral damage you will inflict, especially if you have to ban subnets.
@ -56,18 +56,13 @@ Let's now move to the second type of unlawful API usage, namely using in the mal
1. Maintaining metrics collection by IP addresses and subnets might be of use in this case as well. If the malefactor's app isn't a public one but rather targeted to some closed audience, this fact will be visible in the dashboards (and if you're lucky enough, you might also find suspicious `Referer`s, public access to which is restricted).
2. Allowing partners to restrict the functionality available under specific API keys:
* 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 to prohibit API calls outside of countries and cities they work in).
* 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 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;
* instead of API keys, time-based one-time passwords (TOTP) might be used; these tokens are valid for a short period of time only (typically, one minute), which makes using stolen keys much more complicated.
* 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 for a short period of time only (typically, one minute), which makes using stolen keys much more complicated.
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.

View File

@ -11,8 +11,8 @@ From banning users, let's change the topic to supporting them. First of all, an
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 regard to the terms of service and the SLA (and that's usually about responding to business owners' inquiries);
* helping developers with technical issues.
* 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.
@ -31,10 +31,8 @@ Importantly, whatever options you choose, it's still the API developers in the s
1. You must take into account working with 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 about the perspective of coping with 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 perform support functions; it might be someone on the team (and not necessarily a developer) or some active community member;
* the remaining load must be distributed among the developers equally and fairly, up to introducing the duty calendar.
* Try to find people with a customer-oriented mindset, who like this activity, and encourage them (including financial stimulus) to perform support functions; it might be someone on the team (and not necessarily a developer) or some active community member.
* The remaining load must be distributed among the developers equally and fairly, up to introducing the duty calendar.
And of course, analyzing the questions is a useful exercise to populate FAQs and improve the documentation and the first-line support scripts.

View File

@ -32,39 +32,39 @@ Today, the method nomenclature descriptions are frequently additionally exposed
##### Code Samples
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 a situation when a synthetic code sample, totally meaningless in the real world, was mindlessly replicated by developers in abundance.
* 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 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, e.g., the reference might contain code samples relevant to the entity being described.
##### Sandboxes
Code samples will be much more useful to developers if they are “live,” i.e., 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.
* 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.
##### Tutorial
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 “book” that you write to explain to the reader how to work with your API. So, a proper tutorial must follow book-writing patterns, i.e., 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, i.e., the “happy paths”;
* proper reactions to program errors that could happen;
* detailed studies on advanced API functionality (with detailed examples).
* 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, i.e., the “happy paths”
* Proper reactions to program errors that could happen
* Detailed studies on advanced API functionality (with detailed examples).
Usually, 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. Frequently, 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.
* 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.
##### Frequently Asked Questions and Knowledge Bases
After you publish the API and start supporting users (see the previous chapter) you will also accumulate some knowledge of what questions are asked most frequently. If you can't easily integrate answers into the documentation, it's useful to compile a specific “Frequently Asked Questions” (aka FAQ) article. A FAQ article must meet the following criteria:
* address the real questions (you might frequently find FAQs that were reflecting not users' needs, but the API owner's desire to repeat some important information once more; it's useless, or worse — annoying; perfect examples of this anti-pattern realization might be found on any bank or air company website);
* both questions and answers must be formulated clearly and succinctly; it's acceptable (and even desirable) to provide links to corresponding reference and tutorial articles, but the answer itself can't be longer than a couple of paragraphs.
* Address the real questions (you might frequently find FAQs that were reflecting not users' needs, but the API owner's desire to repeat some important information once more; it's useless, or worse — annoying; perfect examples of this anti-pattern realization might be found on any bank or air company website)
* Both questions and answers must be formulated clearly and succinctly. It's acceptable (and even desirable) to provide links to corresponding reference and tutorial articles, but the answer itself can't be longer than a couple of paragraphs.
Also, FAQs are a convenient place to explicitly highlight the advantages of the API. In a question-answer form, you might demonstrably show how your API solves complex problems easily and handsomely. (Or at least, *solves them*, unlike the competitors' products.)
@ -77,17 +77,17 @@ 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 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.
* 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 backward 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.
In this case, you need to choose one of the following strategies:
* if the documentation topic content is totally identical for every platform, i.e., only the code syntax differs, you will need to develop generalized documentation: each article provides code samples (and maybe some additional notes) for every supported platform on a single page;
* on the contrary, if the content differs significantly, as is in the iOS/Android case, we might suggest splitting the documentation sites (up to having separate domains for each platform): the good news is that developers almost always need one specific version, and they don't care about other platforms.
* If the documentation topic content is totally identical for every platform, i.e., only the code syntax differs, you will need to develop generalized documentation: each article provides code samples (and maybe some additional notes) for every supported platform on a single page.
* On the contrary, if the content differs significantly, as is in the iOS/Android case, we might suggest splitting the documentation sites (up to having separate domains for each platform): the good news is that developers almost always need one specific version, and they don't care about other platforms.
#### The Documentation Quality

View File

@ -5,9 +5,9 @@ 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 test versions for a full set of APIs and administrative interfaces. It means that developers will be able 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:
* 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.
* 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.
There are two main approaches to tackling these problems.

View File

@ -49,6 +49,7 @@
* Любые ресурсы выделяются в первую очередь на поддержку внутренних потребителей. Это означает, что:
* планы развития API для партнёров совершенно непрозрачны и бывает что попросту абсурдны; очевидные недоработки не исправляются годами;
* техническая поддержка внешних пользователей осуществляется по остаточному принципу.
* Разработчики внутренних сервисов часто ломают обратную совместимость или выпускают новые мажорные версии, совершенно не заботясь о последствиях этих действий для внешних партнёрах.
Всё это приводит к тому, что наличие внешнего API зачастую работает не в плюс компании, а в минус: фактически, вы предоставляете крайне критически и скептически настроенной аудитории очень плохой продукт. Если у вас нет ресурсов на грамотное развитие API как продукта для внешних пользователей — лучше за него не браться совсем.