From 8a5c1e1bff10e31f33742257e7fd3b05448a1d42 Mon Sep 17 00:00:00 2001 From: Sergey Konstantinov Date: Fri, 11 Dec 2020 17:37:42 +0300 Subject: [PATCH] typos and style fixes --- README.md | 2 +- docs/API.en.epub | Bin 46989 -> 47167 bytes docs/API.en.html | 357 +++++++++--------- docs/API.en.pdf | Bin 3594413 -> 3603829 bytes docs/API.ru.epub | Bin 70131 -> 70126 bytes docs/API.ru.html | 6 +- docs/API.ru.pdf | Bin 3871533 -> 3870079 bytes src/en/clean-copy/01-Introduction/01.md | 6 +- src/en/clean-copy/01-Introduction/02.md | 10 +- src/en/clean-copy/01-Introduction/03.md | 12 +- src/en/clean-copy/01-Introduction/04.md | 6 +- src/en/clean-copy/01-Introduction/05.md | 4 +- src/en/clean-copy/01-Introduction/06.md | 24 +- .../02-Section I. The API Design/01.md | 6 +- .../02-Section I. The API Design/02.md | 18 +- .../02-Section I. The API Design/03.md | 181 ++++----- .../02-Section I. The API Design/04.md | 92 +++-- .../02-Section I. The API Design/05.md | 2 +- .../02-Раздел I. Проектирование API/03.md | 2 +- .../02-Раздел I. Проектирование API/04.md | 4 +- 20 files changed, 363 insertions(+), 369 deletions(-) diff --git a/README.md b/README.md index fc9ed02..2c41579 100644 --- a/README.md +++ b/README.md @@ -53,4 +53,4 @@ I will translate sections into English at the moment they're ready. Which means I am accepting inquiries. Fill free to open issues. -I am NOT accepting pull requests, since I'm willing to be the only author and contributor. \ No newline at end of file +I am NOT accepting pull requests introducing any new content, since I'm willing to be the only author and contributor. I would gratefully accept typo fixes, though. \ No newline at end of file diff --git a/docs/API.en.epub b/docs/API.en.epub index 320ec7b31a1e898ace9b7ab8a459c047499a31be..4129e8d48551a56a04ae84137797507445a9e796 100644 GIT binary patch delta 42857 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 delta 42811 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 LDBy7G{z3ddP{}`o diff --git a/docs/API.en.html b/docs/API.en.html index e5a7895..2eecb79 100644 --- a/docs/API.en.html +++ b/docs/API.en.html @@ -128,15 +128,15 @@ h4, h5 {

This work is licensed under a Creative Commons Attribution-NonCommercial 4.0 International License.

Introduction

Chapter 1. On the Structure of This Book

The book you're holding in your hands comprises this Introduction and three large sections.

-

In Section I we'll discuss designing the API as a concept: how to build the architecture properly, from a high-level planning down to final interfaces.

-

Section II is dedicated to API's lifecycle: how interfaces evolve over time, and how to elaborate the product to match users' needs.

+

In Section I we'll discuss designing APIs as a concept: how to build the architecture properly, from a high-level planning down to final interfaces.

+

Section II is dedicated to an API's lifecycle: how interfaces evolve over time, and how to elaborate the product to match users' needs.

Finally, Section III is more about un-engineering sides of the API, like API marketing, organizing support, and working with a community.

-

First two sections are the most interesting to engineers, while third section is being more relevant to both engineers and product managers. But we insist that this section is the most important for the API software developer. Since API is the product for engineers, you cannot simply pronounce non-engineering team responsible for its product planning and support. Nobody but you understands more what product features your API is capable of.

-

Let's start.

Chapter 2. The API Definition

Before we start talking about the API design, we need to explicitly define what the API is. Encyclopedia tells us that API is an acronym for ‘Application Program Interface’. This definition is fine, but useless. Much like ‘Man’ definition by Plato: Man stood upright on two legs without feathers. This definition is fine again, but it gives us no understanding what's so important about a Man. (Actually, not ‘fine’ either. Diogenes of Sinope once brought a plucked chicken, saying ‘That's Plato's Man’. And Plato had to add ‘with broad nails’ to his definition.)

+

First two sections are interesting to engineers mostly, while the third section is more relevant to both engineers and product managers. However, we insist that the third section is the most important for the API software developer. Since an API is a product for engineers, you cannot simply pronounce non-engineering team responsible for product planning and support. Nobody but you understands more about your API's product features.

+

Let's start.

Chapter 2. The API Definition

Before we start talking about the API design, we need to explicitly define what the API is. Encyclopedia tells us that ‘API’ is an acronym for ‘Application Program Interface’. This definition is fine, but useless. Much like ‘Man’ definition by Plato: Man stood upright on two legs without feathers. This definition is fine again, but it gives us no understanding what's so important about a Man. (Actually, not ‘fine’ either. Diogenes of Sinope once brought a plucked chicken, saying ‘That's Plato's Man’. And Plato had to add ‘with broad nails’ to his definition.)

What API means apart from the formal definition?

You're possibly reading this book using a Web browser. To make the browser display this page correctly, a bunch of stuff must work correctly: parsing the URL according to the specification; DNS service; TLS handshake protocol; transmitting the data over HTTP protocol; HTML document parsing; CSS document parsing; correct HTML+CSS rendering.

-

But those are just a tip of an iceberg. To make HTTP protocol work you need the entire network stack (comprising 4-5 or even more different level protocols) work correctly. HTML document parsing is being performed according to hundreds of different specifications. Document rendering calls the underlying operating system API, or even directly graphical processor API. And so on: down to contemporary CISC processor commands implemented on top of microcommands API.

-

In other words, hundreds or even thousands of different APIs must work correctly to make possible basic actions like viewing a webpage. Contemporary internet technologies simply couldn't exist without these tons of API working fine.

+

But those are just a tip of an iceberg. To make HTTP protocol work you need the entire network stack (comprising 4-5 or even more different level protocols) work correctly. HTML document parsing is being performed according to hundreds of different specifications. Document rendering calls the underlying operating system API, or even directly graphical processor API. And so on: down to modern CISC processor commands being implemented on top of the microcommands API.

+

In other words, hundreds or even thousands of different APIs must work correctly to make basic actions possible, like viewing a webpage. Modern internet technologies simply couldn't exist without these tons of API working fine.

An API is an obligation. A formal obligation to connect different programmable contexts.

