diff --git a/src/en/clean-copy/01-Introduction/03.md b/src/en/clean-copy/01-Introduction/03.md index a503dec..cc7a690 100644 --- a/src/en/clean-copy/01-Introduction/03.md +++ b/src/en/clean-copy/01-Introduction/03.md @@ -1,6 +1,6 @@ ### [Overview of Existing API Development Solutions][intro-api-solutions-overview] -In the first three sections of this book, we aim to discuss API design in general, not bound to any specific technology. The concepts we describe are equally applicable to web services and, let's say, operating systems (OS) APIs. +In the first three sections of this book, we aim to discuss API design in general, not bound to any specific technology. The concepts we describe are equally applicable to, let's say, web services and operating system (OS) APIs. Still, two main scenarios dominate the stage when we talk about API development: * developing client-server applications @@ -10,7 +10,7 @@ In the first case, we almost universally talk about APIs working atop the HTTP p #### HTTP API -Though the technology looks homogenous because of using the same application-level protocol, in reality, there is significant diversity regarding different approaches to realizing HTTP-based APIs. +Although the technology looks homogeneous because of using the same application-level protocol, in reality, there is significant diversity regarding different approaches to realizing HTTP-based APIs. **First**, implementations differ in terms of utilizing HTTP capabilities: * either the client-server interaction heavily relies on the features described in the HTTP standard (or rather standards, as the functionality is split across several different RFCs), @@ -19,16 +19,16 @@ Though the technology looks homogenous because of using the same application-lev The APIs that belong to the first category are usually denoted as “REST” or “RESTful” APIs. The second category comprises different RPC formats and some service protocols, for example, SSH. **Second**, different HTTP APIs rely on different data formats: - * REST APIs and some RPCs (JSON-RPC, GraphQL, etc.) use the JSON format (sometimes with some additional endpoints to transfer binary data); - * GRPC and some specialized RPC protocols like Apache Avro utilize binary formats (such as Protocol Buffers, FlatBuffers, or Apache Avro's own format); + * REST APIs and some RPCs (JSON-RPC, GraphQL, etc.) use the JSON format (sometimes with some additional endpoints to transfer binary data) + * GRPC and some specialized RPC protocols like Apache Avro utilize binary formats (such as Protocol Buffers, FlatBuffers, or Apache Avro's own format) * finally, some RPC protocols (notably SOAP and XML-RPC) employ the XML data format (which is considered a rather outdated practice by many developers). -All the above-mentioned technologies are operating in significantly dissimilar paradigms — which arise rather hot “holy war” debates among software engineers — though at the moment this book is being written we observe the choice for general-purpose APIs is reduced to the “REST API (in fact, JSON-over-HTTP) vs. GRPC vs. GraphQL” triad. +All the above-mentioned technologies operate in significantly dissimilar paradigms, which give rise to rather hot “holy war” debates among software engineers. However, at the moment this book is being written we observe the choice for general-purpose APIs is reduced to the “REST API (in fact, JSON-over-HTTP) vs. GRPC vs. GraphQL” triad. #### SDKs -The term “SDK” is not, strictly speaking, related to APIs: this is a generic term for a software toolkit. As with “REST,” however, it got some popular reading as a client framework to work with some underlying API. This might be, for example, a wrapper to a client-server API, or a UI to some OS API. The major difference from the APIs we discussed in the previous paragraph is that an “SDK” is implemented for a specific programming language and platform, and its purpose is translating the abstract language-agnostic set methods (comprising a client-server or an OS API) into concrete structures specific for the programming language and the platform. +The term “SDK” is not, strictly speaking, related to APIs: this is a generic term for a software toolkit. As with “REST,” however, it got some popular reading as a client framework to work with some underlying API. This might be, for example, a wrapper to a client-server API or a UI to some OS API. The major difference from the APIs we discussed in the previous paragraph is that an “SDK” is implemented for a specific programming language and platform, and its purpose is translating the abstract language-agnostic set methods (comprising a client-server or an OS API) into concrete structures specific for the programming language and the platform. -Unlike client-server APIs, such SDKs can hardly be generalized as each of them is developed for a specific language-platform pair. There are some interoperable SDKs, notable cross-platform mobile (React Native, Flutter, Xamarin) and desktop (JavaFX, QT) frameworks and some highly-specialized solutions (Unity). +Unlike client-server APIs, such SDKs can hardly be generalized as each of them is developed for a specific language-platform pair. There are some interoperable SDKs, notably cross-platform mobile (React Native, Flutter, Xamarin) and desktop (JavaFX, QT) frameworks and some highly-specialized solutions (Unity). Still, SDKs feature some generality in terms of *the problems they solve*, and Section V of this book will be dedicated to solving these problems of translating contexts and making UI components. \ No newline at end of file diff --git a/src/en/clean-copy/01-Introduction/04.md b/src/en/clean-copy/01-Introduction/04.md index 00da472..b27a36c 100644 --- a/src/en/clean-copy/01-Introduction/04.md +++ b/src/en/clean-copy/01-Introduction/04.md @@ -7,7 +7,7 @@ Let's discuss the second question first. Obviously, API “finesse” is primari So, how might a “fine” API design assist developers in solving their (and their users') problems? Quite simply: a well-designed API allows developers to do their jobs in the most efficient and convenient manner. The distance from formulating a task to writing working code must be as short as possible. Among other things, this means that: * it must be totally obvious from your API's structure how to solve a task * ideally, developers should be able to understand at first glance, what entities are meant to solve their problem - * the API must be readable; + * the API must be readable * ideally, developers should write correct code after just looking at the methods' nomenclature, never bothering about details (especially API implementation details!) * it is also essential to mention that not only should the problem solution (the “happy path”) be obvious, but also possible errors and exceptions (the “unhappy path”) * the API must be consistent diff --git a/src/en/clean-copy/01-Introduction/05.md b/src/en/clean-copy/01-Introduction/05.md index 6213941..30e9f3a 100644 --- a/src/en/clean-copy/01-Introduction/05.md +++ b/src/en/clean-copy/01-Introduction/05.md @@ -1,21 +1,21 @@ ### [The API-first approach][intro-api-first-approach] -Today, more and more IT companies accept the importance of the “API-first” approach, i.e., the paradigm of developing software with a heavy focus on developing APIs. +Today, more and more IT companies are recognizing the importance of the “API-first” approach, which is the paradigm of developing software with a heavy focus on APIs. -However, we must differentiate the product concept of the API-first approach from a technical one. +However, we must differentiate between the product concept of the API-first approach and the technical one. The former means that the first (and sometimes the only) step in developing a service is creating an API for it, and we will discuss it in “The API Product” section of this book. -If we, however, talk about the API-first approach in a technical sense, we mean the following: **the contract, i.e. the obligation to connect two programmable contexts, precedes the implementation and defines it**. More specifically, two rules are to be respected: - * the contract is developed and committed in a form of a specification before the functionality is implemented; - * if it turns out that the implementation and the contract differ, it is the implementation to be fixed, not the contract. +If we talk about the API-first approach in a technical sense, we mean the following: **the contract, i.e. the obligation to connect two programmable contexts, precedes the implementation and defines it**. More specifically, two rules must be respected: + * the contract is developed and committed to in the form of a specification before the functionality is implemented + * if it turns out that the implementation and the contract differ, the implementation is to be fixed, not the contract. -The “specification” in this context is a formal machine-readable description of the contract in one of the interface definition languages (IDL) — for example, in a form of a Swagger/OpenAPI document or a `.proto` file. +The “specification” in this context is a formal machine-readable description of the contract in one of the interface definition languages (IDL) — for example, in the form of a Swagger/OpenAPI document or a `.proto` file. Both rules assert that partner developers' interests are given the highest priority: - * rule \#1 allows partners for writing code based on the specification without coordinating the process with the API provider; - * the possibility of auto-generating code based on the specification emerges, and that might make development significantly less complex or even automate it; - * the code might be developed without having an access to the API; - * rule \#2 means partners won't need to change their implementations should some inconsistencies between the specification and the API functionality pop up. + * rule \#1 allows partners to write code based on the specification without coordinating the process with the API provider + * the possibility of auto-generating code based on the specification emerges, which might make development significantly less complex and error-prone or even automate it + * the code might be developed without having access to the API + * rule \#2 means partners won't need to change their implementations should some inconsistencies between the specification and the API functionality arise. -Therefore, for your API consumers, the API-first approach is a guarantee of a kind. However, it only works if the API was initially well-designed: if some irreparable flaws in the specification surfaced out, we would have no other option but break rule \#2. \ No newline at end of file +Therefore, for your API consumers, the API-first approach is a guarantee of a kind. However, it only works if the API was initially well-designed. If some irreparable flaws in the specification surface, we would have no other option but to break rule \#2. \ No newline at end of file diff --git a/src/en/clean-copy/01-Introduction/06.md b/src/en/clean-copy/01-Introduction/06.md index fc55d8f..233526b 100644 --- a/src/en/clean-copy/01-Introduction/06.md +++ b/src/en/clean-copy/01-Introduction/06.md @@ -1,13 +1,13 @@ ### [On Backward Compatibility][intro-back-compat] -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. +Backward compatibility is a *temporal* characteristic of an API. The obligation to maintain backward compatibility is the crucial point where API development differs from software development in general. -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. +Of course, backward compatibility isn't absolute. In some subject areas shipping new backward-incompatible API versions is routine. Nevertheless, every time a new backward-incompatible API version is deployed, developers need to make some non-zero effort to adapt their code to the new version. In this sense, releasing new API versions puts a sort of “tax” on customers who must spend quite real money just to ensure 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. +Large companies that occupy solid 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 backward 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 can 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. +Of course, maintaining old API versions is a sort of tax as well. Technology changes, and you cannot foresee everything, regardless of how nicely 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. We will discuss API lifecycle and version policies in Section II. \ No newline at end of file diff --git a/src/en/clean-copy/01-Introduction/07.md b/src/en/clean-copy/01-Introduction/07.md index 613d02d..402344e 100644 --- a/src/en/clean-copy/01-Introduction/07.md +++ b/src/en/clean-copy/01-Introduction/07.md @@ -1,14 +1,14 @@ ### [On Versioning][intro-versioning] -Here and throughout this book, we firmly stick to [semver](https://semver.org/) principles of versioning. +Here and throughout this book, we firmly adhere to [semver](https://semver.org/) principles of versioning. 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 backward compatibility intact. + 2. The first number (a major version) increases when backward-incompatible changes in the API are introduced. + 3. The second number (a minor version) increases when new functionality is added to the API while 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. +The sentences “a major API version” and “a new API version, containing backward-incompatible changes” are considered equivalent. -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. +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 for defining the desired version (for example, `^1.2.3` reads like “get the last stable API release that is backward-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 [“Backward Compatibility Problem Statement”](#back-compat-statement) chapter. diff --git a/src/en/clean-copy/01-Introduction/08.md b/src/en/clean-copy/01-Introduction/08.md index 3260220..21c5267 100644 --- a/src/en/clean-copy/01-Introduction/08.md +++ b/src/en/clean-copy/01-Introduction/08.md @@ -1,10 +1,10 @@ ### [Terms and Notation Keys][intro-terms-notation] -Software development is 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). +Software development is characterized, among other things, by the existence of many different engineering paradigms, whose adherents are sometimes quite aggressive towards other paradigms' adherents. 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 a 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 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. +Most of the examples of APIs will be provided in the 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: @@ -37,17 +37,17 @@ It should be read like this: * a specific `X-Idempotency-Token` header is added to the request alongside standard headers (which we omit); * terms in angle brackets (``) describe the semantics of an entity value (field, header, parameter); * a specific JSON, containing a `some_parameter` field and some other unspecified fields (indicated by ellipsis) is being sent as a request body payload; - * in response (marked with an arrow symbol `→`) server returns a `404 Not Founds` status code; the status might be omitted (treat it like a `200 OK` if no status is provided); + * in response (marked with an arrow symbol `→`) the server returns a `404 Not Found` status code; the status might be omitted (treat it like a `200 OK` if no status is provided); * the response could possibly contain additional notable headers; * the response body is a JSON comprising two fields: `error_reason` and `error_message`; field value absence means that the field contains exactly what you expect it should contain — so there is some generic error reason value which we omitted; - * if some token is too long to fit a single line, we will split it into several lines adding `⮠` to indicate it continues next line. + * if some token is too long to fit on a single line, we will split it into several lines adding `⮠` to indicate it continues next line. -The term “client” here stands for an application being executed on a user's device, either a native or a web one. The terms “agent” and “user agent” are synonymous 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 with “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-resource`s in the chapter, so there is no ambiguity) to refer to such endpoint definitions. +We will use sentences like “`POST /v1/bucket/{id}/some-resource` method” (or simply “`bucket/some-resource` method,” “`some-resource`” method — if there are no other `some-resource`s 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 one, since types are omitted. We assume such imperative structures are readable enough to skip detailed grammar explanations. \ No newline at end of file +Apart from HTTP API notation, we will employ C-style pseudocode, or, to be more precise, JavaScript-like or Python-like one since types are omitted. We assume such imperative structures are readable enough to skip detailed grammar explanations. \ No newline at end of file diff --git a/src/v1/en/clean-copy/03-Section II. The Backward Compatibility/02.md b/src/v1/en/clean-copy/03-Section II. The Backward Compatibility/02.md index e675a5f..23013b9 100644 --- a/src/v1/en/clean-copy/03-Section II. The Backward Compatibility/02.md +++ b/src/v1/en/clean-copy/03-Section II. The Backward 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 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. +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: