You've already forked The-API-Book
							
							
				mirror of
				https://github.com/twirl/The-API-Book.git
				synced 2025-10-30 23:37:47 +02:00 
			
		
		
		
	Typography improvements
This commit is contained in:
		| @@ -12,5 +12,5 @@ export { | ||||
|     L10n, | ||||
|     Section, | ||||
|     Strings, | ||||
|     Structure | ||||
| } from '@twirl/book-builder'; | ||||
|     Structure, | ||||
| } from "@twirl/book-builder"; | ||||
|   | ||||
| @@ -12,12 +12,13 @@ In other words, hundreds or even thousands of different APIs must work correctly | ||||
|  | ||||
| **An API is an obligation**. A formal obligation to connect different programmable contexts. | ||||
|  | ||||
| When the author of this book is asked for an example of a well-designed API, he will usually show a picture of a Roman aqueduct: | ||||
| When the author of this book is asked for an example of a well-designed API, he will usually show a picture of a Roman aqueduct (see Figure 2.1): | ||||
|  | ||||
| [](https://photodune.net/user/slepitssskaya) | ||||
|   * It interconnects two areas | ||||
|   * Backward compatibility has not been broken even once in two thousand years. | ||||
|  | ||||
| [](https://photodune.net/user/slepitssskaya) | ||||
|  | ||||
| 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. | ||||
|  | ||||
| An aqueduct also illustrates another problem with 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. On the one hand, you may provide access to water to many more people through them, not spending your time plugging each individual house into your network. On the other hand, you can't control the quality of suppliers' solutions, and you are to blame every time there is a water problem caused by their incompetence. | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| ### Terms and Notation Keys | ||||
| ### Terms and Notation Keys | ||||
|  | ||||
| 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). | ||||
|  | ||||
| @@ -45,7 +45,7 @@ The term “client” here stands for an application being executed on a user's | ||||
|  | ||||
| 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 /operation` `{…, "some_parameter", …}` → `{ "operation_id" }`; request and response bodies might also be omitted. | ||||
| Simplified notation might be used to avoid redundancies, like `POST /operation` `{…, "some_parameter", …}` `→` `{ "operation_id" }`; request and response bodies might also be omitted. | ||||
|  | ||||
| We will use sentences like “`POST /v1/buckets/{id}/operation` method” (or simply “`buckets/operation` method,” “`operation`” method — if there are no other `operation`s in the chapter, so there is no ambiguity) to refer to such endpoint definitions. | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| ### Separating Abstraction Levels | ||||
| ### Separating Abstraction Levels | ||||
|  | ||||
| “Separate abstraction levels in your code” is possibly the most general advice for software developers. However, we don't think it would be a grave exaggeration to say that separating abstraction levels is also the most challenging task for API developers. | ||||
|  | ||||
| @@ -264,19 +264,14 @@ To be more specific, let's assume those two kinds of coffee machines provide the | ||||
|       // Returns the state of the sensors | ||||
|       GET /sensors | ||||
|       → | ||||
|       { | ||||
|         "sensors": [ | ||||
|           { | ||||
|             // Possible values: | ||||
|             // * cup_volume | ||||
|             // * ground_coffee_volume | ||||
|             // * cup_filled_volume | ||||
|             "type": "cup_volume", | ||||
|             "value": "200ml" | ||||
|           }, | ||||
|           … | ||||
|         ] | ||||
|       } | ||||
|       { "sensors": [{ | ||||
|         // Possible values: | ||||
|         // * cup_volume | ||||
|         // * ground_coffee_volume | ||||
|         // * cup_filled_volume | ||||
|         "type": "cup_volume", | ||||
|         "value": "200ml" | ||||
|       }, … ]} | ||||
|       ``` | ||||
|  | ||||
|       **NB**: The example is intentionally fictitious to model the situation described above: to determine beverage readiness you have to compare the requested volume with volume sensor readings. | ||||
| @@ -411,7 +406,7 @@ And the `state` like that: | ||||
| } | ||||
| ``` | ||||
|  | ||||
| **NB**: When implementing the `orders` → `match` → `run` → `runtimes` call sequence, we have two options: | ||||
| **NB**: When implementing the `orders` `→` `match` `→` `run` `→` `runtimes` call sequence, we have two options: | ||||
|   * Either `POST /orders` handler requests the data regarding the recipe, the coffee machine model, and the program on its own, and forms a stateless request that contains all necessary data (API kind, command sequence, etc.) | ||||
|   * Or the request contains only data identifiers, and the next handler in the chain will request pieces of data it needs via some internal APIs. | ||||
|  | ||||
|   | ||||
| @@ -172,9 +172,13 @@ GET /v1/coffee-machines/{id}↵ | ||||
| ##### Matching Entities Must Have Matching Names and Behave Alike | ||||
|  | ||||
| **Bad**: `begin_transition` / `stop_transition`   | ||||
|  | ||||
| — The terms `begin` and `stop` don't match; developers will have to refer to the documentation to find a paired method. | ||||
|  | ||||
| **Better**: either `begin_transition` / `end_transition` or `start_transition` / `stop_transition`. | ||||
| **Better**:  | ||||
|  | ||||
| * Either `begin_transition` / `end_transition`, or | ||||
| * `start_transition` / `stop_transition`. | ||||
|  | ||||
| **Bad**: | ||||
|  | ||||
|   | ||||
| @@ -50,7 +50,7 @@ If strict consistency is not guaranteed, the second call might easily return an | ||||
| An important pattern that helps in this situation is implementing the “read-your-writes[ref Consistency Model. Read-Your-Writes Consistency|ref:steen-tanenbaum-distributed-systems 7.3.3 Read your writes](https://en.wikipedia.org/wiki/Consistency_model#Read-your-writes_consistency)” model: it guarantees that clients observe the changes they have just made. In APIs, the read-your-writes strategy could be implemented by by making clients pass some token that describes the last change known to the client. | ||||
|  | ||||
| ```typescript | ||||
| let der = await api | ||||
| let order = await api | ||||
|   .createOrder(…); | ||||
| let pendingOrders = await api. | ||||
|   getOngoingOrders({ | ||||
|   | ||||
| @@ -51,9 +51,9 @@ Instead of a duplex connection, two separate channels might be used: one for sen | ||||
|  | ||||
| Therefore, this approach is following neither request-response (even if a callback event is a direct response to the client’s actions, it is received asynchronously, requiring the client to match the response to its requests) nor a duplex connection pattern. However, we must note that this is a *logical* distinction for the convenience of client developers, as, under the hood, the underlying messaging system framework typically relies on WebSockets or implements polling. | ||||
|  | ||||
| The most popular technology of this kind is *MQTT*[ref MQTT](https://docs.oasis-open.org/mqtt/mqtt/v5.0/mqtt-v5.0.html). Although it is considered highly efficient due to its use of low-level protocols, its disadvantages stem from its advantages: | ||||
| The most popular technology of this kind is *MQTT*.[ref MQTT](https://docs.oasis-open.org/mqtt/mqtt/v5.0/mqtt-v5.0.html) Although it is considered highly efficient due to its use of low-level protocols, its disadvantages stem from its advantages: | ||||
|  | ||||
| * The technology is designed to implement the pub/sub pattern, and its primary value lies in the fact that the server software (MQTT Broker) is provided alongside the protocol itself. Applying it to other tasks, especially bidirectional communication, can be challenging. | ||||
| * The technology is designed to implement the pub/sub pattern, and its primary value lies in the fact that the server software (*MQTT Broker*) is provided alongside the protocol itself. Applying it to other tasks, especially bidirectional communication, can be challenging. | ||||
| * The use of low-level protocols requires developers to define their own data formats. | ||||
|  | ||||
| Another popular technology for organizing message queues is the Advanced Message Queuing Protocol (*AMQP*). AMQP is an open standard for implementing message queues,[ref AMQP](https://www.amqp.org/) with many independent client and server (broker) implementations. One notable broker implementation is RabbitMQ,[ref RabbitMQ](https://www.rabbitmq.com/) while AMQP clients are typically implemented as libraries for specific client platforms and programming languages. | ||||
| @@ -103,7 +103,7 @@ What is important is that the *must* be a formal contract (preferably in a form | ||||
|  | ||||
| As a *webhook* is a callback channel, you will need to develop a separate authorization system to deal with it as it's *partners* duty to check that the request is genuinely coming from the API backend, not vice versa. We reiterate here our strictest recommendation to stick to existing standard techniques, such as mTLS; though in the real world, you will likely have to use archaic methods like fixing the caller server's IP address. | ||||
|  | ||||
| ##### 3. Develop an Interface for Setting the URL of a *Webhook* | ||||
| ##### 3. Develop an Interface for Setting the URL of a Webhook | ||||
|  | ||||
| As the callback endpoint is developed by partners, we do not know its URL beforehand. It implies some interface must exist for setting this URL and authorized public keys (probably in a form of a control panel for partners). | ||||
|  | ||||
|   | ||||
| @@ -6,7 +6,7 @@ Let us summarize what we have written in the three previous chapters: | ||||
|   2. Higher-level entities are to be the informational contexts for low-level ones, meaning they don't prescribe any specific behavior but rather translate their state and expose functionality to modify it, either directly through calling some methods or indirectly through firing events. | ||||
|   3. Concrete functionality, such as working with “bare metal” hardware or underlying platform APIs, should be delegated to low-level entities. | ||||
|  | ||||
| **NB**: There is nothing novel about these rules: one might easily recognize them as the *SOLID* architecture principles[ref SOLID|ref:martin-functional-design 12. Solid](https://en.wikipedia.org/wiki/SOLID). This is not surprising either, as *SOLID* focuses on contract-oriented development, and APIs are contracts by definition. We have simply introduced the concepts of “abstraction levels” and “informational contexts” to these principles. | ||||
| **NB**: There is nothing novel about these rules: one might easily recognize them as the *SOLID* architecture principles.[ref SOLID|ref:martin-functional-design 12. Solid](https://en.wikipedia.org/wiki/SOLID) This is not surprising either, as *SOLID* focuses on contract-oriented development, and APIs are contracts by definition. We have simply introduced the concepts of “abstraction levels” and “informational contexts” to these principles. | ||||
|  | ||||
| However, there remains an unanswered question: how should we design the entity nomenclature from the beginning so that extending the API won't result in a mess of assorted inconsistent methods from different stages of development? The answer is quite obvious: to avoid clumsy situations during abstracting (as with the recipe properties), all the entities must be originally considered as specific implementations of a more general interface, even if there are no planned alternative implementations for them. | ||||
|  | ||||
|   | ||||
| @@ -3,8 +3,8 @@ | ||||
| As we discussed in the previous chapter, today, the choice of a technology for developing client-server APIs comes down to selecting either a resource-oriented approach (commonly referred to as “REST API”; let us reiterate that we will use the term “HTTP API” instead) or a modern RPC protocol. As we mentioned earlier, *conceptually* the difference is not that significant. However, *technically* these frameworks use the HTTP protocol quite differently: | ||||
|  | ||||
| **First**, different frameworks rely on different data formats: | ||||
|   * HTTP APIs and some RPC protocols (such as *JSON-RPC*[ref JSON-RPC](https://www.jsonrpc.org/), *GraphQL*[ref GraphQL](https://graphql.org/), etc.) use the *JSON*[ref JSON](https://www.ecma-international.org/publications-and-standards/standards/ecma-404/) format (sometimes with additional endpoints for transferring binary data). | ||||
|   * *gRPC*[ref gRPC](https://grpc.io/) and some specialized RPC protocols like *Thrift*[ref Apache Thrift](https://thrift.apache.org/) and *Avro*[ref Apache Avro](https://avro.apache.org/docs/) utilize binary formats (such as *Protocol Buffers*[ref Protocol Buffers](https://protobuf.dev/), *FlatBuffers*[ref FlatBuffers](https://flatbuffers.dev/), or *Apache Avro*'s own format). | ||||
|   * HTTP APIs and some RPC protocols (such as *JSON-RPC*,[ref JSON-RPC](https://www.jsonrpc.org/) *GraphQL*,[ref GraphQL](https://graphql.org/) etc.) use the *JSON*[ref JSON](https://www.ecma-international.org/publications-and-standards/standards/ecma-404/) format (sometimes with additional endpoints for transferring binary data). | ||||
|   * *gRPC*[ref gRPC](https://grpc.io/) and some specialized RPC protocols like *Thrift*[ref Apache Thrift](https://thrift.apache.org/) and *Avro*[ref Apache Avro](https://avro.apache.org/docs/) utilize binary formats (such as *Protocol Buffers*,[ref Protocol Buffers](https://protobuf.dev/) *FlatBuffers*,[ref FlatBuffers](https://flatbuffers.dev/) or *Apache Avro*'s own format). | ||||
|   * Finally, some RPC protocols (notably *SOAP*[ref SOAP](https://www.w3.org/TR/soap12/) and *XML-RPC*[ref XML-RPC](http://xmlrpc.com/)) employ the *XML*[ref Extensible Markup Language (XML)](https://www.w3.org/TR/xml/) data format (which is considered a rather outdated practice by many developers). | ||||
|  | ||||
| **Second**, these approaches utilize HTTP capabilities differently: | ||||
| @@ -51,9 +51,9 @@ The main disadvantage of HTTP APIs is that you have to rely on intermediary agen | ||||
| ##### Quality of Solutions | ||||
|  | ||||
| The ability to read and interpret the metadata of requests and responses leads to the fragmentation of available software for working with HTTP APIs. There are plenty of tools on the market, being developed by many different companies and collaborations, and many of them are free to use: | ||||
|   * Proxies and gateways (nginx, Envoy, etc.) | ||||
|   * Different IDLs (first of all, OpenAPI) and related tools for working with specifications (Redoc, Swagger UI, etc.) and auto-generating code | ||||
|   * Programmer-oriented software that allows for convenient development and debugging of API clients (Postman, Insomnia), etc. | ||||
|   * Proxies and gateways (nginx,[ref nginx](https://nginx.org) envoy,[ref envoy](https://www.envoyproxy.io/) etc.) | ||||
|   * Different IDLs (first of all, OpenAPI[ref OpenAPI Initiative](https://www.openapis.org/)) and related tools for working with specifications (Redoc,[ref Redoc: Open source API documentation tool](https://redocly.com/docs/redoc) Swagger UI,[ref Swagger UI](https://swagger.io/tools/swagger-ui/) etc.) and auto-generating code | ||||
|   * Programmer-oriented software that allows for convenient development and debugging of API clients (Postman,[ref Postman](https://www.postman.com/) Insomnia[ref Insomnia](https://insomnia.rest/)), etc. | ||||
|  | ||||
| Of course, most of these instruments will work with APIs that utilize other paradigms. However, the ability to read HTTP metadata and interpret it *uniformly* makes it possible to easily design complex pipelines such as exporting nginx access logs to Prometheus and generating response status code monitoring dashboards in Grafana that work out of the box. | ||||
|  | ||||
|   | ||||
| @@ -21,7 +21,7 @@ Consider the following: | ||||
|   * If there is a uniform communication interface, it can be mimicked if needed, so the requirement of client and server implementation independence can always be met. | ||||
|   * If we can create an alternative server, it means we can always have a layered architecture by placing an additional proxy between the client and the server. | ||||
|   * As clients are computational machines, they *always* store some state and cache some data. | ||||
|   * Finally, the code-on-demand requirement is a sly one as in a von Neumann architecture[ref Von Neumann Architecture](https://en.wikipedia.org/wiki/Von_Neumann_architecture), we can always say that the data the client receives actually comprises instructions in some formal language. | ||||
|   * Finally, the code-on-demand requirement is a sly one as in a von Neumann architecture,[ref Von Neumann Architecture](https://en.wikipedia.org/wiki/Von_Neumann_architecture) we can always say that the data the client receives actually comprises instructions in some formal language. | ||||
|  | ||||
| Yes, of course, the reasoning above is a sophism, a reduction to absurdity. Ironically, we might take the opposite path to absurdity by proclaiming that REST constraints are never met. For instance, the code-on-demand requirement obviously contradicts the requirement of having an independently-implemented client and server as the client must be able to interpret the instructions the server sends written in a specific language. As for the “S” rule (i.e., the “stateless” constraint), it is very hard to find a system that does not store *any* client context as it's close to impossible to make anything *useful* for the client in this case. (And, by the way, Fielding explicitly requires that: “communication … cannot take advantage of any stored context on the server.”) | ||||
|  | ||||
|   | ||||
| @@ -33,7 +33,7 @@ Content-Type: application/json | ||||
| } | ||||
| ``` | ||||
|  | ||||
| **NB**: In HTTP/2 (and future HTTP/3), separate binary frames are used for headers and data instead of the holistic text format.[ref:grigorik-high-performance-browser-networking Chapter 12. HTTP/2](https://hpbn.co/http2/) However, this doesn't affect the architectural concepts we will describe below. To avoid ambiguity, we will provide examples in the HTTP/1.1 format. | ||||
| **NB**: In HTTP/2 and HTTP/3, separate binary frames are used for headers and data instead of the holistic text format.[ref:grigorik-high-performance-browser-networking Chapter 12. HTTP/2](https://hpbn.co/http2/) However, this doesn't affect the architectural concepts we will describe below. To avoid ambiguity, we will provide examples in the HTTP/1.1 format. | ||||
|  | ||||
| ##### A URL | ||||
|  | ||||
| @@ -76,7 +76,7 @@ Additionally, headers are used as control flow instructions for so-called “con | ||||
|  | ||||
| One important component of an HTTP request is a method (verb) that describes the operation being applied to a resource. RFC 9110 standardizes eight verbs — namely, `GET`, `POST`, `PUT`, `DELETE`, `HEAD`, `CONNECT`, `OPTIONS`, and `TRACE` — of which we as API developers are interested in the former four. The `CONNECT`, `OPTIONS`, and `TRACE` methods are technical and rarely used in HTTP APIs (except for `OPTIONS`, which needs to be implemented to ensure access to the API from a web browser). Theoretically, the `HEAD` verb, which allows for requesting *resource metadata only*, might be quite useful in API design. However, for reasons unknown to us, it did not take root in this capacity. | ||||
|  | ||||
| Apart from RFC 9110, many other specifications propose additional HTTP verbs, such as `COPY`, `LOCK`, `SEARCH`, etc. — the full list can be found in the registry[ref Hypertext Transfer Protocol (HTTP) Method Registry](https://www.iana.org/assignments/http-methods/http-methods.xhtml). However, only one of them gained widespread popularity — the `PATCH` method. The reasons for this state of affairs are quite trivial: the five methods (`GET`, `POST`, `PUT`, `DELETE`, and `PATCH`) are enough for almost any API. | ||||
| Apart from RFC 9110, many other specifications propose additional HTTP verbs, such as `COPY`, `LOCK`, `SEARCH`, etc. — the full list can be found in the registry.[ref Hypertext Transfer Protocol Method Registry](https://www.iana.org/assignments/http-methods/) However, only one of them gained widespread popularity — the `PATCH` method. The reasons for this state of affairs are quite trivial: the five methods (`GET`, `POST`, `PUT`, `DELETE`, and `PATCH`) are enough for almost any API. | ||||
|  | ||||
| HTTP verbs define two important characteristics of an HTTP call: | ||||
|   * Semantics: what the operation *means* | ||||
| @@ -84,8 +84,8 @@ HTTP verbs define two important characteristics of an HTTP call: | ||||
|       * Whether the request modifies any resource state or if it is safe (and therefore, could it be cached) | ||||
|       * Whether the request is idempotent or not. | ||||
|  | ||||
| | Verb | Semantics | Is safe (non-modifying) | Is idempotent | Can have a body | | ||||
| |------|-----------|-------------------------|---------------|------------| | ||||
| | Method (verb) | Semantics | Safe? | Idempotent? | Can have a body? | | ||||
| |------|-----------|-------|-------------|------------------| | ||||
| | GET    | Returns a representation of a resource | Yes | Yes | Should not | | ||||
| | PUT    | Replaces (fully overwrites) a resource with a provided entity | No | Yes | Yes | | ||||
| | DELETE | Deletes a resource | No | Yes | Should not | | ||||
|   | ||||
| @@ -42,14 +42,15 @@ While scaling the backend is not a problem, this approach works. However, with t | ||||
|   * Service C stores orders | ||||
|   * Gateway service D routes incoming requests to other microservices. | ||||
|  | ||||
| This implies that a request traverses the following path: | ||||
| This implies that a request traverses the following path (Figure 36.1): | ||||
|   * Gateway D receives the request and sends it to both Service C and Service D. | ||||
|   * C and D call Service A to check the authentication token (passed as a proxied `Authorization` header or as an explicit request parameter) and return the requested data — the user's profile and the list of their orders. | ||||
|   * Service D merges the responses and sends them back to the client. | ||||
|  | ||||
| []() | ||||
| []() | ||||
|  | ||||
| It is quite obvious that in this setup, we put excessive load on the authorization service as every nested microservice now needs to query it. Even if we abolish checking the authenticity of internal requests, it won't help as services B and C can't know the identifier of the user. Naturally, this leads to the idea of propagating the once-retrieved `user_id` through the microservice mesh (Figure 36.2): | ||||
|  | ||||
| It is quite obvious that in this setup, we put excessive load on the authorization service as every nested microservice now needs to query it. Even if we abolish checking the authenticity of internal requests, it won't help as services B and C can't know the identifier of the user. Naturally, this leads to the idea of propagating the once-retrieved `user_id` through the microservice mesh: | ||||
|   * Gateway D receives a request and exchanges the token for `user_id` through service A | ||||
|   * Gateway D queries service B: | ||||
|  | ||||
| @@ -63,7 +64,7 @@ It is quite obvious that in this setup, we put excessive load on the authorizati | ||||
|       GET /v1/orders?user_id=<user id> | ||||
|       ``` | ||||
|  | ||||
| []() | ||||
| []() | ||||
|  | ||||
| **NB**: We used the `/v1/orders?user_id` notation and not, let's say, `/v1/users/{user_id}/orders`, because of two reasons: | ||||
|   * The orders service stores orders, not users, and it would be logical to reflect this fact in URLs | ||||
| @@ -96,7 +97,8 @@ ETag: <revision> | ||||
| … | ||||
| ``` | ||||
|  | ||||
| Then gateway D can be implemented following this scenario: | ||||
| Then gateway D can be implemented following this scenario (Figure 36.3): | ||||
|  | ||||
|   1. Cache the response of `GET /v1/orders?user_id=<user_id>` using the URL as a cache key | ||||
|   2. Upon receiving a subsequent request: | ||||
|       * Fetch the cached state, if any | ||||
| @@ -110,7 +112,7 @@ Then gateway D can be implemented following this scenario: | ||||
|       * If service C responds with a `304 Not Modified` status code, return the cached state | ||||
|       * If service C responds with a new version of the data, cache it and then return it to the client. | ||||
|  | ||||
| []() | ||||
| []() | ||||
|  | ||||
| By employing this approach [using `ETag`s to control caching], we automatically get another pleasant bonus. We can reuse the same data in the order creation endpoint design. In the optimistic concurrency control paradigm, the client must pass the actual revision of the `orders` resource to change its state: | ||||
|  | ||||
| @@ -136,9 +138,9 @@ ETag: <new revision> | ||||
| { /* The updated list of orders */ } | ||||
| ``` | ||||
|  | ||||
| and gateway D will update the cache with the current data snapshot. | ||||
| and gateway D will update the cache with the current data snapshot (Figure 36.4). | ||||
|  | ||||
| []() | ||||
| []() | ||||
|  | ||||
| **Importantly**, after this API refactoring, we end up with a system in which we can *remove gateway D* and make the client itself perform its duty. Nothing prevents the client from: | ||||
|   * Storing `user_id` on its side (or retrieving it from the token, if the format allows it) as well as the last known `ETag` of the order list | ||||
|   | ||||
| @@ -42,7 +42,7 @@ This convention allows for reflecting almost any API's entity nomenclature decen | ||||
|  | ||||
|       In other words, with any operation that runs an algorithm rather than returns a predefined result (such as listing offers relevant to a search phrase), we will have to decide what to choose: following verb semantics or indicating side effects? Caching the results or hinting that the results are generated on the fly? | ||||
|  | ||||
|       **NB**: The authors of the standard are also concerned about this dichotomy and have finally proposed the `QUERY` HTTP method[ref The HTTP QUERY Method](https://www.ietf.org/archive/id/draft-ietf-httpbis-safe-method-w-body-02.html), which is basically a safe (i.e., non-modifying) version of `POST`. However, we do not expect it to gain widespread adoption just as the existing `SEARCH` verb[ref Web Distributed Authoring and Versioning (WebDAV) SEARCH](https://www.rfc-editor.org/rfc/rfc5323) did not. | ||||
|       **NB**: The authors of the standard are also concerned about this dichotomy and have finally proposed the `QUERY` HTTP method,[ref The HTTP QUERY Method](https://www.ietf.org/archive/id/draft-ietf-httpbis-safe-method-w-body-02.html) which is basically a safe (i.e., non-modifying) version of `POST`. However, we do not expect it to gain widespread adoption just as the existing `SEARCH` verb[ref Web Distributed Authoring and Versioning (WebDAV) SEARCH](https://www.rfc-editor.org/rfc/rfc5323) did not. | ||||
|  | ||||
| Unfortunately, we don't have simple answers to these questions. Within this book, we adhere to the following approach: the call signature should, first and foremost, be concise and readable. Complicating signatures for the sake of abstract concepts is undesirable. In relation to the mentioned issues, this means that: | ||||
|   1. Operation metadata should not change the meaning of the operation. If a request reaches the final microservice without any headers at all, it should still be executable, although some auxiliary functionality may degrade or be absent. | ||||
| @@ -54,16 +54,17 @@ Unfortunately, we don't have simple answers to these questions. Within this book | ||||
|   5. The semantics of the HTTP verbs take priority over false non-safety / non-idempotency warnings. Furthermore, the author of this book prefers using `POST` to indicate any unexpected side effects of an operation, such as high computational complexity, even if it is fully safe. | ||||
|  | ||||
| **NB**: Passing variables as either query parameters or path fragments affects not only readability. Let's consider the example from the previous chapter and imagine that gateway D is implemented as a stateless proxy with a declarative configuration. Then receiving a request like this: | ||||
|  | ||||
|   * `GET /v1/state?user_id=<user_id>` | ||||
|  | ||||
|       and transforming it into a pair of nested sub-requests: | ||||
| and transforming it into a pair of nested sub-requests: | ||||
|  | ||||
|   * `GET /v1/profiles?user_id=<user_id>` | ||||
|   * `GET /v1/orders?user_id=<user_id>` | ||||
|  | ||||
|       would be much more convenient than extracting identifiers from the path or some header and putting them into query parameters. The former operation [replacing one path with another] is easily described declaratively and is supported by most server software out of the box. On the other hand, retrieving data from various components and rebuilding requests is a complex functionality that most likely requires a gateway supporting scripting languages and/or plugins for such manipulations. Conversely, the automated creation of monitoring panels in services like the Prometheus+Grafana bundle (or basically any other log analyzing tool) is much easier to organize by path prefix than by a synthetic key computed from request parameters. | ||||
| would be much more convenient than extracting identifiers from the path or some header and putting them into query parameters. The former operation [replacing one path with another] is easily described declaratively and is supported by most server software out of the box. On the other hand, retrieving data from various components and rebuilding requests is a complex functionality that most likely requires a gateway supporting scripting languages and/or plugins for such manipulations. Conversely, the automated creation of monitoring panels in services like the Prometheus+Grafana bundle (or basically any other log analyzing tool) is much easier to organize by path prefix than by a synthetic key computed from request parameters. | ||||
|  | ||||
|       All this leads us to the conclusion that maintaining an identical URL structure when paths are fixed and all the parameters are passed as query parameters will result in an even more uniform interface, although less readable and semantic. In internal systems, preferring the convenience of usage over readability is sometimes an obvious decision. In public APIs, we would rather discourage implementing this approach. | ||||
| All this leads us to the conclusion that maintaining an identical URL structure when paths are fixed and all the parameters are passed as query parameters will result in an even more uniform interface, although less readable and semantic. In internal systems, preferring the convenience of usage over readability is sometimes an obvious decision. In public APIs, we would rather discourage implementing this approach. | ||||
|  | ||||
| #### The CRUD Operations | ||||
|  | ||||
| @@ -74,9 +75,10 @@ One of the most popular tasks solved by exposing HTTP APIs is implementing CRUD | ||||
|   * The “delete” operation corresponds to deleting a resource with the `DELETE` method. | ||||
|  | ||||
| **NB**: In fact, this correspondence serves as a mnemonic to choose the appropriate HTTP verb for each operation. However, we must warn the reader that verbs should be chosen according to their definition in the standards, not based on mnemonic rules. For example, it might seem like deleting the third element in a list should be organized via the `DELETE` method: | ||||
|  | ||||
|   * `DELETE /v1/list/{list_id}/?position=3` | ||||
|  | ||||
|       However, as we remember, doing so is a grave mistake: first, such a call is non-idempotent, and second, it violates the `GET` / `DELETE` consistency principle. | ||||
| However, as we remember, doing so is a grave mistake: first, such a call is non-idempotent, and second, it violates the `GET` / `DELETE` consistency principle. | ||||
|  | ||||
| The CRUD/HTTP correspondence might appear convenient as every resource is forced to have its own URL and each operation has a suitable verb. However, upon closer examination, we will quickly understand that the correspondence presents resource manipulation in a very simplified, and, even worse, poorly extensible way. | ||||
|  | ||||
| @@ -167,11 +169,17 @@ This discourse is not intended to be perceived as criticizing the idea of CRUD o | ||||
|   * `/v1/orders/{id}` to be acted upon with `GET` / `PUT` / `DELETE` / optionally `PATCH`. | ||||
|  | ||||
| However, if we add the following requirements: | ||||
|   * Concurrency control in entity creation | ||||
|   * Collaborative editing | ||||
|   * Archiving entities | ||||
|   * Searching entities with filters | ||||
|  | ||||
| * Concurrency control in entity creation | ||||
|  | ||||
| * Collaborative editing | ||||
|  | ||||
| * Archiving entities | ||||
|  | ||||
| * Searching entities with filters | ||||
|  | ||||
| then we end up with the following nomenclature of 8 URLs and 9-10 methods: | ||||
|  | ||||
|   * `GET /v1/orders/?user_id=<user_id>` to retrieve the ongoing orders, perhaps with additional simple filters | ||||
|   * `/v1/orders/drafts/?user_id=<user_id>` to be acted upon with `POST` to create an order draft and with `GET` to retrieve existing drafts and the revision | ||||
|   * `PUT /v1/orders/drafts/{id}/commit` to commit the draft | ||||
|   | ||||
| @@ -27,7 +27,7 @@ What problems could potentially happen while handling the request? Off the top o | ||||
|   12. The server is overloaded and cannot respond | ||||
|   13. Unknown server error (i.e., the server is broken to the extent that it's impossible to understand why the error happened). | ||||
|  | ||||
| From general considerations, the natural idea is to assign a status code for each mistake. Obviously, the `403 Forbidden` code fits well for mistake \#4, and the `429 Too Many Requests` for \#11. However, let's not be rash and ask first *for what purpose* are we assigning codes to errors? | ||||
| From general considerations, the natural idea is to assign a status code for each error. Obviously, the `403 Forbidden` code fits well for \#4, and the `429 Too Many Requests` for \#11. However, let's not be rash and ask first *for what purpose* are we assigning codes to errors? | ||||
|  | ||||
| Generally speaking, there are three kinds of actors in the system: the user, the application (a client), and the server. Each of these actors needs to understand several important things about the error (and the answers could actually differ for each of them): | ||||
|   1. Who made the mistake: the end user, the developer of the client, the backend developer, or another interim agent such as the network stack programmer? | ||||
| @@ -61,7 +61,7 @@ Even if we choose this approach, there are very few status codes that can reflec | ||||
|  | ||||
| The editors of the specification are very well aware of this problem as they state that “the server SHOULD send a representation containing an explanation of the error situation, and whether it is a temporary or permanent condition.” This, however, contradicts the entire idea of a uniform machine-readable interface (and so does the idea of using arbitrary status codes). (Let us additionally emphasize that this lack of standard tools to describe business logic-bound errors is one of the reasons we consider the REST architectural style as described by Fielding in his 2008 article non-viable. The client *must* possess prior knowledge of error formats and how to work with them. Otherwise, it could restore its state after an error only by restarting the application.) | ||||
|  | ||||
| **NB**: Not long ago, the editors of the standard proposed their own version of the JSON description specification for HTTP errors — RFC 9457[ref RFC 9457 Problem Details for HTTP APIs](https://www.rfc-editor.org/rfc/rfc9457.html). You can use it, but keep in mind that it covers only the most basic scenario: | ||||
| **NB**: Not long ago, the editors of the standard proposed their own version of the JSON description specification for HTTP errors — RFC 9457.[ref RFC 9457 Problem Details for HTTP APIs](https://www.rfc-editor.org/rfc/rfc9457.html) You can use it, but keep in mind that it covers only the most basic scenario: | ||||
|   * The error subtype is not transmitted in the metadata. | ||||
|   * There is no distinction between a message for the user and a message for the developer. | ||||
|   * The specific machine-readable format for error descriptions is left to the discretion of the developer. | ||||
|   | ||||
| @@ -16,8 +16,8 @@ To avoid being wordy, we will use the term “SDK” for the former and “UI li | ||||
|  | ||||
| #### Selecting a Framework for UI Component Development | ||||
|  | ||||
| As UI is a high-level abstraction built upon OS primitives, there are specialized visual component frameworks available for almost every platform. Choosing such a framework might be regretfully challenging. For instance, in the case of the Web platform, which is both low-level and highly popular, the number of competing technologies for SDK development is beyond imagination. We could mention the most popular ones today, including React[ref React](https://react.dev/), Angular[ref Angular](https://angular.io/), Svelte[ref Svelte](https://svelte.dev/), Vue.js[ref Vue.js](https://vuejs.org/), as well as those that maintain a strong presence like Bootstrap[ref Bootstrap](https://getbootstrap.com/) and Ember.[ref Ember](https://emberjs.com/) Among these technologies, React demonstrates the most widespread adoption, still measured in single-digit percentages.[ref How Many Websites Use React in 2023? (Usage Statistics)](https://increditools.com/react-usage-statistics/) At the same time, components written in “pure” JavaScript/CSS often receive criticism for being less convenient to use in these frameworks as each of them implements a rigid methodology. The situation with developing visual libraries for Windows is quite similar. The question of “which framework to choose for developing UI components for these platforms” regretfully has no simple answer. In fact, one will need to evaluate the markets and make decisions regarding each individual framework. | ||||
| As UI is a high-level abstraction built upon OS primitives, there are specialized visual component frameworks available for almost every platform. Choosing such a framework might be regretfully challenging. For instance, in the case of the Web platform, which is both low-level and highly popular, the number of competing technologies for SDK development is beyond imagination. We could mention the most popular ones today, including React,[ref React](https://react.dev/) Angular,[ref Angular](https://angular.io/) Svelte,[ref Svelte](https://svelte.dev/) Vue.js,[ref Vue.js](https://vuejs.org/) as well as those that maintain a strong presence like Bootstrap[ref Bootstrap](https://getbootstrap.com/) and Ember.[ref Ember](https://emberjs.com/) Among these technologies, React demonstrates the most widespread adoption, still measured in single-digit percentages.[ref How Many Websites Use React in 2023? (Usage Statistics)](https://increditools.com/react-usage-statistics/) At the same time, components written in “pure” JavaScript/CSS often receive criticism for being less convenient to use in these frameworks as each of them implements a rigid methodology. The situation with developing visual libraries for Windows is quite similar. The question of “which framework to choose for developing UI components for these platforms” regretfully has no simple answer. In fact, one will need to evaluate the markets and make decisions regarding each individual framework. | ||||
|  | ||||
| In the case of actual mobile platforms (and MacOS), the current state of affairs is more favorable as they are more homogeneous. However, a different problem arises: modern applications typically need to support several such platforms simultaneously, which leads to code (and API nomenclatures) duplication. | ||||
|  | ||||
| One potential solution could be using cross-platform mobile (React Native[ref React Native](https://reactnative.dev/), Flutter[ref Flutter](https://flutter.dev/), Xamarin[ref Xamarin](https://dotnet.microsoft.com/en-us/apps/xamarin), etc.) and desktop (JavaFX[ref JavaFX](https://openjfx.io/), QT[ref QT](https://www.qt.io/), etc.) frameworks, or specialized technologies for specific tasks (such as Unity[ref Unity](https://docs.unity3d.com/Manual/index.html) for game development). The inherent advantages of these technologies are faster code-writing and universalism (of both code and software engineers). The disadvantages are obvious as well: achieving maximum performance could be challenging, and many platform tools (such as debugging and profiling) will not work. As of today, we rather see a parity between these two approaches (several independent applications for each platform vs. one cross-platform application). | ||||
| One potential solution could be using cross-platform mobile (React Native,[ref React Native](https://reactnative.dev/) Flutter,[ref Flutter](https://flutter.dev/) Xamarin,[ref Xamarin](https://dotnet.microsoft.com/en-us/apps/xamarin) etc.) and desktop (JavaFX,[ref JavaFX](https://openjfx.io/) QT,[ref QT](https://www.qt.io/) etc.) frameworks, or specialized technologies for specific tasks (such as Unity[ref Unity](https://docs.unity3d.com/Manual/index.html) for game development). The inherent advantages of these technologies are faster code-writing and universalism (of both code and software engineers). The disadvantages are obvious as well: achieving maximum performance could be challenging, and many platform tools (such as debugging and profiling) will not work. As of today, we rather see a parity between these two approaches (several independent applications for each platform vs. one cross-platform application). | ||||
| @@ -2,13 +2,13 @@ | ||||
|  | ||||
| Introducing UI components to an SDK brings an additional dimension to an already complex setup comprising a low-level API and a client wrapper on top of it. Now both developers (who write the application) and end users (who use the application) interact with your API. This might not appear as a game changer at first glance; however, we assure you that it is. Involving an end-user has significant consequences from the API / SDK design point of view as it requires much more careful and elaborate program interfaces compared to a “pure” client-server API. Let us explain this statement with a concrete example. | ||||
|  | ||||
| Imagine that we decided to provide a client SDK for our API that features ready-to-use components for application developers. The functionality is simple: the user enters a search phrase and observes the results in the form of a list. | ||||
| Imagine that we decided to provide a client SDK for our API that features ready-to-use components for application developers. The functionality is simple: the user enters a search phrase and observes the results in the form of a list (Figure 42.1). | ||||
|  | ||||
| []() | ||||
| []() | ||||
|  | ||||
| The user can select an item and view the offer details with available actions. | ||||
| The user can select an item and view the offer details with available actions (Figure 42.2). | ||||
|  | ||||
| []() | ||||
| []() | ||||
|  | ||||
| To implement this scenario, we provide an object-oriented API in the form of, let's say, a class named `SearchBox` that realizes the aforementioned functionality by utilizing the `search` method in our client-server API. | ||||
|  | ||||
| @@ -47,13 +47,13 @@ Any asynchronous operation in a UI component, especially if it is visibly indica | ||||
|  | ||||
| ##### Multiple Inheritance in Entity Hierarchies | ||||
|  | ||||
| Imagine that a developer decided to enhance the design of the offer list with icons of coffee shop chains. If the icon is set, it should be shown in every place related to a specific coffee shop's offer. | ||||
| Imagine that a developer decided to enhance the design of the offer list with icons of coffee shop chains. If the icon is set, it should be shown in every place related to a specific coffee shop's offer (Figure 42.3). | ||||
|  | ||||
| []() | ||||
| []() | ||||
|  | ||||
| Now let's also imagine that the developer additionally customized all buttons in the SDK by adding action icons. | ||||
| Now let's also imagine that the developer additionally customized all buttons in the SDK by adding action icons (Figure 42.4). | ||||
|  | ||||
| []() | ||||
| []() | ||||
|  | ||||
| A question arises: if an offer of the coffee chain is shown in the panel, which icon should be featured on the order creation button: the one inherited from the offer properties (the coffee chain logo) or the one inherited from the action type of the button itself? The order creation control element is incorporated into two entity hierarchies [visual one and data-bound (semantic) one] and inherits from both equally. | ||||
|  | ||||
|   | ||||
| @@ -6,23 +6,16 @@ Let's transition to a more substantive conversation and try to understand why th | ||||
|   * The emergence of ambivalent hierarchies in the inheritance of entity properties and options. | ||||
|  | ||||
| Let's make the task more specific. Imagine that we need to develop a `SearchBox` that allows for the following modifications: | ||||
|   1. Replacing the textual paragraphs representing an offer with a map with markers that could be highlighted: | ||||
|  | ||||
|       []() | ||||
|   1. Replacing the textual paragraphs representing an offer with a map with markers that could be highlighted (Figure 43.1). | ||||
|  | ||||
|       * This illustrates the problem of replacing a subcomponent (the offer list) while preserving the behavior and design of other parts of the system as well as the complexity of implementing shared states. | ||||
|  | ||||
|   2. Combining short and full descriptions of an offer in a single UI (a list item could be expanded, and the order can be created in-place): | ||||
|  | ||||
|       []() | ||||
|  | ||||
|       []() | ||||
|   2. Combining short (Figure 43.2) and full (Figure 43.3) descriptions of an offer in a single UI (a list item could be expanded, and the order can be created in-place). | ||||
|  | ||||
|       * This illustrates the problem of fully removing a subcomponent and transferring its business logic to other parts of the system. | ||||
|  | ||||
|   3. Manipulating the data presented to the user and the available actions for an offer through adding new buttons, such as “Previous offer,” “Next offer,” and “Make a call.” | ||||
|  | ||||
|       []() | ||||
|   3. Manipulating the data presented to the user and the available actions for an offer through adding new buttons, such as “Previous offer,” “Next offer,” and “Make a call” (Figure 43.4). | ||||
|    | ||||
|       In this scenario, we're evaluating different chains of propagating data and options down to the offer panel and building dynamic UIs on top of it: | ||||
|  | ||||
| @@ -32,9 +25,17 @@ Let's make the task more specific. Imagine that we need to develop a `SearchBox` | ||||
|  | ||||
|       * Some data fields (such as the icons of the “Not now” and “Make a call” buttons) are bound to the button type (i.e., the business logic it provides). | ||||
|  | ||||
| The obvious approach to tackling these scenarios appears to be creating two additional subcomponents responsible for presenting a list of offers and the details of the specific offer. Let's name them `OfferList` and `OfferPanel` respectively. | ||||
| []() | ||||
|  | ||||
| []() | ||||
| []() | ||||
|  | ||||
| []() | ||||
|  | ||||
| []() | ||||
|  | ||||
| The obvious approach to tackling these scenarios appears to be creating two additional subcomponents responsible for presenting a list of offers and the details of the specific offer. Let's name them `OfferList` and `OfferPanel` respectively (Figure 43.5). | ||||
|  | ||||
| []() | ||||
|  | ||||
| If we had no customization requirements, the pseudo-code implementing interactions between all three components would look rather trivial: | ||||
|  | ||||
| @@ -515,6 +516,8 @@ If we revisit the cases we began this chapter with, we can now outline solutions | ||||
| The price of this flexibility is the overwhelming complexity of component communications. Each event and data field must be propagated through the chains of such “composers” that elongate the abstraction hierarchy. Every transformation in this chain (for example, generating options for subcomponents or reacting to context events) is to be implemented in an extendable and parametrizable way. We can only offer reasonable helpers to ease using such customization. However, in the SDK code, the complexity will always be present. This is the way. | ||||
|  | ||||
| The reference implementation of all the components with the interfaces we discussed and all three customization cases can be found in this book's repository: | ||||
|   * The source code is available on [www.github.com/twirl/The-API-Book/docs/examples](https://github.com/twirl/The-API-Book/tree/gh-pages/docs/examples/01.%20Decomposing%20UI%20Components) | ||||
|   * The source code is available on: | ||||
|       * [www.github.com/twirl/The-API-Book/docs/examples](https://github.com/twirl/The-API-Book/tree/gh-pages/docs/examples/) | ||||
|       * There are also additional tasks for self-study | ||||
|   * The sandbox with “live” examples is available on [twirl.github.io/The-API-Book](https://twirl.github.io/The-API-Book/examples/01.%20Decomposing%20UI%20Components/). | ||||
|   * The sandbox with “live” examples is available on:  | ||||
|       * [twirl.github.io/The-API-Book](https://twirl.github.io/The-API-Book/). | ||||
| @@ -55,11 +55,11 @@ As we lose the ability to prepare data for subcomponents, we can no longer attac | ||||
|  | ||||
|   * Finally, `SearchBox` doesn't interact with either of them and only provides a context, methods to change it, and the corresponding notifications. | ||||
|  | ||||
| By making these reductions, in fact, we end up with a setup that follows the “Model-View-Controller” (MVC) methodology. This is one of the very first patterns for designing user interfaces proposed as early as 1979 by Trygve Reenskaug.[ref MVC](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller)[ref Reenskaug, T. (1979) MVC](https://folk.universitetetioslo.no/trygver/themes/mvc/mvc-index.html) `OfferList` and `OfferPanel` (also, the code that displays the input field) constitute a *view* that the user observes and interacts with. `Composer` is a *controller* that listens to the *view*'s events and modifies a *model* (`SearchBox` itself). | ||||
| By making these reductions, in fact, we end up with a setup that follows the “Model-View-Controller” (MVC) methodology. This is one of the very first patterns for designing user interfaces proposed as early as 1979 by Trygve Reenskaug.[ref MVC](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller)[ref Reenskaug, T. (1979) MVC](https://folk.universitetetioslo.no/trygver/themes/mvc/mvc-index.html) `OfferList` and `OfferPanel` (also, the code that displays the input field) constitute a *view* that the user observes and interacts with. `Composer` is a *controller* that listens to the *view*'s events and modifies a *model*, i.e., `SearchBox` itself (Figure 44.1). | ||||
|  | ||||
| **NB**: to follow the letter of the paradigm, we must separate the *model*, which will be responsible only for the data, from `SearchBox` itself. We leave this exercise to the reader. | ||||
|  | ||||
| []() | ||||
| []() | ||||
|  | ||||
| If we choose other options for reducing interaction directions, we will get other MV* frameworks (such as Model-View-Viewmodel, Model-View-Presenter, etc.). All of them are ultimately based on the “Model” pattern. | ||||
|  | ||||
| @@ -91,9 +91,9 @@ Another ideological problem is organizing nested controllers. If there are subor | ||||
|  | ||||
| If we take a closer look at modern UI libraries that claim to employ MV* paradigms, we will learn they employ it quite loosely. Usually, only the main principle that a model defines UI and can only be modified through controllers is adopted. Nested components usually have their own models (in most cases, comprising a subset of the parent model enriched with the component's own state), and the global model contains only a limited number of fields. This approach is implemented in many modern UI frameworks, including those that claim they have nothing to do with MV* paradigms (React, for instance[ref Why did we build React?](https://legacy.reactjs.org/blog/2013/06/05/why-react.html)[ref Mattiazzi, R. How React and Redux brought back MVC and everyone loved it](https://rangle.io/blog/how-react-and-redux-brought-back-mvc-and-everyone-loved-it)). | ||||
|  | ||||
| All these problems of the MVC paradigm were highlighted by Martin Fowler in his “GUI Architectures” essay.[ref:fowler-gui-architectures GUI Architectures]() The proposed solution is the “Model-View-*Presenter*” framework, in which the controller entity is replaced with a *presenter*. The responsibility of the presenter is not only translating events, but preparing data for views as well. This allows for full separation of abstraction levels (a model now stores only semantic data while a presenter transforms it into low-level parameters that define UI look; the set of these parameters is called the “Application Model” or “Presentation Model” in Fowler's text). | ||||
| All these problems of the MVC paradigm were highlighted by Martin Fowler in his “GUI Architectures” essay.[ref:fowler-gui-architectures GUI Architectures]() The proposed solution is the “Model-View-*Presenter*” framework, in which the controller entity is replaced with a *presenter*. The responsibility of the presenter is not only translating events, but preparing data for views as well (Figure 44.2). This allows for full separation of abstraction levels (a model now stores only semantic data while a presenter transforms it into low-level parameters that define UI look; the set of these parameters is called the “Application Model” or “Presentation Model” in Fowler's text). | ||||
|  | ||||
| []() | ||||
| []() | ||||
|  | ||||
| Fowler's paradigm closely resembles the `Composer` concept we discussed in the previous chapter with one notable deviation. In MVP, a presenter is stateless (with possible exceptions of caches and closures) and it only deduces the data needed by views from the model data. If some low-level property needs to be manipulated, such as text color, the model needs to be extended in a manner that allows the presenter to calculate text color based on some high-level model data field. This concept significantly narrows the capability to replace subcomponents with alternate implementations. | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| ### Условные обозначения и терминология | ||||
| ### Условные обозначения и терминология | ||||
|  | ||||
| В мире разработки программного обеспечения существует множество различных парадигм разработки, адепты которых зачастую настроены весьма воинственно по отношению к адептам других парадигм. Поэтому при написании этой книги мы намеренно избегали слов «метод», «объект», «функция» и так далее, используя нейтральный термин «сущность», под которым понимается некоторая атомарная единица функциональности: класс, метод, объект, монада, прототип (нужное подчеркнуть). | ||||
|  | ||||
|   | ||||
| @@ -1,12 +1,12 @@ | ||||
| ### О паттернах проектирования в контексте API | ||||
|  | ||||
| Концепция «паттернов» в области разработки программного обеспечения была введена Кентом Бэком и Уордом Каннингемом в 1987 году[ref Software Design Pattern. History](https://en.wikipedia.org/wiki/Software_design_pattern#History), и популяризирована «бандой четырёх» (Эрих Гамма, Ричард Хелм, Ральф Джонсон и Джон Влиссидес) в их книге «Приёмы объектно-ориентированного проектирования. Паттерны проектирования», изданной в 1994 году[ref:{"short":"Gamma, E., Helm, R., Johnson, R., Vlissides, J. (1994)","extra":["Design Patterns","Elements of Reusable Object-Oriented Software"]}](isbn:9780321700698). Согласно общепринятому определению, паттерны программирования — «повторяемая архитектурная конструкция, представляющая собой решение проблемы проектирования в рамках некоторого часто возникающего контекста». | ||||
| Концепция «паттернов» в области разработки программного обеспечения была введена Кентом Бэком и Уордом Каннингемом в 1987 году и популяризирована «бандой четырёх» (Эрих Гамма, Ричард Хелм, Ральф Джонсон и Джон Влиссидес) в их книге «Приёмы объектно-ориентированного проектирования. Паттерны проектирования», изданной в 1994 году[ref:{"short":"Gamma, E., Helm, R., Johnson, R., Vlissides, J. (1994)","extra":["Design Patterns","Elements of Reusable Object-Oriented Software"]}](isbn:9780321700698). Согласно общепринятому определению, паттерны программирования — «повторяемая архитектурная конструкция, представляющая собой решение проблемы проектирования в рамках некоторого часто возникающего контекста». | ||||
|  | ||||
| Если мы говорим об API, особенно если конечным потребителем этих API является разработчик (интерфейсы фреймворков, операционных систем), классические паттерны проектирования вполне к ним применимы. И действительно, многие из описанных в предыдущем разделе примеров представляют собой применение того или иного паттерна. | ||||
|  | ||||
| Однако, если мы попытаемся обобщить этот подход на разработку API в целом, то увидим, что большинство типичных проблем дизайна API являются более высокоуровневыми и не сводятся к базовым паттернам разработки ПО. Скажем, проблемы кэширования ресурсов (и инвалидации кэша) или организация пагинации классиками не покрыты. | ||||
|  | ||||
| В рамках этого раздела мы попытаемся описать те задачи проектирования API, которые представляются нам наиболее важными. Мы не претендуем здесь на то, чтобы охватить *все* проблемы и тем более — все решения, и скорее фокусируемся на описании подходов к решению типовых задач с их достоинствами и недостатками. Мы понимаем, что читатель, знакомый с классическими трудами «банды четырёх», Гради Буча и Мартина Фаулера ожидает от раздела с названием «Паттерны API» большей системности и ширины охвата, и заранее просим у него прощения. | ||||
| В рамках этого раздела мы попытаемся описать те задачи проектирования API, которые представляются нам наиболее важными. Мы не претендуем здесь на то, чтобы охватить *все* проблемы и тем более — все решения, и скорее фокусируемся на описании подходов к решению типовых задач с их достоинствами и недостатками. Мы понимаем, что читатель, знакомый с классическими трудами «банды четырёх», Гради Буча[] и Мартина Фаулера ожидает от раздела с названием «Паттерны API» большей системности и ширины охвата, и заранее просим у него прощения. | ||||
|  | ||||
| **NB**: первый паттерн, о котором необходимо упомянуть — это API-first подход к разработке ПО, который мы описали [в соответствующей главе](#intro-api-first-approach). | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user