When I'm asked of an example of a well-designed API, I usually show the picture of a Roman viaduct:

    @@ -144,33 +144,33 @@ h4, h5 {
  • backwards compatibility being broken not a single time in two thousand years.

What differs between a Roman viaduct and a good API is that APIs presume a contract being programmable. To connect two areas some coding is needed. The goal of this book is to help you in designing APIs which serve their purposes as solidly as a Roman viaduct does.

-

A viaduct also illustrates another problem of the API design: your customers are engineers themselves. You are not supplying water to end-users: suppliers are plugging their pipes to you engineering structure, building their own structures upon it. From one side, you may provide water access to much more people through them, not spending your time on plugging each individual house to your network. But from other side, you can't control the quality of suppliers' solutions, and you are to be blamed every time there is a water problem caused by their incompetence.

-

That's why designing the API implies a larger area of responsibilities. API is a multiplier to both your opportunities and mistakes.

Chapter 3. API Quality Criteria

Before we start laying out the recommendations, we ought to specify what API we consider ‘fine’, and what's the profit of having a ‘fine’ API.

+

A viaduct also illustrates another problem of the API design: your customers are engineers themselves. You are not supplying water to end-users: suppliers are plugging their pipes to you engineering structure, building their own structures upon it. From one side, you may provide an access to the water to much more people through them, not spending your time on plugging each individual house to your network. But from other side, you can't control the quality of suppliers' solutions, and you are to be blamed every time there is a water problem caused by their incompetence.

+

That's why designing the API implies a larger area of responsibility. API is a multiplier to both your opportunities and mistakes.

Chapter 3. API Quality Criteria

Before we start laying out the recommendations, we ought to specify what API we consider ‘fine’, and what's the profit of having a ‘fine’ API.

Let's discuss second question first. Obviously, API ‘finesse’ is first of all defined through its capability to solve developers' problems. (One may reasonably say that solving developers' problem might not be the main purpose of offering the API of ours to developers. However, manipulating public opinion is out of this book's author interest. Here we assume that APIs exist primarily to help developers in solving their problems, not for some other covertly declared purposes.)

-

So, how API design might help the developers? Quite simple: well-designed API must solve their problems in the most efficient and comprehensible manner. Distance from formulating the task to writing working code must be as short as possible. Among other things, it means that:

+

So, how the API design might help the developers? Quite simple: a well-designed API must solve their problems in the most efficient and comprehensible manner. The distance from formulating the task to writing a working code must be as short as possible. Among other things, it means that:

  • it must be totally obvious out of your API's structure how to solve a task; ideally, developers at first glance should be able to understand, what entities are meant to solve their problem;
  • -
  • the API must be readable; ideally, developers write correct code after just looking at method nomenclature, never bothering about details (especially API implementation details!); it also also very important to mention, that not only problem solution should be obvious, but also possible errors and exceptions;
  • -
  • the API must be consistent; while developing new functionality (i.e. while using unknown new API entities) developers may write new code similar to the code they already wrote using known API concepts, and this new code will work.
  • +
  • the API must be readable; ideally, developers write correct code after just looking at the method nomenclature, never bothering about details (especially API implementation details!); it also also very important to mention, that not only problem solution (the ‘happy path’) should be obvious, but also possible errors and exceptions (the ‘unhappy path’);
  • +
  • the API must be consistent; while developing new functionality (i.e. while using unknown API entities) developers may write new code similar to the code they already wrote using known API concepts, and this new code will work.
-

However static convenience and clarity of APIs is a simple part. After all, nobody seeks for making an API deliberately irrational and unreadable. When we are developing an API, we always start with clear basic concepts. While possessing some experience in designing APIs it's quite hard to make an API core which fails to meet obviousness, readability, and consistency criteria.

-

Problems begin we we start to expand our API. Adding new functionality sooner or later result in transforming once plain and simple API into a mess of conflicting concepts, and our efforts to maintain backwards compatibility lead to illogical, unobvious and simply bad design solutions. It is partly related to an inability to predict future completely: your understanding of ‘fine’ APIs will change over time, both in objective terms (what problems the API is to solve and what are the best practices) and in subjective ones too (what obviousness, readability and consistency really means regarding your API).

-

Principles we are explaining below are specifically oriented to make APIs evolve smoothly over time, not being turned into a pile of mixed inconsistent interfaces. It is crucial to understand that this approach isn't free: a necessity to bear in mind all possible extension variants and keep essential growth points mean interface redundancy and possibly excessing abstractions being embedded in the API design. Besides both make developers' work harder. Providing excess design complexities being reserved for future use makes sense only when this future actually exists for your API. Otherwise it's simply an overengineering.

Chapter 4. Backwards Compatibility

Backwards compatibility is a temporal characteristics of your API. An obligation to maintain backwards compatibility is the crucial point where API developments differs form software development in general.

-

Of course, backwards compatibility isn't an absolute. In some subject areas shipping new backwards incompatible API versions is a routine. Nevertheless, every time you deploy new backwards incompatible API version, the developers need to make some non-zero effort to adapt their code to the new API version. In this sense, releasing new API versions puts a sort of a ‘tax’ on customers. They must spend quite real money just to make sure they product continue working.

+

However static convenience and clarity of APIs is a simple part. After all, nobody seeks for making an API deliberately irrational and unreadable. When we are developing an API, we always start with clear basic concepts. Providing you've got some experience in APIs, it's quite hard to make an API core which fails to meet obviousness, readability, and consistency criteria.

+

Problems begin when we start to expand our API. Adding new functionality sooner or later result in transforming once plain and simple API into a mess of conflicting concepts, and our efforts to maintain backwards compatibility lead to illogical, unobvious and simply bad design solutions. It is partly related to an inability to predict the future in details: your understanding of ‘fine’ APIs will change over time, both in objective terms (what problems the API is to solve, and what are the best practices) and in subjective ones too (what obviousness, readability and consistency really means regarding your API).

+

Principles we are explaining below are specifically oriented to making APIs evolve smoothly over time, not being turned into a pile of mixed inconsistent interfaces. It is crucial to understand that this approach isn't free: a necessity to bear in mind all possible extension variants and to preserve essential growth points means interface redundancy and possibly excessing abstractions being embedded in the API design. Besides both make developers' work harder. Providing excess design complexities being reserved for future use makes sense only when this future actually exists for your API. Otherwise it's simply an overengineering.

Chapter 4. Backwards Compatibility

Backwards compatibility is a temporal characteristics of your API. An obligation to maintain backwards compatibility is the crucial point where API development differs form software development in general.

+

Of course, backwards compatibility isn't an absolute. In some subject areas shipping new backwards incompatible API versions is a routine. Nevertheless, every time you deploy new backwards incompatible API version, the developers need to make some non-zero effort to adapt their code to the new API version. In this sense, releasing new API versions puts a sort of a ‘tax’ on customers. They must spend quite real money just to make sure their product continue working.

Large companies, which occupy firm market positions, could afford implying such a taxation. Furthermore, they may introduce penalties for those who refuse to adapt their code to new API versions, up to disabling their applications.

From our point of view such practice cannot be justified. Don't imply hidden taxes on your customers. If you're able to avoid breaking backwards compatibility — never break it.

-

Of course, maintaining old API versions is sort of a tax either. Technology changes, and you cannot foresee everything, regardless of how nice your API is initially designed. At some point keeping old API versions results in an inability to provide new functionality and support new platforms, and you will be forced to release new version. But at least you will be able to explain to your customers why they need to make an effort.

+

Of course, maintaining old API versions is a sort of a tax either. Technology changes, and you cannot foresee everything, regardless of how nice your API is initially designed. At some point keeping old API versions results in an inability to provide new functionality and support new platforms, and you will be forced to release new version. But at least you will be able to explain to your customers why they need to make an effort.

We will discuss API lifecycle and version policies in Section II.

Chapter 5. On versioning

Here and throughout we firmly stick to semver principles of versioning:

  1. API versions are denoted with three numbers, i.e. 1.2.3.
  2. -
  3. First number (major version) when backwards incompatible changes in the API are shipped.
  4. +
  5. First number (major version) increases when backwards incompatible changes in the API are shipped.
  6. Second Number (minor version) increases when new functionality is added to the API, keeping backwards compatibility intact.
  7. Third number (patch) increases when new API version contains bug fixes only.
-

Terms ‘major API version’ and ‘new API version, containing backwards incompatible changes to functionality’ are therefore to be considered as equivalent.

+

Sentences ‘major API version’ and ‘new API version, containing backwards incompatible changes’ are therefore to be considered as equivalent ones.

In Section II we will discuss versioning policies in more details. In Section I we will just use semver versions designation, specifically v1, v2, etc.

Chapter 6. Terms and Notation Keys

Software development is being characterized, among other things, by an existence of many different engineering paradigms, whose adepts sometimes are quite aggressive towards other paradigms' adepts. While writing this book we are deliberately avoiding using terms like ‘method’, ‘object’, ‘function’, and so on, using a neutral term ‘entity’ instead. ‘Entity’ means some atomic functionality unit, like class, method, object, monad, prototype (underline what you think right).

-

For entity's components we regretfully failed to find a proper term, so we will use words ‘fields’ and ‘methods’.

-

Most of the examples of APIs in general will be provide in a form of JSON-over-HTTP endpoints. This is some sort of notation which, as we see it, helps to describe concepts in the most comprehensible manner. GET /v1/orders endpoint call could easily be replaced with orders.get() method call, local or remote. JSON could easily be replaced with any other data format. Meaning of assertions shouldn't change.

+

As for an entity's components, we regretfully failed to find a proper term, so we will use words ‘fields’ and ‘methods’.

+

Most of the examples of APIs will be provided in a form of JSON-over-HTTP endpoints. This is some sort of notation which, as we see it, helps to describe concepts in the most comprehensible manner. GET /v1/orders endpoint call could easily be replaced with orders.get() method call, local or remote; JSON could easily be replaced with any other data format. The meaning of assertions shouldn't change.

Let's take a look at the following example:

// Method description
 POST /v1/bucket/{id}/some-resource
@@ -189,56 +189,56 @@ Cache-Control: no-cache
   "error_message"
 }
 
-

It should be read like:

+

It should be read like this:

    -
  • 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} should refer to the nearest term from the left, unless explicitly specified otherwise);
  • +
  • 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 with standard headers (which we omit);
  • -
  • terms in angle brackets (<idempotency token>) describe the semantic of an entity value (field, header, parameter);
  • -
  • a specific JSON, containing a some_parameter field with example value value and some other unspecified fields (indicated by ellipsis) is being sent as a request body payload;
  • -
  • in response (marked with arrow symbol ) server returns a 404 Not Founds status code; status might be omitted (treat it like 200 OK if no status is provided);
  • -
  • response could possibly contain additional notable headers;
  • -
  • response body is a JSON comprising single error_message field; field value absence means that field contains exactly what you expect it should contain — some error message in this case.
  • +
  • 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 arrow symbol ) server returns a 404 Not Founds status code; the status might be omitted (treat it like 200 OK if no status is provided);
  • +
  • the response could possibly contain additional notable headers;
  • +
  • the response body is a JSON comprising single error_message field; field value absence means that field contains exactly what you expect it should contain — some error message in this case.

Term ‘client’ here stands for an application being executed on a user's device, either native of web one. Terms ‘agent’ and ‘user agent’ are synonymous to ‘client’.

Some request and response parts might be omitted if they are irrelevant to a topic being discussed.

-

Simplified notation might be used to avoid redundancies, like POST /some-resource {…,"some_parameter",…}{ "operation_id" }; request and response bodies might also be omitted.

-

We will be using expressions like ‘POST /v1/bucket/{id}/some-resource method’ (or simply ‘bucket/some-resource method’, ‘some-resource’ method if no other some-resources are specified throughout the chapter, so there is no ambiguity) to refer to such endpoint definition.

-

Apart from HTTP API notation we will employ C-style pseudocode, or, to be more precise, JavaScript-like or Python-like since types are omitted. We assume such imperative structures being readable enough to skip detailed grammar explanations.

-

Section I. The API Design

Chapter 7. The API Contexts Pyramid

The approach we use to design API comprises four steps:

+

Simplified notation might be used to avoid redundancies, like POST /some-resource {…, "some_parameter", …}{ "operation_id" }; request and response bodies might also be omitted.

+

We will be using sentenses like ‘POST /v1/bucket/{id}/some-resource method’ (or simply ‘bucket/some-resource method’, ‘some-resource’ method — if there are no other some-resources in the chapter, so there is no ambiguity) to refer to such endpoint definitions.

+

Apart from HTTP API notation, we will employ C-style pseudocode, or, to be more precise, JavaScript-like or Python-like since types are omitted. We assume such imperative structures being readable enough to skip detailed grammar explanations.

+

Section I. The API Design

Chapter 7. The API Contexts Pyramid

The approach we use to design APIs comprises four steps:

  • defining an application field;
  • separating abstraction levels;
  • isolating responsibility areas;
  • describing final interfaces.
-

This for-step algorithm actually builds an API from top to bottom, from common requirements and use case scenarios down to refined entity nomenclature. In fact, moving this way you will eventually get a ready-to-use API — that's why we value this approach.

-

It might seem that the most useful pieces of advice are given in a last chapter, but that's not true. The cost of a mistake made at certain levels differs. Fixing naming is simple; revising wrong understanding what the API stands for is practically impossible.

-

NB. Here and throughout we will illustrate API design concepts using a hypothetical example of an API allowing for ordering a cup of coffee in city cafes. Just in case: this example is totally synthetic. If we were to design such an API in a real world, it would probably have very few in common with our fictional example.

Chapter 8. Defining an Application Field

Key question you should ask yourself looks like that: what problem we solve? It should be asked four times, each time putting emphasis on another word.

+

This four-step algorithm actually builds an API from top to bottom, from common requirements and use case scenarios down to a refined entity nomenclature. In fact, moving this way will eventually conclude with a ready-to-use API — that's why we value this approach highly.

+

It might seem that the most useful pieces of advice are given in the last chapter, but that's not true. The cost of a mistake made at certain levels differs. Fixing the naming is simple; revising the wrong understanding of what the API stands for is practically impossible.

+

NB. Here and throughout we will illustrate API design concepts using a hypothetical example of an API allowing for ordering a cup of coffee in city cafes. Just in case: this example is totally synthetic. If we were to design such an API in a real world, it would probably have very few in common with our fictional example.

Chapter 8. Defining an Application Field

Key question you should ask yourself looks like that: what problem we solve? It should be asked four times, each time putting an emphasis on an another word.

  1. What problem we solve? Could we clearly outline the situation in which our hypothetical API is needed by developers?

  2. What problem we solve? Are we sure that abovementioned situation poses a problem? Does someone really want to pay (literally or figuratively) to automate a solution for this problem?

  3. What problem we solve? Do we actually possess an expertise to solve the problem?

  4. What problem we solve? Is it true that the solution we propose solves the problem indeed? Aren't we creating another problem instead?

-

So, let's imagine that we are going to develop an API for automated coffee ordering in city cafes, and let' apply the key question to it.

+

So, let's imagine that we are going to develop an API for automated coffee ordering in city cafes, and let's apply the key question to it.

  1. Why would someone need an API to make a coffee? Why ordering a coffee via ‘human-to-human’ or ‘human-to-machine’ interface is inconvenient, why have ‘machine-to-machine’ interface?

    • Possibly, we're solving knowledge and selection problems? To provide humans with a full knowledge what options they have right now and right here.
    • -
    • Possibly, we're optimizing waiting times? To save the time people waste while waiting their beverages.
    • +
    • Possibly, we're optimizing waiting times? To save the time people waste waiting their beverages.
    • Possibly, we're reducing the number of errors? To help people get exactly what they wanted to order, stop losing information in imprecise conversational communication or in dealing with unfamiliar coffee machine interfaces?

    ‘Why’ question is the most important of all questions you must ask yourself. And not only about global project goals, but also locally about every single piece of functionality. If you can't briefly and clearly answer the question ‘what for this entity is needed’, then it's not needed.

    Here and throughout we assume, to make our example more complex and bizarre, that we are optimizing all three factors.

  2. -
  3. Do the problems we outlined really exist? Do we really observe unequal coffee-machines utilization in mornings? Do people really suffer from inability to find nearby toffee nut latte they long for? Do they really care about minutes they spend in lines?

  4. +
  5. Do the problems we outlined really exist? Do we really observe unequal coffee-machines utilization in mornings? Do people really suffer from inability to find nearby a toffee nut latte they long for? Do they really care about minutes they spend in lines?

  6. Do we actually have a resource to solve a problem? Do we have an access to sufficient number of coffee machines and users to ensure system's efficiency?

  7. Finally, will we really solve a problem? How we're going to quantify an impact our API makes?

-

In general, there is no simple answers to those questions. Ideally, you should give answers having all relevant metrics measured: how much time is wasted exactly, and what numbers we're going to achieve having this coffee machines density? Let us also stress that in real life obtaining these numbers is only possibly when you're entering a stable market. If you try to create something new, your only option is to rely on your intuition.

+

In general, there are no simple answers to those questions. Ideally, you should give answers having all the relevant metrics measured: how much time is wasted exactly, and what numbers we're going to achieve providing we have such coffee machines density? Let us also stress that in a real life obtaining these numbers is only possible if you're entering a stable market. If you try to create something new, your only option is to rely on your intuition.

Why an API?

-

Since our book is dedicated not to software development per se, but developing APIs, we should look at all those questions from different angle: why solving those problems specifically requires an API, not simply specialized software? In terms of our fictional example we should ask ourselves: why provide a service to developers to allow brewing coffee to end users instead of just making an app for end users?

-

In other words, there must be a solid reason to split two software development domains: there are the operators which provide APIs; and there are the operators which develop services for end users. Their interests are somehow different to such an extent that coupling this two roles in one entity is undesirable. We will talk about the motivation to specifically provide APIs in more details in Section III.

-

We should also note, that you should try making an API when and only when you wrote ‘because that's our area of expertise’ in question 2. Developing APIs is sort of meta-engineering: your writing some software to allow other companies to develop software to solve users' problems. You must possess an expertise in both domains (API and user products) to design your API well.

-

As for our speculative example, let us imagine that in near future some tectonic shift happened on coffee brewing market. Two distinct player groups took shape: some companies provide a ‘hardware’, i.e. coffee machines; other companies have an access to customer auditory. Something like flights market looks like: there are air companies, which actually transport passengers; and there are trip planning services where users are choosing between trip variants the system generates for them. We're aggregating a hardware access to allow app vendors for ordering fresh brewed coffee.

+

Since our book is dedicated not to software development per se, but to developing APIs, we should look at all those questions from a different angle: why solving those problems specifically requires an API, not simply a specialized software application? In terms of our fictional example we should ask ourselves: why provide a service to developers, allowing for brewing coffee to end users, instead of just making an app?

+

In other words, there must be a solid reason to split two software development domains: there are the operators which provide APIs; and there are the operators which develop services for end users. Their interests are somehow different to such an extent, that coupling this two roles in one entity is undesirable. We will talk about the motivation to specifically provide APIs in more details in Section III.

+

We should also note, that you should try making an API when and only when you wrote ‘because that's our area of expertise’ in question 2. Developing APIs is sort of meta-engineering: you're writing some software to allow other companies to develop software to solve users' problems. You must possess an expertise in both domains (APIs and user products) to design your API well.

+

As for our speculative example, let us imagine that in the near future some tectonic shift happened within coffee brewing market. Two distinct player groups took shape: some companies provide a ‘hardware’, i.e. coffee machines; other companies have an access to customer auditory. Something like the flights market looks like: there are air companies, which actually transport passengers; and there are trip planning services where users are choosing between trip variants the system generates for them. We're aggregating a hardware access to allow app vendors for ordering the fresh brewed coffee.

What and How

After finishing all these theoretical exercises, we should proceed right to designing and developing the API, having a decent understanding regarding two things:

    @@ -249,18 +249,18 @@ Cache-Control: no-cache
    • providing an API to services with larger audience, so their users may order a cup of coffee in the most efficient and convenient manner;
    • abstracting an access to coffee machines ‘hardware’ and delivering methods to select a beverage kind and some location to brew — and to make an order.
    • -

    Chapter 9. Separating Abstraction Levels

    ‘Separate abstraction levels in your code’ is possibly the most general advice to software developers. However we don't think it would be a grave exaggeration to say that abstraction levels separation is also the most difficult task to API developers.

    -

    Before proceeding to the theory we should formulate clearly, why abstraction levels are so imprtant and what goals we trying to achieve by separating them.

    -

    Let us remember that software product is a medium connecting two outstanding context, thus transforming terms and operations belonging to one subject area into another area's concepts. The more these areas differ, the more interim connecting links we have to introduce.

    +

Chapter 9. Separating Abstraction Levels

‘Separate abstraction levels in your code’ is possibly the most general advice to software developers. However, we don't think it would be a grave exaggeration to say that abstraction levels separation is also the most difficult task to API developers.

+

