diff --git a/src/en/clean-copy/01-Introduction/01.md b/src/en/clean-copy/01-Introduction/01.md index a8d69d3..3a8d4cf 100644 --- a/src/en/clean-copy/01-Introduction/01.md +++ b/src/en/clean-copy/01-Introduction/01.md @@ -4,7 +4,7 @@ The book you're holding in your hands is dedicated to developing APIs as a separ We expect that the reader possesses expertise in software engineering, so we do not provide detailed definitions and explanations of the terms that a developer should already be familiar with in our understanding. Without this knowledge, it will be rather uncomfortable to read the last section of the book (and even more so, other sections). We sincerely apologize for this but that's the only way of writing the book without tripling its size. -The book comprises the Introduction and six large sections. The first three (namely, “The API Design”, “The API Patterns”, and “The Backwards Compatibility”) are fully abstract and not bound to any concrete technology. We hope they will help those readers who seek to build a systematic understanding of the API architecture in developing complex interface hierarchies. The proposed approach, as we see it, allows for designing APIs from start to finish, from a raw idea to concrete implementation. +The book comprises the Introduction and six large sections. The first three (namely, “The API Design”, “The API Patterns”, and “The Backward Compatibility”) are fully abstract and not bound to any concrete technology. We hope they will help those readers who seek to build a systematic understanding of the API architecture in developing complex interface hierarchies. The proposed approach, as we see it, allows for designing APIs from start to finish, from a raw idea to concrete implementation. The fourth and fifth sections are dedicated to specific technologies, namely developing HTTP APIs (in the “REST paradigm”) and SDKs (we will mostly talk about UI component libraries). diff --git a/src/en/clean-copy/01-Introduction/02.md b/src/en/clean-copy/01-Introduction/02.md index 7aef9f3..12fbb65 100644 --- a/src/en/clean-copy/01-Introduction/02.md +++ b/src/en/clean-copy/01-Introduction/02.md @@ -17,7 +17,7 @@ When I'm asked for an example of a well-designed API, I usually show a picture o [](https://pixabay.com/photos/pont-du-gard-france-aqueduct-bridge-3909998/) * it interconnects two areas, - * backwards compatibility has not been broken even once in two thousand years. + * backward compatibility has not been broken even once in two thousand years. What differs between a Roman aqueduct and a good API is that in the case of APIs, the contract is presumed to be *programmable*. To connect the two areas, *writing some code* is needed. The goal of this book is to help you design APIs that serve their purposes as solidly as a Roman aqueduct does. diff --git a/src/en/clean-copy/01-Introduction/04.md b/src/en/clean-copy/01-Introduction/04.md index f979554..00da472 100644 --- a/src/en/clean-copy/01-Introduction/04.md +++ b/src/en/clean-copy/01-Introduction/04.md @@ -15,6 +15,6 @@ So, how might a “fine” API design assist developers in solving their (and th However, the static convenience and clarity of APIs are simple parts. After all, nobody seeks to make an API deliberately irrational and unreadable. When we develop an API, we always start with clear basic concepts. Providing you have some experience in APIs, it's quite hard to make an API core that fails to meet obviousness, readability, and consistency criteria. -Problems begin when we start to expand our API. Adding new functionality sooner or later results in transforming once plain and simple API into a mess of conflicting concepts, and our efforts to maintain backwards compatibility will lead to illogical, unobvious, and simply bad design solutions. It is partly related to an inability to predict the future in detail: your understanding of “fine” APIs will change over time, both in objective terms (what problems the API is to solve, and what is best practice) and in subjective terms too (what obviousness, readability, and consistency *really mean* to your API design). +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 backward compatibility will lead to illogical, unobvious, and simply bad design solutions. It is partly related to an inability to predict the future in detail: your understanding of “fine” APIs will change over time, both in objective terms (what problems the API is to solve, and what is best practice) and in subjective terms too (what obviousness, readability, and consistency *really mean* to your API design). The principles we are explaining below are specifically oriented towards making APIs evolve smoothly over time, without being turned into a pile of mixed inconsistent interfaces. It is crucial to understand that this approach isn't free: the necessity to bear in mind all possible extension variants and to preserve essential growth points means interface redundancy and possibly excessive abstractions being embedded in the API design. Besides, both make the developers' jobs harder. **Providing excess design complexities being reserved for future use makes sense only if this future actually exists for your API. Otherwise, it's simply overengineering.** diff --git a/src/en/clean-copy/01-Introduction/06.md b/src/en/clean-copy/01-Introduction/06.md index dd892ca..fc55d8f 100644 --- a/src/en/clean-copy/01-Introduction/06.md +++ b/src/en/clean-copy/01-Introduction/06.md @@ -1,12 +1,12 @@ -### [On Backwards Compatibility][intro-back-compat] +### [On Backward Compatibility][intro-back-compat] -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. +Backward compatibility is a *temporal* characteristic of your API. An obligation to maintain backward 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 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. +Of course, backward 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 charge such a tax. 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 impose hidden levies on your customers. If you're able to avoid breaking backwards compatibility — never break it. +From our point of view, such a practice cannot be justified. Don't impose hidden levies on your customers. If you're able to avoid breaking backward 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. diff --git a/src/en/clean-copy/01-Introduction/07.md b/src/en/clean-copy/01-Introduction/07.md index 3b2b325..613d02d 100644 --- a/src/en/clean-copy/01-Introduction/07.md +++ b/src/en/clean-copy/01-Introduction/07.md @@ -4,11 +4,11 @@ Here and throughout this book, we firmly stick to [semver](https://semver.org/) 1. API versions are denoted with three numbers, e.g., `1.2.3`. 2. The first number (a major version) increases when backwards-incompatible changes in the API are introduced. - 3. The second number (a minor version) increases when new functionality is added to the API, keeping backwards compatibility intact. + 3. The second number (a minor version) increases when new functionality is added to the API, keeping backward compatibility intact. 4. The third number (a patch) increases when a new API version contains bug fixes only. Sentences “a major API version” and “new API version, containing backwards-incompatible changes” are therefore to be considered equivalent ones. It is usually (though not necessary) agreed that the last stable API release might be referenced by either a full version (e.g., `1.2.3`) or a reduced one (`1.2` or just `1`). Some systems support more sophisticated schemes of defining the desired version (for example, `^1.2.3` reads like “get the last stable API release that is backwards-compatible to the `1.2.3` version”) or additional shortcuts (for example, `1.2-beta` to refer to the last beta release of the `1.2` API version family). In this book, we will mostly use designations like `v1` (`v2`, `v3`, etc.) to denote the latest stable release of the `1.x.x` version family of an API. -The practical meaning of this versioning system and the applicable policies will be discussed in more detail in the [“Backwards Compatibility Problem Statement”](#back-compat-statement) chapter. +The practical meaning of this versioning system and the applicable policies will be discussed in more detail in the [“Backward Compatibility Problem Statement”](#back-compat-statement) chapter. diff --git a/src/en/clean-copy/02-Section I. The API Design/03.md b/src/en/clean-copy/02-Section I. The API Design/03.md index f7cd208..786d732 100644 --- a/src/en/clean-copy/02-Section I. The API Design/03.md +++ b/src/en/clean-copy/02-Section I. The API Design/03.md @@ -16,7 +16,7 @@ Every level presents a developer-facing “facet” in our API. While elaboratin 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. - 2. Preserving backwards compatibility. Properly separated abstraction levels allow for adding new functionality while keeping interfaces intact. + 2. Preserving backward compatibility. Properly separated abstraction levels allow for adding new functionality while keeping interfaces intact. 3. Maintaining interoperability. Properly isolated low-level abstractions help us to adapt the API to different platforms and technologies without changing high-level entities. diff --git a/src/en/clean-copy/03-[Work in Progress] Section II. The API Patterns/04.md b/src/en/clean-copy/03-[Work in Progress] Section II. The API Patterns/04.md index 83388f0..cbdfa58 100644 --- a/src/en/clean-copy/03-[Work in Progress] Section II. The API Patterns/04.md +++ b/src/en/clean-copy/03-[Work in Progress] Section II. The API Patterns/04.md @@ -22,7 +22,7 @@ try { As orders are created much more rarely than read, we might significantly increase the system performance if we drop the requirement of returning the most recent state of the resource from the state retrieval endpoints. The versioning will help us avoid possible problems: creating an order will still be impossible unless the client has the actual version. In fact, we transited to the [eventual consistency](https://en.wikipedia.org/wiki/Consistency_model#Eventual_consistency) model: the client will be able to fulfill its request *sometime* when it finally gets the actual data. In modern microservice architectures, eventual consistency is rather an industrial standard, and it might be close to impossible to achieve the opposite, i.e., strict consistency. -**NB**: let us stress that you might choose the approach only in the case of exposing new APIs. If you're already providing an endpoint implementing some consistency model, you can't just lower the consistency level (for instance, introduce eventual consistency instead of the strict one) even if you never documented the behavior. This will be discussed in detail in the [“On the Waterline of the Iceberg”](#back-compat-iceberg-waterline) chapter of “The Backwards Compatibility” section of this book. +**NB**: let us stress that you might choose the approach only in the case of exposing new APIs. If you're already providing an endpoint implementing some consistency model, you can't just lower the consistency level (for instance, introduce eventual consistency instead of the strict one) even if you never documented the behavior. This will be discussed in detail in the [“On the Waterline of the Iceberg”](#back-compat-iceberg-waterline) chapter of “The Backward Compatibility” section of this book. Choosing weak consistency instead of a strict one, however, brings some disadvantages. For instance, we might require partners to wait until they get the actual resource state to make changes — but it is quite unobvious for partners (and actually inconvenient) they must be prepared to wait for changes they made themselves to propagate. diff --git a/src/en/clean-copy/04-Section III. The Backwards Compatibility/01.md b/src/en/clean-copy/04-Section III. The Backwards Compatibility/01.md index aea9661..2c320e0 100644 --- a/src/en/clean-copy/04-Section III. The Backwards Compatibility/01.md +++ b/src/en/clean-copy/04-Section III. The Backwards Compatibility/01.md @@ -1,8 +1,8 @@ -### [The Backwards Compatibility Problem Statement][back-compat-statement] +### [The Backward Compatibility Problem Statement][back-compat-statement] -As usual, let's conceptually define “backwards compatibility” before we start. +As usual, let's conceptually define “backward compatibility” before we start. -Backwards compatibility is a feature of the entire API system to be stable in time. It means the following: **the code that developers have written using your API continues working functionally correctly for a long period of time**. There are two important questions to this definition and two explanations: +Backward compatibility is a feature of the entire API system to be stable in time. It means the following: **the code that developers have written using your API continues working functionally correctly for a long period of time**. There are two important questions to this definition and two explanations: 1. What does “functionally correctly” mean? @@ -10,13 +10,13 @@ Backwards compatibility is a feature of the entire API system to be stable in ti 2. What does “a long period of time” mean? - From our point of view, the backwards compatibility maintenance period should be reconciled with the typical lifetime of applications in the subject area. Platform LTS periods are decent guidance in most cases. Since the applications will be rewritten anyway when the platform maintenance period ends, it is reasonable to expect developers to move to the new API version as well. In mainstream subject areas (i.e., desktop and mobile operating systems) this period lasts several years. + From our point of view, the backward compatibility maintenance period should be reconciled with the typical lifetime of applications in the subject area. Platform LTS periods are decent guidance in most cases. Since the applications will be rewritten anyway when the platform maintenance period ends, it is reasonable to expect developers to move to the new API version as well. In mainstream subject areas (i.e., desktop and mobile operating systems) this period lasts several years. -From the definition becomes obvious why backwards compatibility needs to be maintained (including taking necessary measures at the API design stage). An outage, full or partial, caused by an API vendor, is an extremely uncomfortable situation for every developer, if not a disaster — especially if they pay money for the API usage. +From the definition becomes obvious why backward compatibility needs to be maintained (including taking necessary measures at the API design stage). An outage, full or partial, caused by an API vendor, is an extremely uncomfortable situation for every developer, if not a disaster — especially if they pay money for the API usage. -But let's take a look at the problem from another angle: why the problem of maintaining backwards compatibility exists in the first place? Why would anyone *want* to break it? This question, though it looks quite trivial, is much more complicated than the previous one. +But let's take a look at the problem from another angle: why the problem of maintaining backward compatibility exists in the first place? Why would anyone *want* to break it? This question, though it looks quite trivial, is much more complicated than the previous one. -We could say that *we break backwards compatibility to introduce new features to the API*. But that would be deceiving: new features are called *“new”* for a reason, as they cannot affect existing implementations which are not using them. We must admit there are several associated problems, which lead to the aspiration to rewrite *our* code, the code of the API itself, and ship a new major version: +We could say that *we break backward compatibility to introduce new features to the API*. But that would be deceiving: new features are called *“new”* for a reason, as they cannot affect existing implementations which are not using them. We must admit there are several associated problems, which lead to the aspiration to rewrite *our* code, the code of the API itself, and ship a new major version: * the codebase eventually becomes outdated; making changes, even introducing totally new functionality, becomes impractical; @@ -36,9 +36,9 @@ Apart from our aspirations to change the API architecture, three other tectonic When you shipped the very first API version, and the very first clients started to use it, the situation was perfect. There was only one version, and all clients were using only it. When this perfection ends, two scenarios are possible. - 1. If the platform allows for fetching code on-demand as the good old Web does, and you weren't too lazy to implement that code-on-demand feature (in a form of a platform SDK — for example, JS API), then the evolution of your API is more or less under your control. Maintaining backwards compatibility effectively means keeping *the client library* backwards-compatible. As for client-server interaction, you're free. + 1. If the platform allows for fetching code on-demand as the good old Web does, and you weren't too lazy to implement that code-on-demand feature (in a form of a platform SDK — for example, JS API), then the evolution of your API is more or less under your control. Maintaining backward compatibility effectively means keeping *the client library* backwards-compatible. As for client-server interaction, you're free. - It doesn't mean that you can't break backwards compatibility. You still can make a mess with cache-control headers or just overlook a bug in the code. Besides, even code-on-demand systems don't get updated instantly. The author of this book faced a situation when users were deliberately keeping a browser tab open *for weeks* to get rid of updates. But still, you usually don't have to support more than two API versions — the last one and the penultimate one. Furthermore, you may try to rewrite the previous major version of the library, implementing it on top of the actual API version. + It doesn't mean that you can't break backward compatibility. You still can make a mess with cache-control headers or just overlook a bug in the code. Besides, even code-on-demand systems don't get updated instantly. The author of this book faced a situation when users were deliberately keeping a browser tab open *for weeks* to get rid of updates. But still, you usually don't have to support more than two API versions — the last one and the penultimate one. Furthermore, you may try to rewrite the previous major version of the library, implementing it on top of the actual API version. 2. If the code-on-demand feature isn't supported or is prohibited by the platform, as in modern mobile operating systems, then the situation becomes more severe. Each client effectively borrows a snapshot of the code working with your API, frozen at the moment of compilation. Client application updates are scattered over time to much more extent than Web application updates. The most painful thing is that *some clients will never be up to date*, because one of three reasons: @@ -48,7 +48,7 @@ When you shipped the very first API version, and the very first clients started In modern times these three categories combined could easily constitute tens of per cent of auditory. It implies that cutting the support of any API version might be a nightmare experience — especially if developers' apps continue supporting a more broad spectrum of platforms than the API does. - You could have never issued any SDK, providing just the server-side API, for example in a form of HTTP endpoints. You might think that the backwards compatibility problem is mitigated (by making your API less competitive on the market because of a lack of SDKs). That's not true: if you don't provide an SDK, then developers will either adopt an unofficial one (if someone bothered to make it) or just write a framework themselves — independently. “Your framework — your problems” strategy, fortunately, or not, works badly: if developers write low-quality code atop your API, then your API is of low quality itself — definitely in the view of developers, possibly in the view of end-users, if the API performance within the app is visible to them. + You could have never issued any SDK, providing just the server-side API, for example in a form of HTTP endpoints. You might think that the backward compatibility problem is mitigated (by making your API less competitive on the market because of a lack of SDKs). That's not true: if you don't provide an SDK, then developers will either adopt an unofficial one (if someone bothered to make it) or just write a framework themselves — independently. “Your framework — your problems” strategy, fortunately, or not, works badly: if developers write low-quality code atop your API, then your API is of low quality itself — definitely in the view of developers, possibly in the view of end-users, if the API performance within the app is visible to them. Certainly, if you provide a stateless API that doesn't require client SDKs (or they might be auto-generated from the spec), those problems will be much less noticeable, but not fully avoidable unless you never issue any new API version. If you do, you will still have to deal with some fragmentation of users by API and SDK versions. @@ -62,7 +62,7 @@ The other side of the canyon is the underlying functionality you're exposing via As usual, the API provides an abstraction to a much more granular subject area. In the case of our coffee machine API example, one might reasonably expect new machine models to pop up, which are to be supported by the platform. New models tend to provide new APIs, and it's hard to guarantee they might be adopted while preserving the same high-level API. And anyway, the code needs to be altered, which might lead to incompatibility, albeit unintentional. -Let us also stress that vendors of low-level API are not always as resolute regarding maintaining backwards compatibility for their APIs (actually, any software they provide) as (we hope so) you are. You should be warned that keeping your API in an operational state, i.e., writing and supporting facades to the shifting subject area landscape, will be a problem of yours, and sometimes rather a sudden one. +Let us also stress that vendors of low-level API are not always as resolute regarding maintaining backward compatibility for their APIs (actually, any software they provide) as (we hope so) you are. You should be warned that keeping your API in an operational state, i.e., writing and supporting facades to the shifting subject area landscape, will be a problem of yours, and sometimes rather a sudden one. #### Platform Drift @@ -74,15 +74,15 @@ The nastiest thing here is that not only does incremental progress in a form of #### Backwards-Compatible Specifications -In the case of the API-first approach, the backwards compatibility problem gets one more dimension: the specification and code generation based on it. It becomes possible to break backwards compatibility without breaking the spec (let's say by introducing eventual consistency instead of the strict one) — and vice versa, modify the spec in a backwards-incompatible manner changing nothing in the protocol and therefore not affecting existing integrations at all (let's say, by replacing `additionalProperties: false` with `true` in OpenAPI). +In the case of the API-first approach, the backward compatibility problem gets one more dimension: the specification and code generation based on it. It becomes possible to break backward compatibility without breaking the spec (let's say by introducing eventual consistency instead of the strict one) — and vice versa, modify the spec in a backwards-incompatible manner changing nothing in the protocol and therefore not affecting existing integrations at all (let's say, by replacing `additionalProperties: false` with `true` in OpenAPI). The question of whether two specification versions are backwards compatible or not rather belongs to a gray zone, as specification standards themselves do not define this. Generally speaking, the “specification change is backwards-compatible” statement is equivalent to “any client code written or generated based on the previous version of the spec continues working correctly after the API vendor releases the new API version implementing the new version of the spec.” Practically speaking, following this definition seems quite unrealistic for two reasons: it's impossible to learn the behavior of every piece of code-generating software there (for instance, it's rather hard to say whether code generated based on a specification that includes the parameter `additionalProperties: false` will still function properly if the server starts returning additional fields). Thus, using IDLs to describe APIs with all advantages it undeniably brings to the field, leads to having one more side to the technology drift problem: the IDL version and, more importantly, versions of helper software based on it, are constantly and sometimes unpredictably evolving. -**NB**: we incline to recommend sticking to reasonable practices, i.e., don't use the functionality that is controversial from the backwards compatibility point of view (including the above-mentioned `additionalProperties: false`) and, while evaluating the safety of changes, consider spec-generated code behave just like a manually written one. If you still get into the situation of unresolvable doubts, your only option is to manually check every code generator with regards to whether its output continues working with the new version of the API. +**NB**: we incline to recommend sticking to reasonable practices, i.e., don't use the functionality that is controversial from the backward compatibility point of view (including the above-mentioned `additionalProperties: false`) and, while evaluating the safety of changes, consider spec-generated code behave just like a manually written one. If you still get into the situation of unresolvable doubts, your only option is to manually check every code generator with regards to whether its output continues working with the new version of the API. -#### Backwards Compatibility Policy +#### Backward Compatibility Policy To summarize the above: * you will have to deploy new API versions because of apps, platforms, and subject areas evolution; different areas are evolving at a different pace, but never stop doing so; @@ -122,4 +122,4 @@ The important (and undeniable) advantage of the *semver* system is that it provi Of course, preserving minor versions infinitely isn't possible (partly because of security and compliance issues that tend to pile up). However, providing such access for a reasonable period of time is rather a hygienic norm for popular APIs. -**NB**. Sometimes to defend the single accessible API version concept, the following argument is put forward: preserving the SDK or API application server code is not enough to maintain strict backwards compatibility as it might be relying on some un-versioned services (for example, some data in the DB that are shared between all the API versions). We, however, consider this an additional reason to isolate such dependencies (see [“The Serenity Notepad”](#back-compat-serenity-notepad) chapter) as it means that changes to these subsystems might lead to the inoperability of the API. \ No newline at end of file +**NB**. Sometimes to defend the single accessible API version concept, the following argument is put forward: preserving the SDK or API application server code is not enough to maintain strict backward compatibility as it might be relying on some un-versioned services (for example, some data in the DB that are shared between all the API versions). We, however, consider this an additional reason to isolate such dependencies (see [“The Serenity Notepad”](#back-compat-serenity-notepad) chapter) as it means that changes to these subsystems might lead to the inoperability of the API. \ No newline at end of file diff --git a/src/en/clean-copy/04-Section III. The Backwards Compatibility/02.md b/src/en/clean-copy/04-Section III. The Backwards Compatibility/02.md index b7cd40c..e675a5f 100644 --- a/src/en/clean-copy/04-Section III. The Backwards Compatibility/02.md +++ b/src/en/clean-copy/04-Section III. The Backwards Compatibility/02.md @@ -61,7 +61,7 @@ if (status) { We presume we may skip the explanations why such code must never be written under any circumstances. If you're really providing a non-strictly consistent API, then either the `createOrder` operation must be asynchronous and return the result when all replicas are synchronized, or the retry policy must be hidden inside the `getStatus` operation implementation. -If you failed to describe the eventual consistency in the first place, then you simply couldn't make these changes in the API. You will effectively break backwards compatibility, which will lead to huge problems with your customers' apps, intensified by the fact they can't be simply reproduced by QA engineers. +If you failed to describe the eventual consistency in the first place, then you simply couldn't make these changes in the API. You will effectively break backward compatibility, which will lead to huge problems with your customers' apps, intensified by the fact they can't be simply reproduced by QA engineers. **Example \#2**. Take a look at the following code: diff --git a/src/en/clean-copy/04-Section III. The Backwards Compatibility/03.md b/src/en/clean-copy/04-Section III. The Backwards Compatibility/03.md index df7b3d9..94cd473 100644 --- a/src/en/clean-copy/04-Section III. The Backwards Compatibility/03.md +++ b/src/en/clean-copy/04-Section III. The Backwards Compatibility/03.md @@ -83,7 +83,7 @@ More specifically, if we talk about changing available order options, we should Usually, just adding a new optional parameter to the existing interface is enough; in our case, adding non-mandatory `options` to the `PUT /coffee-machines` endpoint. -**NB**. When we talk about defining the contract as it works right now, we're talking about *internal* agreements. We must have asked partners to support those three options while negotiating the interaction format. If we had failed to do so from the very beginning, and now are defining these in a course of expanding the public API, it's a very strong claim to break backwards compatibility, and we should never do that (see the previous chapter). +**NB**. When we talk about defining the contract as it works right now, we're talking about *internal* agreements. We must have asked partners to support those three options while negotiating the interaction format. If we had failed to do so from the very beginning, and now are defining these in a course of expanding the public API, it's a very strong claim to break backward compatibility, and we should never do that (see the previous chapter). #### Limits of Applicability diff --git a/src/en/clean-copy/04-Section III. The Backwards Compatibility/05.md b/src/en/clean-copy/04-Section III. The Backwards Compatibility/05.md index 0195286..045cede 100644 --- a/src/en/clean-copy/04-Section III. The Backwards Compatibility/05.md +++ b/src/en/clean-copy/04-Section III. The Backwards Compatibility/05.md @@ -39,7 +39,7 @@ Though this API looks absolutely universal, it's quite easy to demonstrate how o 1. It describes nicely the integrations we've already implemented (it costs almost nothing to support the API types we already know) but brings no flexibility to the approach. In fact, we simply described what we'd already learned, not even trying to look at the larger picture. 2. This design is ultimately based on a single principle: every order preparation might be codified with these three imperative commands. -We may easily disprove the \#2 statement, and that will uncover the implications of the \#1. For the beginning, let us imagine that on a course of further service growth, we decided to allow end-users to change the order after the execution started. For example, request a contactless takeout. That would lead us to the creation of a new endpoint, let's say, `program_modify_endpoint`, and new difficulties in data format development (as new fields for contactless delivery requested and satisfied flags need to be passed both directions). What *is* important is that both the endpoint and the new data fields would be optional because of the backwards compatibility requirement. +We may easily disprove the \#2 statement, and that will uncover the implications of the \#1. For the beginning, let us imagine that on a course of further service growth, we decided to allow end-users to change the order after the execution started. For example, request a contactless takeout. That would lead us to the creation of a new endpoint, let's say, `program_modify_endpoint`, and new difficulties in data format development (as new fields for contactless delivery requested and satisfied flags need to be passed both directions). What *is* important is that both the endpoint and the new data fields would be optional because of the backward compatibility requirement. Now let's try to imagine a real-world example that doesn't fit into our “three imperatives to rule them all” picture. That's quite easy as well: what if we're plugging not a coffee house, but a vending machine via our API? From one side, it means that the `modify` endpoint and all related stuff are simply meaningless: the contactless takeout requirement means nothing to a vending machine. On the other side, the machine, unlike the people-operated café, requires *takeout approval*: the end-user places an order while being somewhere in some other place then walks to the machine and pushes the “get the order” button in the app. We might, of course, require the user to stand up in front of the machine when placing an order, but that would contradict the entire product concept of users selecting and ordering beverages and then walking to the takeout point. @@ -165,7 +165,7 @@ Again, this solution might look counter-intuitive, since we efficiently returned Another reason to justify this solution is that major changes occurring at different abstraction levels have different weights: * if the technical level is under change, that must not affect product qualities and the code written by partners; - * if the product is changing, e.g., we start selling flight tickets instead of preparing coffee, there is literally no sense to preserve backwards compatibility at technical abstraction levels. Ironically, we may actually make our API sell tickets instead of brewing coffee without breaking backwards compatibility, but the partners' code will still become obsolete. + * if the product is changing, e.g., we start selling flight tickets instead of preparing coffee, there is literally no sense to preserve backward compatibility at technical abstraction levels. Ironically, we may actually make our API sell tickets instead of brewing coffee without breaking backward compatibility, but the partners' code will still become obsolete. In conclusion, as higher-level APIs are evolving more slowly and much more consistently than low-level APIs, reverse strong coupling might often be acceptable or even desirable, at least from the price-quality ratio point of view. diff --git a/src/en/clean-copy/04-Section III. The Backwards Compatibility/07.md b/src/en/clean-copy/04-Section III. The Backwards Compatibility/07.md index 47795a9..509e344 100644 --- a/src/en/clean-copy/04-Section III. The Backwards Compatibility/07.md +++ b/src/en/clean-copy/04-Section III. The Backwards Compatibility/07.md @@ -1,6 +1,6 @@ ### [The Serenity Notepad][back-compat-serenity-notepad] -Apart from the abovementioned abstract principles, let us give a list of concrete recommendations: how to make changes in existing APIs to maintain backwards compatibility. +Apart from the abovementioned abstract principles, let us give a list of concrete recommendations: how to make changes in existing APIs to maintain backward compatibility ##### Remember the Iceberg's Waterline @@ -21,11 +21,11 @@ Any software must be tested, and APIs ain't an exclusion. However, there are som ##### Isolate the Dependencies In the case of a gateway API that provides access to some underlying API or aggregates several APIs behind a single façade, there is a strong temptation to proxy the original interface as is, thus not introducing any changes to it and making life much simpler by sparing an effort needed to implement the weak-coupled interaction between services. For example, while developing program execution interfaces as described in the [“Separating Abstraction Levels”](#api-design-separating-abstractions) chapter we might have taken the existing first-kind coffee-machine API as a role model and provided it in our API by just proxying the requests and responses as is. Doing so is highly undesirable because of several reasons: - * usually, you have no guarantees that the partner will maintain backwards compatibility or at least keep new versions more or less conceptually akin to the older ones; + * usually, you have no guarantees that the partner will maintain backward compatibility or at least keep new versions more or less conceptually akin to the older ones; * any partner's problem will automatically ricochet into your customers. The best practice is quite the opposite: isolate the third-party API usage, i.e., develop an abstraction level that will allow for: - * keeping backwards compatibility intact because of extension capabilities incorporated in the API design; + * keeping backward compatibility intact because of extension capabilities incorporated in the API design; * negating partner's problems by technical means: * limiting the partner's API usage in case of load surges; * implementing the retry policies or other methods of recovering after failures; diff --git a/src/en/clean-copy/07-Section VI. The API Product/06.md b/src/en/clean-copy/07-Section VI. The API Product/06.md index 5952c66..dff5234 100644 --- a/src/en/clean-copy/07-Section VI. The API Product/06.md +++ b/src/en/clean-copy/07-Section VI. The API Product/06.md @@ -8,7 +8,7 @@ Usually, any functionality available through an API might be split into independ Different companies employ different approaches to determining the granularity of API services, i.e., what is counted as a separate product and what is not. To some extent, this is a matter of convenience and taste judgment. Consider splitting an API into parts if: * it makes sense for partners to integrate only one API part, i.e., there are some isolated subsets of the API that alone provide enough means to solve users' problems; - * API parts might be versioned separately and independently, and it is meaningful from the partners' point of view (this usually means that those “isolated” APIs are either fully independent or maintain strict backwards compatibility and introduce new major versions only when it's absolutely necessary; otherwise, maintaining a matrix which API \#1 version is compatible with which API \#2 version will soon become a catastrophe); + * API parts might be versioned separately and independently, and it is meaningful from the partners' point of view (this usually means that those “isolated” APIs are either fully independent or maintain strict backward compatibility and introduce new major versions only when it's absolutely necessary; otherwise, maintaining a matrix which API \#1 version is compatible with which API \#2 version will soon become a catastrophe); * it makes sense to set tariffs and limits for each API service independently; * the auditory of the API segments (either developers, business owners, or end users) is not overlapping, and “selling” granular API to customers is much easier than aggregated. diff --git a/src/en/clean-copy/07-Section VI. The API Product/11.md b/src/en/clean-copy/07-Section VI. The API Product/11.md index 569c7be..c034e3b 100644 --- a/src/en/clean-copy/07-Section VI. The API Product/11.md +++ b/src/en/clean-copy/07-Section VI. The API Product/11.md @@ -81,7 +81,7 @@ A significant problem that harms documentation clarity is API versioning: articl * if a version of the current page exists for newer API versions, there is an explicit link to the actual version; * docs for deprecated API versions are pessimized or even excluded from indexing. -If you're strictly maintaining backwards compatibility, it is possible to create a single documentation for all API versions. To do so, each entity is to be marked with the API version it is supported from. However, there is an apparent problem with this approach: it's not that simple to get docs for a specific (outdated) API version (and, generally speaking, to understand which capabilities this API version provides). (Though the offline documentation we mentioned earlier will help.) +If you're strictly maintaining backward compatibility, it is possible to create a single documentation for all API versions. To do so, each entity is to be marked with the API version it is supported from. However, there is an apparent problem with this approach: it's not that simple to get docs for a specific (outdated) API version (and, generally speaking, to understand which capabilities this API version provides). (Though the offline documentation we mentioned earlier will help.) The problem becomes worse if you're supporting not only different API versions but also different environments / platforms / programming languages; for example, if your UI lib supports both iOS and Android. Then both documentation versions are equal, and it's impossible to pessimize one of them. diff --git a/src/en/clean-copy/07-Section VI. The API Product/13.md b/src/en/clean-copy/07-Section VI. The API Product/13.md index 5be9930..43645f0 100644 --- a/src/en/clean-copy/07-Section VI. The API Product/13.md +++ b/src/en/clean-copy/07-Section VI. The API Product/13.md @@ -6,7 +6,7 @@ Finally, the last aspect we would like to shed the light on is managing partners Ideally, the API once published should live eternally; but as we all are reasonable people, we do understand it's impossible in the real life. Even if we continue supporting older versions, they will still become outdated eventually, and partners will need to rewrite the code to use newer functionality. -The author of this book formulates the rule of issuing new major API versions like this: the period of time after which partners will need to rewrite the code should coincide with the application lifespan in the subject area (see [“The Backwards Compatibility Problem Statement”](#back-compat-statement) chapter). Apart from updating *major* versions, sooner or later you will face issues with accessing some outdated *minor* versions as well. As we mentioned in the [“On the Waterline of the Iceberg”](#back-compat-iceberg-waterline) chapter, even fixing bugs might eventually lead to breaking some integrations, and that naturally leads us to the necessity of keeping older *minor* versions of the API until the partner resolves the problem. +The author of this book formulates the rule of issuing new major API versions like this: the period of time after which partners will need to rewrite the code should coincide with the application lifespan in the subject area (see [“The Backward Compatibility Problem Statement”](#back-compat-statement) chapter). Apart from updating *major* versions, sooner or later you will face issues with accessing some outdated *minor* versions as well. As we mentioned in the [“On the Waterline of the Iceberg”](#back-compat-iceberg-waterline) chapter, even fixing bugs might eventually lead to breaking some integrations, and that naturally leads us to the necessity of keeping older *minor* versions of the API until the partner resolves the problem. In this aspect, integrating with large companies that have a dedicated software engineering department differs dramatically from providing a solution to individual amateur programmers: on one hand, the former are much more likely to find undocumented features and unfixed bugs in your code; on the other hand, because of the internal bureaucracy, fixing the related issues might easily take months, save not years. The common recommendation there is to maintain old minor API versions for a period of time long enough for the most dilatory partner to switch no the newest version. @@ -22,4 +22,4 @@ Finally, apart from those specific issues, your customers must be caring about m You might work with these customer expectations by publishing roadmaps. It's quite common that many companies avoid publicly announcing their concrete plans (for a reason, of course). Nevertheless, in the case of APIs, we strongly recommend providing the roadmaps, even if they are tentative and lack precise dates — *especially* if we talk about deprecating some functionality. Announcing these promises (given the company keeps them, of course) is a very important competitive advantage to every kind of consumer. -With this, we would like to conclude this book. We hope that the principles and the concepts we have outlined will help you in creating APIs that fit all the developers, businesses, and end users' needs, and in expanding them (while maintaining the backwards compatibility) for the next two thousand years or so. \ No newline at end of file +With this, we would like to conclude this book. We hope that the principles and the concepts we have outlined will help you in creating APIs that fit all the developers, businesses, and end users' needs, and in expanding them (while maintaining the backward compatibility) for the next two thousand years or so. \ No newline at end of file diff --git a/src/en/l10n.json b/src/en/l10n.json index cae8dca..6160753 100644 --- a/src/en/l10n.json +++ b/src/en/l10n.json @@ -3,7 +3,7 @@ "author": "Sergey Konstantinov", "chapter": "Chapter", "toc": "Table of Contents", - "description": "API-first development is one of the hottest technical topics nowadays since many companies started to realize that API serves as a multiplicator to their opportunities—but it also amplifies the design mistakes as well. This book is written to share the expertise and describe the best practices in designing and developing APIs. It comprises six sections dedicated to: the API design, API patterns, maintaining backwards compatibility, HTTP API & REST, SDK and UI libraries, API product management.", + "description": "API-first development is one of the hottest technical topics nowadays since many companies started to realize that API serves as a multiplicator to their opportunities—but it also amplifies the design mistakes as well. This book is written to share the expertise and describe the best practices in designing and developing APIs. It comprises six sections dedicated to: the API design, API patterns, maintaining backward compatibility, HTTP API & REST, SDK and UI libraries, API product management.", "locale": "en_US", "file": "API", "aboutMe": { @@ -72,7 +72,7 @@ "
This book is written to share the expertise and describe the best practices in designing and developing APIs. It comprises six sections dedicated to:
", "This book is written to share the expertise and describe the best practices in designing and developing APIs. It comprises six sections dedicated to:
", "