diff --git a/docs/API.en.epub b/docs/API.en.epub index 8b3e733..b0888ee 100644 Binary files a/docs/API.en.epub and b/docs/API.en.epub differ diff --git a/docs/API.en.html b/docs/API.en.html index 9cc66e9..a1e3309 100644 --- a/docs/API.en.html +++ b/docs/API.en.html @@ -368,8 +368,8 @@ h1 { -

Introduction

Chapter 1. On the Structure of This Book

-

The book you're holding in your hands comprises this Introduction and two sections: ‘The API Design’ and ‘Backwards Compatibility’.

+

Introduction

Chapter 1. On the Structure of This Book

+

The book you're holding in your hands comprises this Introduction and two sections: ‘The API Design’ and ‘The Backwards Compatibility’.

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

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

One more section is planned for the future: the ‘API as a Product’ section will be more about the un-engineering sides of the API, like API marketing, organizing customer support, and working with a community.

@@ -378,8 +378,8 @@ h1 {

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 of what's so important about a Man. (Actually, not ‘fine’ either. Diogenes of Sinope once brought a plucked chicken, saying ‘That's Plato's Man’. And Plato had to add ‘with broad nails’ to his definition.)

What 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 the tip of the 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.

+

But those are just the tip of the iceberg. To make the HTTP protocol work you need the entire network stack (comprising 4-5 or even more different level protocols) works 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 APIs working fine.

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

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

The Pont-du-Gard aqueduct. Built in the 1st century AD.  Image Credit: igorelick @ pixabay
The Pont-du-Gard aqueduct. Built in the 1st century AD
Image Credit: igorelick @ pixabay
@@ -387,8 +387,8 @@ h1 {
  • it interconnects two areas;
  • backwards compatibility being broken not a single time in two thousand years.
  • -

    What differs between a Roman aqueduct and a good API is that APIs presume 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 aqueduct does.

    -

    An aqueduct also illustrates another problem of the API design: your customers are engineers themselves. You are not supplying water to end-users: suppliers are plugging their pipes to your engineering structure, building their own structures upon it. From one side, you may provide access to the water to many more people through them, not spending your time on plugging each individual house to your network. From the 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.

    +

    What differs between a Roman aqueduct and a good API is that APIs presume a contract to be 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 aqueduct does.

    +

    An aqueduct also illustrates another problem of the API design: your customers are engineers themselves. You are not supplying water to end-users: suppliers are plugging their pipes into your engineering structure, building their own structures upon it. From one side, you may provide access to the water to many more people through them, not spending your time plugging each individual house into your network. From the 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 the 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' problems might not be the main purpose of offering the API of ours to developers. However, manipulating public opinion is out of this book's author's interest. Here we assume that APIs exist primarily to help developers in solving their problems, not for some other covertly declared purposes.)

    @@ -398,11 +398,11 @@ h1 {
  • 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 is 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. Providing you've got some experience in APIs, it's quite hard to make an API core that 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 detail: 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).

    -

    The principles we are explaining below are specifically oriented to making APIs evolve smoothly over time, not being turned into a pile of mixed inconsistent interfaces. It is crucial to understand that this approach isn't free: a necessity to bear in mind all possible extension variants and to preserve essential growth points means interface redundancy and possibly excessing abstractions being embedded in the API design. Besides both make 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

    +

    However static convenience and clarity of APIs are simple parts. 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 that fails to meet obviousness, readability, and consistency criteria.

    +

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

    +

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

    Chapter 4. Backwards Compatibility

    Backwards compatibility is a temporal characteristic of your API. An obligation to maintain backwards compatibility is the crucial point where API development differs from 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 continues working.

    +

    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 a 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 continues working.

    Large companies, which occupy firm market positions, could afford to imply such 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 a 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 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 a new version. But at least you will be able to explain to your customers why they need to make an effort.

    @@ -410,15 +410,15 @@ h1 {

    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) increases when backwards incompatible changes in the API are shipped.
    4. -
    5. Second Number (minor version) increases when new functionality is added to the API, keeping backwards compatibility intact.
    6. +
    7. First number (major version) increases when backwards-incompatible changes in the API are shipped.
    8. +
    9. Second number (minor version) increases when new functionality is added to the API, keeping backwards compatibility intact.
    10. Third number (patch) increases when a new API version contains bug fixes only.
    -

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

    +

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

    In Section II we will discuss versioning policies in more detail. 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 the 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 is right).

    +

    Software development is being characterized, among other things, by the 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 the neutral term ‘entity’ instead. ‘Entity’ means some atomic functionality unit, like class, method, object, monad, prototype (underline what you think is right).

    As for an entity's components, we regretfully failed to find a proper term, so we will use the 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.

    +

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

    Let's take a look at the following example:

    // Method description
     POST /v1/bucket/{id}/some-resource
    @@ -443,11 +443,11 @@ Cache-Control: no-cache
     
  • a specific X-Idempotency-Token header is added to the request alongside standard headers (which we omit);
  • terms in angle brackets (<idempotency token>) describe the semantics of an entity value (field, header, parameter);
  • a specific JSON, containing a some_parameter field and some other unspecified fields (indicated by ellipsis) is being sent as a request body payload;
  • -
  • in response (marked with 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);
  • +
  • in response (marked with an arrow symbol ) server returns a 404 Not Founds status code; the status might be omitted (treat it like a 200 OK if no status is provided);
  • the response could possibly contain additional notable headers;
  • the response body is a JSON comprising a 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 or web one. Terms ‘agent’ and ‘user agent’ are synonymous to ‘client’.

    +

    The term ‘client’ here stands for an application being executed on a user's device, either a native or a web one. The terms ‘agent’ and ‘user agent’ are synonymous to ‘client’.

    Some request and response parts might be omitted if they are irrelevant to the 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 sentences 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.

    @@ -462,31 +462,31 @@ Cache-Control: no-cache

    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 the real world, it would probably have very little in common with our fictional example.

    Chapter 8. Defining an Application Field

    -

    The 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 another word.

    +

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

    1. -

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

      +

      What problem do 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 the abovementioned situation poses a problem? Does someone really want to pay (literally or figuratively) to automate a solution for this problem?

      +

      What problem do we solve? Are we sure that the 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 the expertise to solve the problem?

      +

      What problem do we solve? Do we actually possess the 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?

      +

      What problem do 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'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?

      +

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

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

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

      +

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

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

    2. @@ -502,11 +502,11 @@ Cache-Control: no-cache

      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 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 these two roles in one entity is undesirable. We will talk about the motivation to specifically provide APIs in more detail in Section III.

      +

      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 these two roles in one entity is undesirable. We will talk about the motivation to specifically provide APIs in more detail 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 a sort of meta-engineering: you're writing some software to allow other companies to develop software to solve users' problems. You must possess expertise in both domains (APIs and user products) to design your API well.

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

      What and How

      -

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

      +

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

      • what we're doing, exactly;
      • how we're doing it, exactly.
      • @@ -525,7 +525,7 @@ Cache-Control: no-cache
      • Each cup of coffee is being prepared according to some recipe, which implies the presence of different ingredients and sequences of preparation steps.
      • 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 elaborating abstractions hierarchy, we are 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 on the hierarchy of abstractions, we are first of all trying to reduce the interconnectivity of different entities. That would help us to reach several goals.

    1. 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.

      @@ -563,7 +563,7 @@ GET /v1/orders/{id}

      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.

      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 request different cup volume when placing an order:

      +

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

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

      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:

      +

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

        -
      • there is a significant chance that developers will make mistakes in this functionality implementation, if they add arbitrary volume support in the code working with the POST /v1/orders handler, but forget to make corresponding changes in the order readiness check code;
      • -
      • the same field (coffee volume) now means different things in different interfaces. In 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.
      • +
      • there is a significant chance that developers will make mistakes in this functionality implementation if they add arbitrary volume support in the code working with the POST /v1/orders handler, but forget to make corresponding changes in the order readiness check code;
      • +
      • the same field (coffee volume) now means different things in different interfaces. In the GET /v1/recipes context the volume field means ‘a volume to be prepared if no arbitrary volume is specified in the POST /v1/orders request’; and it cannot be renamed to ‘default volume’ easily, 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 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:

      +

      Third, the entire scheme becomes totally inoperable if different types of coffee machines produce different volumes of lungo. To introduce the ‘lungo volume depends on machine type’ constraint we have to do quite a nasty thing: make recipes depend on coffee machine ids. 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 naughty. But how to make them nice?

      +

      Abstraction levels separation should go in three directions:

      1. From user scenarios to their internal representation: high-level entities and their method nomenclature must directly reflect API usage scenarios; low-level entities reflect the decomposition of scenarios into smaller parts.

        @@ -594,7 +594,7 @@ GET /v1/orders/{id}

        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 the other side, a coffee machine should not store the data regarding order properties (and its API probably doesn't provide such functionality).
        • +
        • on the 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:

        {
        @@ -615,7 +615,7 @@ GET /v1/orders/{id}
         

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

        An experienced developer in this case must ask: what options do exist? How 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:

        +

        In our example let's assume that we have studied coffee machines' API specs, and learned that two device types exist:

        • coffee machines capable of executing programs coded in the firmware; the only customizable options are some beverage parameters, like desired volume, a syrup flavor, and a kind of milk;
        • coffee machines with built-in functions, like ‘grind specified coffee volume’, ‘shed the specified amount of water’, etc.; such coffee machines lack ‘preparation programs’, but provide access to commands and sensors.
        • @@ -658,7 +658,7 @@ POST /cancel // 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 the real life, you really got 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, (2) in the real life, you would really get something like that from vendors, and this API is quite a sane one, actually.

  • Coffee machines with built-in functions:

    @@ -714,17 +714,17 @@ GET /sensors

    Now the picture becomes more apparent: we need to abstract coffee machine API calls so that the ‘execution level’ in our API provides general functions (like beverage readiness detection) in a unified form. We should also note that these two coffee machine API kinds belong to different abstraction levels themselves: the first one provides a higher-level API than the second one. Therefore, a ‘branch’ of our API working with second-kind machines will be 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 the specific coffee machine executes it.
    2. +
    3. Obviously, the developers desire to create an order uniformly: list high-level order properties (beverage kind, volume, and special options like syrup or milk type), and don't think about how the specific coffee machine executes it.
    4. 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).
    5. 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.
    6. 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:

    +

    Note, that the first-kind API is much closer to developers' needs than the second-kind API. An indivisible ‘program’ is a way more convenient concept than working with raw commands and sensor data. There are only two problems we see in the first-kind API:

    -

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

    +

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

    So we need to introduce two abstraction levels.

    1. @@ -735,7 +735,7 @@ GET /sensors
    2. -

      Program runtime level. For the first-kind API it will provide just a wrapper for existing programs API; for the second-kind API the entire ‘runtime’ concept is to be developed from scratch by us.

      +

      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 a practical sense? Developers will still be creating orders, dealing with high-level entities only:

    @@ -774,7 +774,7 @@ GET /sensors
  • POST /v1/program-matcher/{api_type}
  • POST /v1/programs/{api_type}/{program_id}/run
  • -

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

    +

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

  • Program execution control level entities.
  • -

    We will address these questions in more details in the next chapters. Additionally, in the Section III we will also discuss, how to communicate to customers about new versions releases and older versions support discontinuance, and how to stimulate them to adopt new API versions.

    Chapter 14. On the Iceberg's Waterline

    +

    We will address these questions in more detail in the next chapters. Additionally, in Section III we will also discuss, how to communicate to customers about new releases and older versions support discontinuance, and how to stimulate them to adopt new API versions.

    Chapter 14. On the Iceberg's Waterline

    Before we start talking about the extensible API design, we should discuss the hygienic minimum. A huge number of problems would have never happened if API vendors had paid more attention to marking their area of responsibility.

    1. Provide a minimal amount of functionality

    At any moment in its lifetime, your API is like an iceberg: it comprises an observable (e.g. documented) part and a hidden one, undocumented. If the API is designed properly, these two parts correspond to each other just like the above-water and under-water parts of a real iceberg do, i.e. one to ten. Why so? Because of two obvious reasons.

    diff --git a/docs/API.en.pdf b/docs/API.en.pdf index cc239e9..bca1ab1 100644 Binary files a/docs/API.en.pdf and b/docs/API.en.pdf differ diff --git a/docs/API.ru.epub b/docs/API.ru.epub index 68fd9df..544e130 100644 Binary files a/docs/API.ru.epub and b/docs/API.ru.epub differ diff --git a/docs/API.ru.pdf b/docs/API.ru.pdf index f93c7ac..455a2e7 100644 Binary files a/docs/API.ru.pdf and b/docs/API.ru.pdf differ