Before proceeding to the theory, we should formulate clearly why abstraction levels are so important, and what goals we're trying to achieve by separating them.

+

Let us remember that software product is a medium connecting two outstanding contexts, thus transforming terms and operations belonging to one subject area into another area's concepts. The more these areas differ, the more interim connecting links we have to introduce.

Back to our coffee example. What entity abstraction levels we see?

    -
  1. We're preparing an order via the API: one (or more) cup of coffee and take payments for this.
  2. +
  3. We're preparing an order via the API: one (or more) cup of coffee, and receive payments for this.
  4. Each cup of coffee is being prepared according to some recipe, which implies the presence of different ingredients and sequences of preparation steps.
  5. -
  6. Each beverage is being prepared on some physical coffee machine occupying some position in space.
  7. +
  8. Each beverage is being prepared on some physical coffee machine, occupying some position in space.
-

Every level presents a developer-facing ‘facet’ in our API. While elaboration abstractions hierarchy we first of all trying to reduce the interconnectivity of different entities. That would help us to reach several goals.

+

Every level presents a developer-facing ‘facet’ in our API. While elaborating abstractions hierarchy, we first of all trying to reduce the interconnectivity of different entities. That would help us to reach several goals.

    -
  1. Simplifying developers' work and learning curve. At each moment of time a developer is operating only those entities which are necessary for the task they're solving right now. And conversely, badly designed isolation leads to the situation when developers have to keep in mind lots of concepts mostly unrelated to the task being solved.

  2. +
  3. Simplifying developers' work and the learning curve. At each moment of time a developer is operating only those entities which are necessary for the task they're solving right now. And conversely, badly designed isolation leads to the situation when developers have to keep in mind lots of concepts mostly unrelated to the task being solved.

  4. Preserving backwards compatibility. Properly separated abstraction levels allow for adding new functionality while keeping interfaces intact.

  5. Maintaining interoperability. Properly isolated low-level abstraction help us to adapt the API to different platforms and technologies without changing high-level entities.

@@ -269,7 +269,7 @@ Cache-Control: no-cache GET /v1/recipes/lungo
// Posts an order to make a lungo
-// using coffee-machine specified
+// using specified coffee-machine,
 // and returns an order identifier
 POST /v1/orders
 {
@@ -283,14 +283,14 @@ GET /v1/orders/{id}
 

Let's consider the question: how exactly developers should 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 currently prepared volume of beverage to order state.
  • +
  • add currently prepared volume of beverage to the order state.
-

Then a developer just need to compare to numbers to find out whether the order is ready.

-

This solutions intuitively looks bad, and it really is: it violates all abovementioned principles.

-

In first, to solve the task ‘order a lung’ a developer need to refer to the ‘recipe’ entity and learn that every recipe has an associated volume. Then they need to embrace the concept that order is ready at that particular moment when beverage volume becomes equal to reference one. This concept is simply unguessable and bears to particular sense in knowing it.

-

In second, we will automatically got problems if we need to vary beverage size. For example, if one day we decide to offer a choice to a customer how many milliliters of lungo they desire exactly, then we will have to performs one of the following tricks.

-

Variant I: we have a list of possible volumes fixed and introduce bogus recipes like /recipes/small-lungo or recipes/large-lungo. Why ‘bogus’? Because it's still the same lungo recipe, same ingredients, same preparation steps, only volumes differ. We will have to start mass producing a bunch of recipes only different in volume, or introduce some recipe ‘inheritance’ to be able to specify ‘base’ recipe and just redefine the volume.

-

Variant II: we modify an interface, pronouncing volumes stated in recipes being just default values. We allow to set different volume when placing an order:

+

Then a developer just needs to compare two numbers to find out whether the order is ready.

+

This solution intuitively looks bad, and it really is: it violates all abovementioned principles.

+

In first, to solve the task ‘order a lungo’ a developer needs to refer to the ‘recipe’ entity and learn that every recipe has an associated volume. Then they need to embrace the concept that an order is ready at that particular moment when the prepared beverage volume becomes equal to the reference one. This concept is simply unguessable, and knowing it is mostly useless.

+

In second, we will have automatically got problems if we need to vary the beverage size. For example, if one day we decide to offer a choice to a customer, how many milliliters of lungo they desire exactly, then we have to perform one of the following tricks.

+

Variant I: we have a list of possible volumes fixed and introduce bogus recipes like /recipes/small-lungo or recipes/large-lungo. Why ‘bogus’? Because it's still the same lungo recipe, same ingredients, same preparation steps, only volumes differ. We will have to start the mass production of recipes, only different in volume, or introduce some recipe ‘inheritance’ to be able to specify the ‘base’ recipe and just redefine the volume.

+

Variant II: we modify an interface, pronouncing volumes stated in recipes being just the default values. We allow to set different volume when placing an order:

POST /v1/orders
 {
   "coffee_machine_id",
@@ -298,12 +298,12 @@ GET /v1/orders/{id}
   "volume":"800ml"
 }
 
-

For those orders with arbitrary volume requested a developer will need to obtain requested volume not from GET /v1/recipes, but GET /v1/orders. Doing so we're getting a whole bunch of related problems:

+

For those orders with an arbitrary volume requested, a developer will need to obtain the requested volume not from GET /v1/recipes, but GET /v1/orders. 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 a code working with the POST /v1/orders handler, but forget to make corresponding changes in an order readiness check code;
  • -
  • the same field (coffee volume) now means different things in different interfaces. In GET /v1/recipes context volume field means ‘a volume to be prepared if no arbitrary volume is specified in POST /v1/orders request’; and it cannot simply be renamed to ‘default volume’, we now have to live with that.
  • +
  • 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 GET /v1/recipes context volume field means ‘a volume to be prepared if no arbitrary volume is specified in POST /v1/orders request’; and it cannot be renamed to ‘default volume’ easily, we now have to live with that.
-

In third, the entire scheme becomes totally inoperable if different types of coffee machines produce different volumes of lungo. To introduce ‘lungo volume depends on machine type’ constraint we have to do quite a nasty thing: make recipes depend on coffee machine id. By doing so we start actively ‘stir’ abstraction levels: one part of our API (recipe endpoints) becomes unusable without explicit knowledge of another part (coffee machines parameters). And which is even worse, developers will have to change logics of their apps: previously it was possible to choose volume first, then a coffee-machine; but now this step must be rebuilt from scratch.

+

In third, the entire scheme becomes totally inoperable if different types of coffee machines produce different volumes of lungo. To introduce ‘lungo volume depends on machine type’ constraint we have to do quite a nasty thing: make recipes depend on coffee machine id. By doing so we start actively ‘stir’ abstraction levels: one part of our API (recipe endpoints) becomes unusable without explicit knowledge of another part (coffee machines listing). And what is even worse, developers will have to change the logic of their apps: previously it was possible to choose volume first, then a coffee-machine; but now this step must be rebuilt from scratch.

Okay, we understood how to make things bad. But how to make them nice?

Abstraction levels separation should go alongside three directions:

    @@ -311,13 +311,13 @@ GET /v1/orders/{id}
  1. From user subject field terms to ‘raw’ data subject field terms — in our case from high-level terms like ‘order’, ‘recipe’, ‘café’ to low-level terms like ‘beverage temperature’, ‘coffee machine geographical coordinates’, etc.

  2. Finally, from data structures suitable for end users to ‘raw’ data structures — in our case, from ‘lungo recipe’ and ‘"Chamomile" café chain’ to raw byte data stream from ‘Good Morning’ coffee machine sensors.

-

The more is the distance between programmable context which our API connects, the deeper is the hierarchy of the entities we are to develop.

+

The more is the distance between programmable contexts our API connects, the deeper is 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:

  • from one side, an ‘order’ should not store the data regarding coffee machine sensors;
  • from other side, 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 to another. For example, introduce a task entity like that:

+

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 to another. For example, introduce a task entity like that:

{
   …
   "volume_requested": "800ml",
@@ -328,18 +328,18 @@ GET /v1/orders/{id}
     "status": "executing",
     "operations": [
       // description of commands
-      // being executed on physical coffee machine
+      // being executed on a physical coffee machine
     ]
   }
   …
 }
 
-

We call this approach ‘naïve’ not because its wrong; on the contrary, that's quite logical ‘default’ solution if you don't know yet (or don't understand yet) how your API will look like. The problem with this approach lies in its speculativeness: it doesn't reflect subject area's organization.

-

An experienced developer in this case must ask: what options do exist? How we really should determine beverage readiness? If it turns out that comparing volumes is the only working method to tell whether the beverage is ready, then all the speculations above are wrong. You may safely include readiness by volume detection into your interfaces, since no other method exists. Before abstraction 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:

+

We call this approach ‘naïve’ not because it's wrong; on the contrary, that's quite a logical ‘default’ solution if you don't know yet (or don't understand yet) how your API will look like. The problem with this approach lies in its speculativeness: it doesn't reflect the subject area's organization.

+

An experienced developer in this case must ask: what options do exist? How we really should determine beverage readiness? If it turns out that comparing volumes is the only working method to tell whether the beverage is ready, then all the speculations above are wrong. You may safely include readiness-by-volume detection into your interfaces, since no other methods exist. Before abstracting something we need to learn what exactly we're abstracting.

+

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, and the only customizable options are some beverage parameters, like desired volume, syrup flavor and kind of milk;
  • -
  • coffee machines with builtin functions like ‘grind specified coffee volume’, ‘shed specified amount of water’, etc; such coffee machines lack ‘preparation programs’, but provide an access to commands and sensors.
  • +
  • coffee machines capable of executing programs coded in the firmware; the only customizable options are some beverage parameters, like desired volume, a syrup flavor and a kind of milk;
  • +
  • coffee machines with builtin functions, like ‘grind specified coffee volume’, ‘shed specified amount of water’, etc.; such coffee machines lack ‘preparation programs’, but provide an access to commands and sensors.

To be more specific, let's assume those two kinds of coffee machines provide the following physical API.

    @@ -349,7 +349,7 @@ GET /programs → { // program identifier - "program": "01", + "program": 1, // coffee type "type": "lungo" } @@ -374,11 +374,11 @@ POST /execute
    // Cancels current program
     POST /cancel
     
    -
    // Returns execution status
    -// Format is the same as in POST /execute
    +
    // Returns execution status.
    +// The format is the same as in `POST /execute`
     GET /execution/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, (b) in real life you really get something like that from vendors, and this API is quite sane, actually.

    +

    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, (b) in a real life you really got something like that from vendors, and this API is quite sane, actually.

  • Coffee machines with builtin functions:

    // Returns a list of functions available
     GET /functions
    @@ -426,14 +426,14 @@ GET /sensors
       ]
     }
     
    -

    NB. The example is intentionally factitious to model a situation described above: to determine beverage readiness you have to compare requested volume with volume sensor state.

  • +

    NB. The example is intentionally factitious to model a situation described above: to determine beverage readiness you have to compare the requested volume with volume sensor readings.

-

Now the picture becomes more apparent: wee need to abstract coffee machine API calls, so that ‘execution level’ in our API provides general functions (like beverage readiness detection) in a unified form. We should also note that these two coffee machine kinds belong to different abstraction levels themselves: first one provide a higher level API than second one. Therefore, a ‘branch’ of our API working with second kind machines will be more intricate.

-

The next step in abstraction level separating is determining what functionality we're abstracting. To do so we need to understand the tasks developers solve at the ‘order’ level, and to learn what problems they got if our interim level missed.

+

Now the picture becomes more apparent: we need to abstract coffee machine API calls, so that ‘execution level’ in our API provides general functions (like beverage readiness detection) in a unified form. We should also note that these two coffee machine API kinds belong to different abstraction levels themselves: the first one provides a higher level API than the second one. Therefore, a ‘branch’ of our API working with second kind machines will be more intricate.

+

The next step in abstraction level separating is determining what functionality we're abstracting. To do so we need to understand the tasks developers solve at the ‘order’ level, and to learn what problems they get if our interim level is missing.

  1. Obviously the developers desire to create an order uniformly: list high-level order properties (beverage kind, volume and special options like syrup or milk type), and don't think about how specific coffee machine executes it.
  2. -
  3. Developers must be able to learn the execution state: is order ready? if not — when to expect it's ready (and is there any sense to wait in case of execution errors).
  4. -
  5. Developers need to address an order's location in space and time — to explain to users where and when they should pick the order up.
  6. +
  7. Developers must be able to learn the execution state: is the order ready? if not — when to expect it's ready (and is there any sense to wait in case of execution errors).
  8. +
  9. Developers need to address the order's location in space and time — to explain to users where and when they should pick the order up.
  10. 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. 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:

@@ -444,13 +444,13 @@ GET /sensors

But with the second kind API it's much worse. The main problem we foresee is an absence of ‘memory’ for actions being executed. Functions and sensors API is totally stateless, which means we don't event understand who called a function being currently executed, when, and which order it is related to.

So we need to introduce two abstraction levels.

    -
  1. Execution control level which provides uniform interface to indivisible programs. ‘Uniform interface’ means here that, regardless of a coffee machine kind, developers may expect:

    +
  2. Execution control level, which provides 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 error) being the same;
    • methods nomenclature (for example, order cancellation method) and their behavior being the same.
  3. 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.

-

What does this mean in practical sense? Developers will still be creating orders dealing with high-level entities only:

+

What does this mean in practical sense? Developers will still be creating orders, dealing with high-level entities only:

POST /v1/orders
 {
   "coffee_machin
@@ -460,13 +460,13 @@ GET /sensors
 →
 { "order_id" }
 
-

The POST /orders handler will check all order parameters, puts a hold of corresponding sum on user's credit card, forms a request to run and calls the execution level. First, correct execution program needs to be fetched:

+

The POST /orders handler checks all order parameters, puts a hold of corresponding sum on user's credit card, forms a request to run, and calls the execution level. First, correct execution program needs to be fetched:

POST /v1/programs/match
 { "recipe", "coffee-machine" }
 →
 { "program_id" }
 
-

Now, after obtaining a correct program identifier the handler runs a program:

+

Now, after obtaining a correct program identifier, the handler runs a program:

POST /v1/programs/{id}/run
 {
   "order_id",
@@ -481,17 +481,17 @@ GET /sensors
 →
 { "program_run_id" }
 
-

Please note that knowing the coffee machine API kind isn't required at all; that's why we're making abstractions! We could make interfaces more specific, implementing different run and match endpoints for different coffee machines:

+

Please note that knowing the coffee machine API kind isn't required at all; that's why we're making abstractions! We could possibly make interfaces more specific, implementing different run and match endpoints for different coffee machines:

  • POST /v1/programs/{api_type}/match
  • POST /v1/programs/{api_type}/{program_id}/run

This approach has some benefits, like a possibility to provide different sets of parameters, specific to the API kind. But we see no need in such fragmentation. run method handler is capable of extracting all the program metadata and perform one of two actions:

    -
  • call POST /execute physical API method passing internal program identifier — for the first API kind;
  • +
  • call POST /execute physical API method, passing internal program identifier — for the first API kind;
  • initiate runtime creation to proceed with the second API kind.
-

Out of general concerns 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 which creates a ‘runtime’ (e.g. stateful execution context) to run a program and controls its state.

+

Out of general concerns 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 which creates a ‘runtime’ (e.g. a stateful execution context) to run a program and control its state.

POST /v1/runtimes
 { "coffee_machine", "program", "parameters" }
 →
@@ -519,14 +519,15 @@ GET /sensors
   // * "ready_waiting" — beverage is ready
   // * "finished" — all operations done
   "status": "ready_waiting",
-  // Command being currently executed
+  // Command being currently executed.
+  // Similar to line numbers in computer programs
   "command_sequence_id",
-  // How the exectuion concluded:
+  // How the execution concluded:
   // * "success" — beverage prepared and taken
   // * "terminated" — execution aborted
   // * "technical_error" — preparation error
-  // * "waiting_time_exceeded" — beverage prepared
-  //   but not taken; timed out then disposed
+  // * "waiting_time_exceeded" — beverage prepared,
+  //     but not taken; timed out then disposed
   "resolution": "success",
   // All variables values,
   // including sensors state
@@ -535,82 +536,82 @@ GET /sensors
 

NB: while implementing ordersmatchrunruntimes call sequence we have two options:

    -
  • either POST /orders handler requests the data regarding recipe, coffee machine model, and program on its own behalf and forms a stateless request which contains all the necessary data (the API kind, command sequence, etc.);
  • -
  • or the request contains only data identifiers, and next in chain handlers will request pieces of data they need via some internal APIs.
  • +
  • either POST /orders handler requests the data regarding the recipe, the coffee machine model, and the program on its own behalf, and forms a stateless request which contains all the necessary data (the API kind, command sequence, etc.);
  • +
  • or the request contains only data identifiers, and next in chain handler will request pieces of data it needs via some internal APIs.

Both variants are plausible, selecting one of them depends on implementation details.

Abstraction Levels Isolation

Crucial quality of properly separated abstraction levels (and therefore a requirement to their design) is a level isolation restriction: only adjacent levels may interact. If ‘jumping over’ is needed in the API design, then clearly mistakes were made.

-

Get back to our example. How retrieving order status operation would work? To obtain a status the following call chain is to be performed:

+

Get back to our example. How retrieving order status would work? To obtain a status the following call chain is to be performed:

    -
  • user initiate a call to GET /v1/orders method;
  • -
  • order handler completes operations on its level of responsibility (for example, checks user authorization), finds program_run_id identifier and performs a call to runs/{program_run_id} endpoint;
  • -
  • runs endpoint in its turn completes operations corresponding to its level (for example, checks the coffee machine API kind) and, depending on the API kind, proceeds with one of two possible execution branches:
      -
    • either calls GET /execution/status method of a physical coffee machine API, gets coffee volume and compares it to the reference value;
    • -
    • or invokes GET /v1/runtimes/{runtime_id} to obtain state.status and convert it to order status;
  • -
  • in case of the second API kind the call chain continues: GET /runtimes handler invokes GET /sensors method of a physical coffee machine API and performs some manipulations on them, like comparing cup / ground coffee / shed water volume with those requested upon command execution and changing state and status if needed.
  • +
  • user initiates a call to the GET /v1/orders method;
  • +
  • the orders handler completes operations on its level of responsibility (for example, checks user authorization), finds program_run_id identifier and performs a call to the runs/{program_run_id} endpoint;
  • +
  • the runs endpoint in its turn completes operations corresponding to its level (for example, 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 a 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 case of the second API kind, the call chain continues: the GET /runtimes handler invokes the GET /sensors method of a 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: ‘Call chain’ wording shouldn't be treated literally. Each abstraction level might be organized differently in a technical sense:

+

NB: The ‘call chain’ wording shouldn't be treated literally. Each abstraction level might be organized differently in a technical sense:

  • there might be explicit proxying of calls down the hierarchy;
  • -
  • there might be a cache at each level being updated upon receiving a callback call or an event. In particular, low-level runtime execution cycle obviously must be independent from upper levels and renew its state in background, not waiting for an explicit call.
  • +
  • there might be a cache at each level, being updated upon receiving a callback call or an event. In particular, a low-level runtime execution cycle obviously must be independent from upper levels and renew its state in background, not waiting for an explicit call.
-

Note that what happens here: each abstraction level wields its own status (e.g. order, runtime, sensors status), being formulated in corresponding to this level subject area terms. Forbidding the ‘jumping over’ results in necessity to spawn statuses at each level independently.

-

Let's now look how the order cancel operation springs through our abstraction level. In this case the call chain will look like that:

+

Note what happens here: each abstraction level wields its own status (e.g. order, runtime, sensors status), being formulated in corresponding to this level subject area terms. Forbidding the ‘jumping over’ results in necessity to spawn statuses at each level independently.

+

Let's now look how the order cancel operation flows through our abstraction levels. In this case the call chain will look like that:

    -
  • user initiates a call to POST /v1/orders/{id}/cancel method;
  • +
  • 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;
    • -
    • solves money issues, whether a refund is needed;
    • -
    • finds program_run_id identifier and calls runs/{program_run_id}/cancel method;
  • +
  • solves money issues, i.e. whether a refund is needed;
  • +
  • finds the program_run_id identifier and calls the runs/{program_run_id}/cancel method;
  • the rides/cancel handler completes operations on its level of responsibility and, depending on the coffee machine API kind, proceeds with one of two possible execution branches:
      -
    • either calls POST /execution/cancel method of a physical coffee machine API;
    • -
    • or invokes POST /v1/runtimes/{id}/terminate;
  • -
  • in a second case the call chain continues, terminate handler operates its internal state:
      -
    • changes resolution to "terminated";
    • -
    • runs "discard_cup" command.
  • +
  • either calls the POST /execution/cancel method of a physical coffee machine API;
  • +
  • or invokes the POST /v1/runtimes/{id}/terminate method;
  • +
  • in a second case the call chain continues, the terminate handler operates its internal state:
      +
    • changes the resolution to "terminated";
    • +
    • runs the "discard_cup" command.
  • Handling state-modifying operations like cancel requires more advanced abstraction levels juggling skills compared to non-modifying calls like GET /status. There are two important moments:

      -
    1. At every abstraction level the idea of ‘order canceling’ is reformulated:

      +
    2. At each abstraction level the idea of ‘order canceling’ is reformulated:

        -
      • at orders level this action in fact splits into several ‘cancels’ of other levels: you need to cancel money holding and to cancel an order execution;
      • -
      • while at a second API kind physical level a ‘cancel’ operation itself doesn't exist: ‘cancel’ means executing a discard_cup command, which is quite the same as any other command. -The interim API level is need to make this transition between different level ‘cancels’ smooth and rational without jumping over principes.
    3. -
    4. From a high-level point of view, cancelling an order is a terminal action, since no further operations are possible. From a low-level point of view processing a request continues until the cup is discard, and then the machine is to be unlocked (e.g. new runtimes creation allowed). It's a task to execution control level to couple those two states, outer (the order is canceled) and inner (the execution continues).

    5. +
    6. at the orders level this action in fact splits into several ‘cancels’ of other levels: you need to cancel money holding and to cancel an order execution;
    7. +
    8. at the second API kind physical level a ‘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 need to make this transition between different level ‘cancels’ smooth and rational without jumping over canyons.
    9. +
    10. From a high-level point of view, canceling an order is a terminal action, since no further operations are possible. From a low-level point of view, the processing continues until the cup is discarded, and then the machine is to be unlocked (e.g. new runtimes creation allowed). It's a task to the execution control level to couple those two states, outer (the order is canceled) and inner (the execution continues).

    -

    It might look like forcing the abstraction levels isolation is redundant and makes interfaces more complicated. In fact, it is: it's very important to understand that flexibility, consistency, readability and extensibility come with a price. One may construct an API with zero overhead, essentially just provide an access to coffee machine's microcontrollers. However using such an API would be a disaster, not mentioning and inability to expand it.

    +

    It might look that forcing the abstraction levels isolation is redundant and makes interfaces more complicated. In fact, it is: it's very important to understand that flexibility, consistency, readability and extensibility come with a price. One may construct an API with zero overhead, essentially just provide an access to coffee machine's microcontrollers. However using such an API would be a disaster to a developer, not mentioning an inability to expand it.

    Separating abstraction levels is first of all a logical procedure: how we explain to ourselves and to developers what our API consists of. The abstraction gap between entities exists objectively, no matter what interfaces we design. Our task is just separate this gap into levels explicitly. The more implicitly abstraction levels are separated (or worse — blended into each other), the more complicated is your API's learning curve, and the worse is the code which use it.

    -

    Data Flow

    -

    One useful exercise allowing to examine the entire abstraction hierarchy is excluding all the particulars and constructing (on a paper or just in your head) a data flow chart: what data is flowing through you API entities and how it's being altered at each step.

    -

    This exercise doesn't just helps, but also allows to design really large APIs with huge entities nomenclatures. Human memory isn't boundless; any project which grows extensively will eventually become too big to keep the entire entities hierarchy in mind. But it's usually possible to keep in mind the data flow chart; or at least keep a much larger portion of the hierarchy.

    -

    What data flows we have in our coffee API?

    +

    The Data Flow

    +

    One useful exercise allowing to examine the entire abstraction hierarchy is excluding all the particulars and constructing (on a paper or just in your head) a data flow chart: what data is flowing through you API entities, and how it's being altered at each step.

    +

    This exercise doesn't just helps, but also allows to design really large APIs with huge entity nomenclatures. Human memory isn't boundless; any project which grows extensively will eventually become too big to keep the entire entities hierarchy in mind. But it's usually possible to keep in mind the data flow chart; or at least keep a much larger portion of the hierarchy.

    +

    What data flow we have in our coffee API?

      -
    1. Sensor data, i.e. volumes of coffee / water / cups. This is the lowest data level we have, and here we can't change anything.

    2. -
    3. A continuous sensors data stream is being transformed into a discrete command execution statuses, injecting new concepts which don't exist within the subject area. A coffee machine API doesn't provide ‘coffee is being shed’ or ‘cup is being set’ notions. It's our software which treats incoming sensor data and introduces new terms: if the volume of coffee or water is less than target one, then the process isn't over yet. If the target value is reached, then this synthetic status is to be switched and next command to be executed.
      -It is important to note that we don't calculate new variables out from sensor data: we need to create new data set first, a context, an ‘execution program’ comprising a sequence of steps and conditions, and to fill it with initial values. If this context is missing, it's impossible to understand what's happening with the machine.

    4. -
    5. Having a logical data on program execution state we can (again via creating new, high-level data context) merge two different data streams from two different kinds of APIs into a single stream in a unified form of executing a beverage preparation program with logical variables like recipe, volume, and readiness status.

    6. +
    7. It starts with the sensors data, i.e. volumes of coffee / water / cups. This is the lowest data level we have, and here we can't change anything.

    8. +
    9. A continuous sensors data stream is being transformed into a discrete command execution statuses, injecting new concepts which don't exist within the subject area. A coffee machine API doesn't provide a ‘coffee is being shed’ or a ‘cup is being set’ notion. It's our software which treats incoming sensors data and introduces new terms: if the volume of coffee or water is less than the target one, then the process isn't over yet. If the target value is reached, then this synthetic status is to be switched, and the next command to be executed.
      +It is important to note that we don't calculate new variables out from sensors data: we need to create a new dataset first, a context, an ‘execution program’ comprising a sequence of steps and conditions, and to fill it with initial values. If this context is missing, it's impossible to understand what's happening with the machine.

    10. +
    11. Having a logical data about the program execution state, we can (again via creating a new high-level data context) merge two different data streams from two different kinds of APIs into a single stream, which provides in a unified form the data regarding executing a beverage preparation program with logical variables like recipe, volume, and readiness status.

    -

    Each API abstraction level therefore corresponds to data flow generalization and enrichment, converting low-level (and in fact useless to end users) context terms into upper higher level context terms.

    +

    Each API abstraction level therefore corresponds to some data flow generalization and enrichment, converting the low-level (and in fact useless to end users) context terms into the higher level context terms.

    We may also traverse the tree backwards.

      -
    1. At an order level we set its logical parameters: recipe, volume, execution place and possible statuses set.

    2. -
    3. At an execution level we read order level data and create lower level execution contest: a program as a sequence of steps, their parameters, transition rules, and initial state.

    4. -
    5. At a runtime level we read target parameters (which operation to execute, what the target volume is) and translate them into coffee machine API microcommands and a status for each command.

    6. +
    7. At the order level we set its logical parameters: recipe, volume, execution place and possible statuses set.

    8. +
    9. At the execution level we read the order level data and create a lower level execution context: the program as a sequence of steps, their parameters, transition rules, and initial state.

    10. +
    11. At the runtime level we read the target parameters (which operation to execute, what the target volume is) and translate them into coffee machine API microcommands and statuses for each command.

    -

    Also, if we take a look into the ‘bad’ decision, being discussed in the beginning of this chapter (forcing developers to determine actual order status on their own), we could notice a data flow collision there:

    +

    Also, if we take a deeper look into the ‘bad’ decision, being discussed in the beginning of this chapter (forcing developers to determine actual order status on their own), we could notice a data flow collision there:

      -
    • from one side, in an order context ‘leaked’ physical data (beverage volume prepared) is injected, therefore stirring abstraction levels irreversibly;
    • -
    • from other side, an order context itself is deficient: it doesn't provide new meta-variables non-existent on low levels (order status, in particular), doesn't initialize them and don't provide game rules.
    • +
    • from one side, in the order context ‘leaked’ physical data (beverage volume prepared) is injected, therefore stirring abstraction levels irreversibly;
    • +
    • from other side, 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 don't provide the game rules.
    -

    We will discuss data context in more details in Section II. There we will just state that data flows and their transformations might be and must be examined as an API facet which, from one side, helps us to separate abstraction levels properly, and, from other side, to check if our theoretical structures work as intended.

    Chapter 10. Isolating Responsibility Areas

    Based on previous chapter, we understand that an abstraction hierarchy in out hypothetical project would look like that:

    +

    We will discuss data contexts in more details in the 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, from one side, helps us to separate abstraction levels properly, and, from other side, to check if our theoretical structures work as intended.

    Chapter 10. Isolating Responsibility Areas

    Basing on the previous chapter, we understand that the abstraction hierarchy in our hypothetical project would look like that:

      -
    • user level (those entities users directly interact with and which are formulated in terms, understandable by user: orders, coffee recipes);
    • -
    • program execution control level (entities responsible for transforming orders into machine commands);
    • -
    • runtime level for the second API kind (entities describing command execution state machine).
    • +
    • the user level (those entities users directly interact with and which are formulated in terms, understandable by user: 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 reason in keeping this entity within our API boundaries; which operations are applicable to the entity itself (and which are delegated to other objects). In fact we are to apply the ‘why’-principle to every single API entity.

    -

    To do so we must iterate over the API and formulate in subject area terms what every object is. Let us remind that abstraction levels concept implies that each level is some interim subject area per se; a step we take to traverse from describing a task in first connected context terms (‘a lungo ordered by a user’) to second connect context terms (‘a command performed by a coffee machine’)

    +

    We are now to define each entity's responsibility area: what's the reasoning in keeping this entity within our API boundaries; what operations are applicable to the entity directly (and which are delegated to other objects). In fact, we are to apply the ‘why’-principle to every single API entity.

    +

    To do so we must iterate all over the API and formulate in subject area terms what every object is. Let us remind that the abstraction levels concept implies that each level is a some interim subject area per se; a step we take in the journey from describing a task in the first connected context terms (‘a lungo ordered by a user’) to the second connect context terms (‘a command performed by a coffee machine’).

    As for our fictional example, it would look like that:

    1. User level entities.
        @@ -619,11 +620,11 @@ It is important to note that we don't calculate new variables out from sensor da
      • checked for its status;
      • retrieved;
      • canceled;
    2. -
    3. A recipe describes an ‘ideal model’ of some coffee beverage type, its customer properties. A recipe is immutable entities for us, which means we could only read it.
    4. -
    5. A coffee-machine is a model of a real world device. From coffee machine description we must be able to retrieve its geographical location and the options it support (will be discussed below).
    6. +
    7. A recipe describes an ‘ideal model’ of some coffee beverage type, its customer properties. A recipe is an immutable entity for us, which means we could only read it.
    8. +
    9. 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 support from this model (will be discussed below).
    10. Program execution control level entities.
        -
      • A ‘program’ describes some general execution plan for a coffee machine. Program could only be read.
      • -
      • A program matcher programs/matcher is capable of coupling a recipe and a program, which in fact means to retrieve a dataset needed to prepare a specific recipe on a specific coffee machine.
      • +
      • A program describes some general execution plan for a coffee machine. Programs could only be read.
      • +
      • The program matcher programs/matcher is capable of coupling a recipe and a program, which in fact means ‘to retrieve a dataset needed to prepare a specific recipe on a specific coffee machine’.
      • A program execution programs/run describes a single fact of running a program on a coffee machine. run might be:
        • initialized (created);
        • checked for its status;
        • @@ -634,22 +635,22 @@ It is important to note that we don't calculate new variables out from sensor da
        • checked for its status;
        • terminated.
    -

    If we look closely at each object, we may notice that each entity turns out to be a composite. For example a program will operate high-level data (recipe and coffee-machine), enhancing them with its level terms (program_run_id for instance). This is totally fine: connecting context is what APIs do.

    +

    If we look closely at the entities, we may notice that each entity turns out to be a composite. For example a program will operate high-level data (recipe and coffee-machine), enhancing them with its subject area terms (program_run_id for instance). This is totally fine: connecting contexts is what APIs do.

    Use Case Scenarios

    At this point, when our API is in general clearly outlined and drafted, we must put ourselves into developer's shoes and try writing code. Our task is to look at the entities nomenclature and make some estimates regarding their future usage.

    So, let us imagine we've got a task to write an app for ordering a coffee, based upon our API. What code would we write?

    Obviously the first step is offering a choice to a 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. A developer has to do something like that:

      -
    • retrieve all possible recipes from GET /v1/recipes;
    • -
    • retrieve a list of all available coffee machines from GET /v1/coffee-machines;
    • -
    • write a code to traverse 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 a code which traverse all this data.

    If we try writing a pseudocode, we will get something like that:

    // Retrieve all possible recipes
     let recipes = api.getRecipes();
     // Retrieve a list of all available coffee machines
     let coffeeMachines = api.getCoffeeMachines();
    -// Build spatial index
    +// Build a spatial index
     let coffeeMachineRecipesIndex = buildGeoIndex(recipes, coffee-machines);
     // Select coffee machines matching user's needs
     let matchingCoffeeMachines = coffeeMachineRecipesIndex.query(
    @@ -659,10 +660,10 @@ let matchingCoffeeMachines = coffeeMachineRecipesIndex.query(
     // Finally, show offers to user
     app.display(coffeeMachines);
     
    -

    As you see, developers are to write a lot of redundant code (to say nothing about difficulties 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 resources on retrieving lists and indexing them.

    -

    A necessity of adding a new endpoint for searching becomes obvious. To design such an interface we must imagine ourselves being a UX designer and think about how an app could try to arouse users' interest. Two scenarios are evident:

    +

    As you see, developers are to write a lot of redundant code (to say nothing about the difficulties 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 resources on retrieving lists and indexing them.

    +

    A necessity of adding a new endpoint for searching becomes obvious. To design such an interface we must imagine ourselves being a UX designer, and think about how an app could try to arouse users' interest. Two scenarios are evident:

      -
    • display cafes in the vicinity and types of coffee they offer (‘service discovery scenario’) — for new users or just users with no specific tastes;
    • +
    • display all cafes in the vicinity and types of coffee they offer (a ‘service discovery’ scenario) — for new users or just users with no specific tastes;
    • 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:

    @@ -686,11 +687,11 @@ app.display(coffeeMachines);

    Here:

      -
    • an offer — is a marketing bid: on what conditions a user could order requested coffee beverage (if specified in request), or a some kind of marketing offering — prices for the most popular or interesting products (if no specific preference was set);
    • +
    • an offer — is a marketing bid: on what conditions a user could have the requested coffee beverage (if specified in request), or a some kind of marketing offering — prices for the most popular or interesting products (if no specific preference was set);
    • a place — is a spot (café, restaurant, street vending machine) where the coffee machine is located; we never introduced this entity before, but it's quite obvious that users need more convenient guidance to find a proper coffee machine than just geographical coordinates.

    NB. We could have been enriched the existing /coffee-machines endpoint instead of adding a new one. This decision, however, looks less semantically viable: coupling in one interface different modes of listing entities, by relevance and by order, is usually a bad idea, because these two types of rankings implies different usage features and scenarios.

    -

    Coming back to the code developers are write, it would now look like that:

    +

    Coming back to the code developers are writing, it would now look like that:

    // Searching for coffee machines
     // matching a user's intent
     let coffeeMachines = api.search(parameters);
    @@ -698,14 +699,14 @@ let coffeeMachines = api.search(parameters);
     app.display(coffeeMachines);
     

    Helpers

    -

    Methods similar to newly invented coffee-machines/search are called helpers. The purposes they exist is to generalize known API usage scenarios and facilitate implementing them. By ‘facilitating’ we mean not only reducing wordiness (getting rid of ‘boilerplates’), but also helping developers to avoid common problems and mistakes.

    -

    For instance, let's consider order price question. Our search function returns some ‘offers’ with prices. But ‘price’ is volatile; coffee could cost less during ‘happy hours’, for example. Implementing this functionality, developers could make a mistake thrice:

    +

    Methods similar to newly invented coffee-machines/search are called helpers. The purpose they exist is to generalize known API usage scenarios and facilitate implementing them. By ‘facilitating’ we mean not only reducing wordiness (getting rid of ‘boilerplates’), but also helping developers to avoid common problems and mistakes.

    +

    For instance, let's consider the order price question. Our search function returns some ‘offers’ with prices. But ‘price’ is volatile; coffee could cost less during ‘happy hours’, for example. Developers could make a mistake thrice while implementing this functionality:

    • cache search results on a client device for too long (as a result, the price will always be nonactual);
    • -
    • contrary to previous, call search method excessively just to actualize prices, thus overloading network and the API servers;
    • +
    • contrary to previous, call search method excessively just to actualize prices, thus overloading the network and the API servers;
    • create an order with invalid price (therefore deceiving a user, displaying one sum and debiting another).
    -

    To solve the third problem we could demand including displayed price in the order creation request, and return an error if it differs from the actual one. (Actually, any APY working with money shall do so.) But it isn't helping with first two problems, and makes user experience to degrade. Displaying actual price is always much more convenient behavior than displaying errors upon pushing the ‘place an order’ button.

    +

    To solve the third problem we could demand including the displayed price in the order creation request, and return an error if it differs from the actual one. (In fact, any API working with money shall do so.) But it isn't helping with the first two problems, and makes user experience degrade. Displaying actual price is always a much more convenient behavior than displaying errors upon pressing the ‘place an order’ button.

    One solution is to provide a special identifier to an offer. This identifier must be specified in an order creation request.

    {
       "results": [
    @@ -723,10 +724,10 @@ app.display(coffeeMachines);
       "cursor"
     }
     
    -

    Doing so we're not only helping developers to grasp a concept of getting relevant price, but also solving a UX task of telling a user about ‘happy hours’.

    -

    As an alternative we could split endpoints: one for searching, another one for obtaining offers. This second endpoint would only be needed to actualize prices in specific cafes.

    +

    Doing so we're not only helping developers to grasp a concept of getting relevant price, but also solving a UX task of telling users about ‘happy hours’.

    +

    As an alternative we could split endpoints: one for searching, another one for obtaining offers. This second endpoint would only be needed to actualize prices in the specified places.

    Error Handling

    -

    And one more step towards making developers' life easier: how an invalid price’ error would look like?

    +

    And one more step towards making developers' life easier: how an ‘invalid price’ error would look like?

    POST /v1/orders
     { … "offer_id" …}
     → 409 Conflict
    @@ -734,21 +735,21 @@ app.display(coffeeMachines);
       "message": "Invalid price"
     }
     
    -

    Formally speaking, this error response is enough: users get ‘Invalid price’ message, and they have to repeat the order. But from a UX point of view that would be a horrible decision: user hasn't made any mistakes, and this message isn't helpful at all.

    -

    The main rule of error interface in the APIs is: error response must help a client to understand what to do with this error. All other stuff is unimportant: if the error response was machine readable, there would be no need in user readable message.

    -

    Error response content must address the following questions:

    +

    Formally speaking, this error response is enough: users get the ‘Invalid price’ message, and they have to repeat the order. But from a UX point of view that would be a horrible decision: the user hasn't made any mistakes, and this message isn't helpful at all.

    +

    The main rule of error interfaces in the APIs is: an error response must help a client to understand what to do with this error. All other stuff is unimportant: if the error response was machine readable, there would be no need in the user readable message.

    +

    An error response content must address the following questions:

    1. Which party is the problem's source, client or server?
      -HTTP API traditionally employs 4xx status codes to indicate client problems, 5xx to indicates server problems (with the exception of a 404, which is an uncertainty status).
    2. +HTTP APIs traditionally employ 4xx status codes to indicate client problems, 5xx to indicates server problems (with the exception of a 404, which is an uncertainty status).
    3. If the error is caused by a server, is there any sense to repeat the request? If yes, then when?
    4. If the error is caused by a client, is it resolvable, or not?
      -Invalid price is resolvable: client could obtain new price offer and create new order using it. But if the error occurred because of a client code containing a mistake, then eliminating the cause is impossible, and there is no need to make user push the ‘place an order’ button again: this request will never succeed.
      -Here and throughout we indicate resolvable problems with 409 Conflict code, and unresolvable ones with 400 Bad Request.
    5. +The invalid price error is resolvable: client could obtain a new price offer and create a new order with it. But if the error occurred because of a mistake in the client code, then eliminating the cause is impossible, and there is no need to make user push the ‘place an order’ button again: this request will never succeed.
      +NB: here and throughout we indicate resolvable problems with 409 Conflict code, and unresolvable ones with 400 Bad Request.
    6. If the error is resolvable, then what's the kind of the problem? Obviously, client couldn't resolve a problem it's unaware of. For every resolvable problem some code must be written (reobtaining the offer in our case), so a list of error descriptions must exist.
    7. If the same kind of errors arise because of different parameters being invalid, then which parameter value is wrong exactly?
    8. Finally, if some parameter value is unacceptable, then what values are acceptable?
    -

    In our case, price mismatch error should look like:

    +

    In our case, the price mismatch error should look like:

    409 Conflict
     {
       // Error kind
    @@ -764,13 +765,13 @@ Here and throughout we indicate resolvable problems with 409 Conflict
    -

    After getting this mistake, a client is to check its kind (‘some problem with offer’), check specific error reason (‘order lifetime expired’) and send offer retrieve request again. If checks_failed field indicated another error reason (for example, the offer isn't bound to the specified user), client actions would be different (re-authorize the user, then get a new offer). If there were no error handler for this specific reason, a client would show localized_message to the user and invoke standard error recovery procedure.

    -

    It is also worth mentioning that unresolvable errors are useless to a user at the time (since the client couldn't react usefully to unknown errors), but it doesn't mean that providing extended error data is excessive. A developer will read it when fixing the error in the code. Also check paragraphs 12&13 in the next chapter.

    +

    After getting this error, a client is to check the error's kind (‘some problem with offer’), check the specific error reason (‘order lifetime expired’) and send an offer retrieve request again. If checks_failed field indicated another error reason (for example, the offer isn't bound to the specified user), client actions would be different (re-authorize the user, then get a new offer). If there were no error handler for this specific reason, a client would show localized_message to the user, and invoke standard error recovery procedure.

    +

    It is also worth mentioning that unresolvable errors are useless to a user at the time (since the client couldn't react usefully to unknown errors), but it doesn't mean that providing extended error data is excessive. A developer will read it when fixing the error in the code. Also, check paragraphs 12&13 in the next chapter.

    Decomposing Interfaces. The ‘7±2’ Rule

    -

    Out of our own API development experience, we can tell without any doubt, that the greatest final interfaces design mistake (and a greatest developers' pain accordingly) is an excessive overloading of entities interfaces with fields, methods, events, parameters and other attributes.

    -

    Meanwhile, there is the ‘Golden Rule’ of interface design (applicable not only APIs, but almost to anything): humans could comfortably keep 7±2 entities in a short-term memory. Manipulating a larger number of chunks complicates things for most of humans. The rule is also known as ‘Miller's law’.

    -

    The only possible method of overcoming this law is decomposition. Entities should be grouped under single designation at every concept level of the API, so developers never operate more than 10 entities at a time.

    -

    Let's take a look at a simple example: what coffee machine search function returns. To ensure adequate UX of the app, quite bulky datasets are required.

    +

    Out of our own API development experience, we can tell without any doubt that the greatest final interfaces design mistake (and the greatest developers' pain accordingly) is an excessive overloading of entities' interfaces with fields, methods, events, parameters, and other attributes.

    +

    Meanwhile, there is the ‘Golden Rule’ of interface design (applicable not only to APIs, but almost to anything): humans could comfortably keep 7±2 entities in a short-term memory. Manipulating a larger number of chunks complicates things for most of humans. The rule is also known as ‘Miller's law’.

    +

    The only possible method of overcoming this law is decomposition. Entities should be grouped under single designation at every concept level of the API, so developers are never to operate more than 10 entities at a time.

    +

    Let's take a look at a simple example: what the coffee machine search function returns. To ensure an adequate UX of the app, quite bulky datasets are required.

    {
       "results": [
         {
    @@ -803,14 +804,12 @@ Here and throughout we indicate resolvable problems with 409 Conflict
    -

    This approach is quite normal, alas. Could be found in almost every API. As we see, a number of entities' fields exceeds recommended seven, and even 9. Fields are being mixed in a single list, often with similar prefixes.

    -

    In this situation we are to split this structure into data domains: which fields are logically related to a single subject area. In our case we may identify at least following data clusters:

    +

    This approach is quite normal, alas; could be found in almost every API. As we see, a number of entities' fields exceeds recommended 7, and even 9. Fields are being mixed into one single list, often with similar prefixes.

    +

    In this situation we are to split this structure into data domains: which fields are logically related to a single subject area. In our case we may identify at least 7 data clusters: