@@ -598,7 +598,7 @@ ul.references li p a.back-anchor { This book is distributed under the Creative Commons Attribution-NonCommercial 4.0 International licence. Source code available at github.com/twirl/The-API-Book - Share: facebook · twitter · linkedin · redditTable of ContentsIntroductionChapter 1. On the Structure of This BookChapter 2. The API DefinitionChapter 3. Overview of Existing API Development SolutionsChapter 4. API Quality CriteriaChapter 5. The API-First ApproachChapter 6. On Backward CompatibilityChapter 7. On VersioningChapter 8. Terms and Notation KeysSection I. The API DesignChapter 9. The API Contexts PyramidChapter 10. Defining an Application FieldChapter 11. Separating Abstraction LevelsChapter 12. Isolating Responsibility AreasChapter 13. Describing Final InterfacesChapter 14. Annex to Section I. Generic API ExampleSection II. The API PatternsChapter 15. On Design Patterns in the API ContextChapter 16. Authenticating Partners and Authorizing API CallsChapter 17. Synchronization StrategiesChapter 18. Eventual ConsistencyChapter 19. Asynchronicity and Time ManagementChapter 20. Lists and Accessing ThemChapter 21. Bidirectional Data Flows. Push and Poll ModelsChapter 22. Multiplexing Notifications. Asynchronous Event ProcessingChapter 23. Atomicity of Bulk ChangesChapter 24. Partial UpdatesChapter 25. Degradation and PredictabilitySection III. The Backward CompatibilityChapter 26. The Backward Compatibility Problem StatementChapter 27. On the Waterline of the IcebergChapter 28. Extending through AbstractingChapter 29. Strong Coupling and Related ProblemsChapter 30. Weak CouplingChapter 31. Interfaces as a Universal PatternChapter 32. The Serenity Notepad[Work in Progress] Section IV. The HTTP API & RESTChapter 33. On the HTTP API Concept and TerminologyChapter 34. The REST MythChapter 35. Components of an HTTP Request and Their SemanticsChapter 36. The HTTP API Advantages and DisadvantagesChapter 37. HTTP API Organization PrinciplesChapter 38. Working with HTTP API ErrorsChapter 39. Organizing the HTTP API Resources and OperationsChapter 40. Final Provisions and General Recommendations[Work in Progress] Section V. The SDK & UI LibrariesChapter 41. On the Content of This SectionChapter 42. The SDK: Problems and SolutionsChapter 43. The Code Generation PatternChapter 44. The UI ComponentsChapter 45. Decomposing UI ComponentsChapter 46. The MV* FrameworksChapter 47. The Backend-Driven UIChapter 48. Shared Resources and Asynchronous LocksChapter 49. Computed PropertiesChapter 50. ConclusionSection VI. The API ProductChapter 51. API as a ProductChapter 52. The API Business ModelsChapter 53. Developing a Product VisionChapter 54. Communicating with DevelopersChapter 55. Communicating with Business OwnersChapter 56. The API Services RangeChapter 57. The API Key Performance IndicatorsChapter 58. Identifying Users and Preventing FraudChapter 59. The Technical Means of Preventing ToS ViolationsChapter 60. Supporting customersChapter 61. The DocumentationChapter 62. The Testing EnvironmentChapter 63. Managing Expectations + Share: facebook · twitter · linkedin · redditTable of ContentsIntroductionChapter 1. On the Structure of This BookChapter 2. The API DefinitionChapter 3. Overview of Existing API Development SolutionsChapter 4. API Quality CriteriaChapter 5. The API-First ApproachChapter 6. On Backward CompatibilityChapter 7. On VersioningChapter 8. Terms and Notation KeysSection I. The API DesignChapter 9. The API Contexts PyramidChapter 10. Defining an Application FieldChapter 11. Separating Abstraction LevelsChapter 12. Isolating Responsibility AreasChapter 13. Describing Final InterfacesChapter 14. Annex to Section I. Generic API ExampleSection II. The API PatternsChapter 15. On Design Patterns in the API ContextChapter 16. Authenticating Partners and Authorizing API CallsChapter 17. Synchronization StrategiesChapter 18. Eventual ConsistencyChapter 19. Asynchronicity and Time ManagementChapter 20. Lists and Accessing ThemChapter 21. Bidirectional Data Flows. Push and Poll ModelsChapter 22. Multiplexing Notifications. Asynchronous Event ProcessingChapter 23. Atomicity of Bulk ChangesChapter 24. Partial UpdatesChapter 25. Degradation and PredictabilitySection III. The Backward CompatibilityChapter 26. The Backward Compatibility Problem StatementChapter 27. On the Waterline of the IcebergChapter 28. Extending through AbstractingChapter 29. Strong Coupling and Related ProblemsChapter 30. Weak CouplingChapter 31. Interfaces as a Universal PatternChapter 32. The Serenity Notepad[Work in Progress] Section IV. The HTTP API & RESTChapter 33. On the HTTP API Concept and TerminologyChapter 34. The REST MythChapter 35. Components of an HTTP Request and Their SemanticsChapter 36. Advantages and Disadvantages of HTTP APIsChapter 37. HTTP API Organization PrinciplesChapter 38. Working with HTTP API ErrorsChapter 39. Organizing the HTTP API Resources and OperationsChapter 40. Final Provisions and General Recommendations[Work in Progress] Section V. The SDK & UI LibrariesChapter 41. On the Content of This SectionChapter 42. The SDK: Problems and SolutionsChapter 43. The Code Generation PatternChapter 44. The UI ComponentsChapter 45. Decomposing UI ComponentsChapter 46. The MV* FrameworksChapter 47. The Backend-Driven UIChapter 48. Shared Resources and Asynchronous LocksChapter 49. Computed PropertiesChapter 50. ConclusionSection VI. The API ProductChapter 51. API as a ProductChapter 52. The API Business ModelsChapter 53. Developing a Product VisionChapter 54. Communicating with DevelopersChapter 55. Communicating with Business OwnersChapter 56. The API Services RangeChapter 57. The API Key Performance IndicatorsChapter 58. Identifying Users and Preventing FraudChapter 59. The Technical Means of Preventing ToS ViolationsChapter 60. Supporting customersChapter 61. The DocumentationChapter 62. The Testing EnvironmentChapter 63. Managing Expectations § @@ -3579,7 +3579,7 @@ PATCH /v1/orders/{id} A full example of an API implementing the naïve approach would look like this: // Partially rewrites the order: -// * resets delivery address +// * resets the delivery address // to the default values // * leaves the first beverage // intact @@ -3594,7 +3594,7 @@ PATCH /v1/orders/{id} // do nothing to the entity {}, // “Special” value #3: - // delete an entity + // delete the entity false ] } @@ -3620,6 +3620,42 @@ PATCH /v1/orders/{id} +The solution could be enhanced by introducing explicit control sequences instead of relying on “magical” values and adding meta settings for the operation (such as a field name filter as it's implemented in gRPC). Here's an example: +// Partially rewrites the order: +// * resets the delivery address +// to the default values +// * leaves the first beverage +// intact +// * removes the second beverage +PATCH /v1/orders/{id}?⮠ + // A meta filter: which fields + // are allowed to be modified + field_mask=delivery_address,items +{ + // “Special” value #1: + // reset the field + "delivery_address": { + // The `__` prefix is needed + // to avoid collisions + // with real field names + "__operation": "reset" + }, + "items": [ + // “Special” value #2: + // do nothing to the entity + { "__operation": "skip" }, + // “Special” value #3: + // delete the entity + { "__operation": "delete" } + ] +} + +While this approach may appear more robust, it doesn't fundamentally address the problems: + +“Magical” values are replaced with “magical” prefixes +The fragmentation of algorithms and the non-transitivity of operations persist. + +Given that the format becomes more complex and less intuitively understandable, we consider this enhancement dubious. A more consistent solution is to split an endpoint into several idempotent sub-endpoints, each having its own independent identifier and/or address (which is usually enough to ensure the transitivity of independent operations). This approach aligns well with the decomposition principle we discussed in the “Isolating Responsibility Areas” chapter. // Creates an order // comprising two beverages @@ -4539,7 +4575,7 @@ Content-Type: application/json 1. A URL A Uniform Resource Locator (URL) is an addressing unit in an HTTP API. Some evangelists of the technology even use the term “URL space” as a synonym for “The World Wide Web.” It is expected that a proper HTTP API should employ an addressing system that is as granular as the subject area itself; in other words, each entity that the API can manipulate should have its own URL. The URL format is governed by a separate standard developed by an independent body known as the Web Hypertext Application Technology Working Group (WHATWG). The concepts of URLs and Uniform Resource Names (URNs) together constitute a more general entity called Uniform Resource Identifiers (URIs). (The difference between the two is that a URL allows for locating a resource within the framework of some protocol whereas a URN is an “internal” entity name that does not provide information on how to find the resource.) -URLs are decomposed into sub-components, each of which is optional: +URLs can be decomposed into sub-components, each of which is optional. While the standard enumerates a number of legacy practices, such as passing logins and passwords in URLs or using non-UTF encodings, we will skip discussing those. Instead, we will focus on the following components that are relevant to the topic of HTTP API design: A scheme: a protocol to access the resource (in our case it is always https:) A host: a top-level address unit in the form of either a domain name or an IP address. A host might contain subdomains. @@ -4565,7 +4601,7 @@ Content-Type: application/json In HTTP requests, the scheme, host, and port are usually (but not always) omitted and presumed to be equal to the connection parameters. (Fielding actually names this arrangement one of the biggest flaws in the protocol design.) -NB: the standard also enumerates some legacy components such as logins and passwords in URLs or non-UTF encoding marks, which we consider irrelevant to the topic of API design. Additionally, the standard contains rules for serializing, normalizing, and comparing URLs, knowing which can be useful for an HTTP API developer. +Additionally, the standard contains rules for serializing, normalizing, and comparing URLs, knowing which can be useful for an HTTP API developer. 2. Headers Headers contain metadata associated with a request or a response. They might describe properties of entities being passed (e.g., Content-Length), provide additional information regarding a client or a server (e.g., User-Agent, Date, etc.) or simply contain additional fields that are not directly related to the request/response semantics (such as Authorization). The important feature of headers is the possibility to read them before the message body is fully transmitted. This allows for altering request or response handling depending on the headers, and it is perfectly fine to manipulate headers while proxying requests. Many network agents actually do this, i.e., add, remove, or modify headers while proxying requests. In particular, modern web browsers automatically add a number of technical headers, such as User-Agent, Origin, Accept-Language, Connection, Referer, Sec-Fetch-*, etc., and modern server software automatically adds or modifies such headers as X-Powered-By, Date, Content-Length, Content-Encoding, X-Forwarded-For, etc. @@ -4600,7 +4636,7 @@ Content-Type: application/json Returns a representation of a resource Yes Yes -No +Should not PUT @@ -4614,7 +4650,7 @@ Content-Type: application/json Deletes a resource No Yes -No +Should not POST @@ -4714,7 +4750,85 @@ X-ApiName-Partner-Id: <partner_id> Theoretically, it is possible to use kebab-case everywhere. However, most programming languages do not allow variable names and object fields in kebab-case, so working with such an API would be quite inconvenient. To wrap this up, the situation with casing is so spoiled and convoluted that there is no consistent solution to employ. In this book, we follow this rule: tokens are cased according to the common practice for the corresponding request component. If a token's position changes, the casing is changed as well. (However, we're far from recommending following this approach unconditionally. Our recommendation is rather to try to avoid increasing the entropy by choosing a solution that minimizes the probability of misunderstanding.) -NB: strictly speaking, JSON stands for “JavaScript Object Notation,” and in JavaScript, the default casing is camelCase. However, we dare to say that JSON ceased to be a format bound to JavaScript long ago and is now a universal format for organizing communication between agents written in different programming languages. Employing camel_case allows for easily moving a parameter from a query to a body, which is the most frequent case. Although the inverse solution (i.e., using camelCase in query parameter names) is also possible.Chapter 36. The HTTP API Advantages and DisadvantagesChapter 37. HTTP API Organization PrinciplesChapter 38. Working with HTTP API ErrorsChapter 39. Organizing the HTTP API Resources and OperationsChapter 40. Final Provisions and General Recommendations[Work in Progress] Section V. The SDK & UI LibrariesChapter 41. On the Content of This SectionChapter 42. The SDK: Problems and SolutionsChapter 43. The Code Generation PatternChapter 44. The UI ComponentsChapter 45. Decomposing UI ComponentsChapter 46. The MV* FrameworksChapter 47. The Backend-Driven UIChapter 48. Shared Resources and Asynchronous LocksChapter 49. Computed PropertiesChapter 50. ConclusionSection VI. The API ProductChapter 51. API as a Product +NB: strictly speaking, JSON stands for “JavaScript Object Notation,” and in JavaScript, the default casing is camelCase. However, we dare to say that JSON ceased to be a format bound to JavaScript long ago and is now a universal format for organizing communication between agents written in different programming languages. Employing camel_case allows for easily moving a parameter from a query to a body, which is the most frequent case. Although the inverse solution (i.e., using camelCase in query parameter names) is also possible.Chapter 36. Advantages and Disadvantages of HTTP APIs +After the three introductory chapters dedicated to clarifying terms and concepts (that's the price of the popularity of the technology) the reader might wonder why this dichotomy exists in the first place, i.e., why do some HTTP APIs rely on HTTP semantics while others reject it in favor of custom arrangements? For example, if we consider the JSON-RPC response format, we will quickly notice that it might be replaced with standard HTTP protocol functionality. Instead of this: +HTTP/1.1 200 OK + +{ + "jsonrpc": "2.0", + "id", + "error": { + "code": -32600, + "message": "Invalid request" + } +} + +the server could have simply responded with 400 Bad Request, passing the request identifier as a custom header like X-OurCoffeeAPI-Request-Id. Nevertheless, protocol designers decided to introduce their own custom format. +This situation (not only with JSON-RPC but with essentially every high-level protocol built on top of HTTP) has developed due to various reasons. Some of them are historical (such as the inability to use many HTTP protocol features in early implementations of the XMLHttpRequest functionality in web browsers). However, new RPC protocols relying on the bare minimum of HTTP capabilities continue to emerge today. +To answer this question, we must emphasize a very important distinction between application-level protocols (such as JSON-RPC in our case) and pure HTTP. A 400 BadRequest is a transparent status for every intermediary network agent but a JSON-RPC custom error is not. Firstly, only a JSON-RPC-enabled client can read it. Secondly, and more importantly, in JSON-RPC, the request status is not metadata. In pure HTTP, the details of the operation, such as the method, requested URL, execution status, and request / response headers are readable without the necessity to parse the entire body. In most higher-level protocols, including JSON-RPC, this is not the case: even a protocol-enabled client must read a body to retrieve that information. +How does an API developer benefit from the capability of reading request and response metadata? The modern client-server communication stack, as envisioned by Fielding, is multi-layered. We can enumerate a number of intermediary agents that process network requests and responses: + +Frameworks that developers use to write code +Programming language APIs that frameworks are built on, and operating system APIs that compilers / interpreters of these languages rely on +Intermediary proxy servers between a client and a server +Various abstractions used in server programming, including server frameworks, programming languages, and operating systems +Web server software that is typically placed in front of backend handlers +Additional modern microservice-oriented tools such as API gateways and proxies + +The main advantage that following the letter of the HTTP standard offers is the possibility to rely on intermediary agents, from client frameworks to API gateways, to read the request metadata and perform actions based on it. This includes regulating timeouts and retry policies, logging, proxying, and sharding requests, among other things, without the necessity to write additional code to achieve these functionalities. +If we try to formulate the main principle of designing HTTP APIs, it will be: you would rather design an API in a way that intermediary agents can read and interpret request and response metadata. +Unlike many alternative technologies, which are usually driven by a single large IT company (such as Facebook, Google, or Apache Software Foundation), tools for working with HTTP APIs are developed by many different companies and collaborations. As a result, instead of a single homogeneous but limited toolkit provided by a single vendor, we have a plethora of instruments that work with HTTP APIs. The most notable examples include: + +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. + +Of course, most of these instruments will work with APIs that utilize other paradigms. However, the ability to read HTTP metadata 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. +Additionally, let's emphasize that the HTTP API paradigm is currently the default choice for public APIs. Because of the aforementioned reasons, partners can integrate an HTTP API without significant obstacles, regardless of their technological stack. Moreover, the prevalence of the technology lowers the entry barrier and the requirements for the qualification of partners' engineers. +The main disadvantage of HTTP APIs is that you have to rely on intermediary agents, from client frameworks to API gateways, to read the request metadata and perform actions based on it. This includes regulating timeouts and retry policies, logging, proxying, and sharding requests, among other things, without your consent. Since HTTP-related specifications are complex and the concepts of REST can be challenging to comprehend, and software engineers do not always write perfect code, these intermediary agents (including partners' developers!) will sometimes interpret HTTP metadata incorrectly, especially when dealing with exotic and hard-to-implement standards. Usually, one of the stated reasons for developing new RPC frameworks is the desire to make working with the protocol simple and consistent, thereby reducing the likelihood of errors when writing integration code. +The Question of Performance +When discussing the advantages of alternative technologies such as GraphQL, gRPC, Apache Thrift, etc., the argument of lower performance of JSON-over-HTTP APIs is often presented. Specifically, the following issues with the technology are commonly mentioned: + +The verbosity of the JSON format: + +Mandatory field names in every object, even for an array of similar entities +The large proportion of technical symbols (quotes, braces, commas, etc.) and the necessity to escape them in string values + + +The common approach of returning a full resource representation on resource retrieval requests, even if the client only needs a subset of the fields +The lower performance of data serializing and deserializing operations +The need to introduce additional encoding, such as Base64, to handle binary data +Performance quirks of the HTTP protocol itself, particularly the inability to serve multiple simultaneous requests through one connection. + +Let's be honest: HTTP APIs do suffer from the listed problems. However, we can confidently say that the impact of these factors is often overestimated. The reason API vendors care little about HTTP API performance is that the actual overhead is not as significant as perceived. Specifically: + + +Regarding the verbosity of the format, it is important to note that these issues are mainly relevant when compressing algorithms are not utilized. Comparisons have shown that enabling compression algorithms such as gzip largely reduces the difference in sizes between JSON documents and alternative binary formats (and there are compression algorithms specifically designed for processing text data, such as brotli). + + +If necessary, API designers can customize the list of returned fields in HTTP APIs. It aligns well with both the letter and the spirit of the standard. However, as we already explained to the reader in the “Partial Updates” chapter, trying to minimize traffic by returning only subsets of data is rarely justified in well-designed APIs. + + +If standard JSON deserializers are used, the overhead compared to binary standards might indeed be significant. However, if this overhead is a real problem, it makes sense to consider alternative JSON serializers such as simdjson. Due to their low-level and highly optimized code, simdjson demonstrates impressive throughput which would be suitable for all APIs except some corner cases. + + +Generally speaking, the HTTP API paradigm implies that binary data (such as images or video files) is served through separate endpoints. Returning binary data in JSON is only necessary when a separate request for the data is a problem from the performance perspective. These situations are virtually non-existent in server-to-server interactions and/or if HTTP/2 or a higher protocol version is used. + + +The HTTP/1.1 protocol version is indeed a suboptimal solution if request multiplexing is needed. However, alternate approaches to tackling the problem usually rely on… HTTP/2. Of course, an HTTP API can also be served over HTTP/2. + + +Let us reiterate once more: JSON-over-HTTP APIs are indeed less performative than binary protocols. Nevertheless, we take the liberty to say that for a well-designed API in common subject areas switching to alternative protocols will generate quite a modest profit. +Advantages and Disadvantages of the JSON Format +It's not hard to notice that most of the claims regarding HTTP API performance are actually not about the HTTP protocol but the JSON format. There is no problem in developing an HTTP API that will utilize any binary format (including, for instance, Protocol Buffers). Then the difference between a Protobuf-over-HTTP API and a gRPC API would be just about using granular URLs, status codes, request/response headers, and the ensuing (in)ability to use integrated software tools out of the box. +However, on many occasions (including this book) developers prefer the textual JSON over binary Protobuf (Flatbuffers, Thrift, Avro, etc.) for a very simple reason: JSON is easy to read. First, it's a text format and doesn't require additional decoding. Second, it's self-descriptive, meaning that property names are included. Unlike Protobuf-encoded messages which are basically impossible to read without a .proto file, one can make a very good guess what a JSON document is about at a glance. Provided that request metadata in HTTP APIs is readable as well, we ultimately get a communication format that is easy to parse and understand with just our eyes. +Apart from being human-readable, JSON features another important advantage: it is strictly formal meaning it does not contain any constructs that can be interpreted differently in different architectures (with a possible exception of the sizes of numbers and strings), and the deserialization result is aligned very well with native data structures (i.e., indexed and associative arrays) of almost every programming language. From this point of view, we actually had no other choice when selecting a format for code samples in this book. +If you happen to design a less general API for a specific subject area, we still recommend the same approach for choosing a format: + +Estimate the overhead of preparing and introducing tools to decipher binary protocols versus the overhead of using not the most optimal data transfer protocols. +Make an assessment of what is more important to you: having a quality but restricted in its capabilities set of bundled software or having the possibility of using a broad range of tools that work with HTTP APIs, even though their quality is not that high. +Evaluate the cost of finding developers proficient with the format. +Chapter 37. HTTP API Organization PrinciplesChapter 38. Working with HTTP API ErrorsChapter 39. Organizing the HTTP API Resources and OperationsChapter 40. Final Provisions and General Recommendations[Work in Progress] Section V. The SDK & UI LibrariesChapter 41. On the Content of This SectionChapter 42. The SDK: Problems and SolutionsChapter 43. The Code Generation PatternChapter 44. The UI ComponentsChapter 45. Decomposing UI ComponentsChapter 46. The MV* FrameworksChapter 47. The Backend-Driven UIChapter 48. Shared Resources and Asynchronous LocksChapter 49. Computed PropertiesChapter 50. ConclusionSection VI. The API ProductChapter 51. API as a Product There are two important statements regarding APIs viewed as products. diff --git a/docs/API.en.pdf b/docs/API.en.pdf index 582c5ad..8da4422 100644 Binary files a/docs/API.en.pdf and b/docs/API.en.pdf differ diff --git a/docs/API.ru.epub b/docs/API.ru.epub index 29c4b5e..d27389c 100644 Binary files a/docs/API.ru.epub and b/docs/API.ru.epub differ diff --git a/docs/API.ru.html b/docs/API.ru.html index 2255687..4bce08b 100644 --- a/docs/API.ru.html +++ b/docs/API.ru.html @@ -577,7 +577,7 @@ ul.references li p a.back-anchor { Ссылка: - СодержаниеВведениеГлава 1. О структуре этой книгиГлава 2. Определение APIГлава 3. Обзор существующих решений в области разработки APIГлава 4. Критерии качества APIГлава 5. API-first подходГлава 6. Обратная совместимостьГлава 7. О версионированииГлава 8. Условные обозначения и терминологияРаздел I. Проектирование APIГлава 9. Пирамида контекстов APIГлава 10. Определение области примененияГлава 11. Разделение уровней абстракцииГлава 12. Разграничение областей ответственностиГлава 13. Описание конечных интерфейсовГлава 14. Приложение к разделу I. Модельный APIРаздел II. Паттерны дизайна APIГлава 15. О паттернах проектирования в контексте APIГлава 16. Аутентификация партнёров и авторизация вызовов APIГлава 17. Стратегии синхронизацииГлава 18. Слабая консистентностьГлава 19. Асинхронность и управление временемГлава 20. Списки и организация доступа к нимГлава 21. Двунаправленные потоки данных. Push и poll-моделиГлава 22. Мультиплексирование сообщений. Асинхронная обработка событийГлава 23. Атомарность массовых измененийГлава 24. Частичные обновленияГлава 25. Деградация и предсказуемостьРаздел III. Обратная совместимостьГлава 26. Постановка проблемы обратной совместимостиГлава 27. О ватерлинии айсбергаГлава 28. Расширение через абстрагированиеГлава 29. Сильная связность и сопутствующие проблемыГлава 30. Слабая связностьГлава 31. Интерфейсы как универсальный паттернГлава 32. Блокнот душевного покоя[В разработке] Раздел IV. HTTP API и RESTГлава 33. О концепции HTTP API и терминологииГлава 34. Мифология RESTГлава 35. Составляющие HTTP запросов и их семантикаГлава 36. Преимущества и недостатки HTTP APIГлава 37. Принципы организации HTTP APIГлава 38. Работа с ошибками в HTTP APIГлава 39. Организация URL ресурсов и операций над ними в HTTP APIГлава 40. Заключительные положения и общие рекомендации[В разработке] Раздел V. SDK и UIГлава 41. О содержании разделаГлава 42. SDK: проблемы и решенияГлава 43. КодогенерацияГлава 44. UI-компонентыГлава 45. Декомпозиция UI-компонентов. MV*-подходыГлава 46. MV*-фреймворкиГлава 47. Backend-Driven UIГлава 48. Разделяемые ресурсы и асинхронные блокировкиГлава 49. Вычисляемые свойстваГлава 50. В заключениеРаздел VI. API как продуктГлава 51. Продукт APIГлава 52. Бизнес-модели APIГлава 53. Формирование продуктового виденияГлава 54. Взаимодействие с разработчикамиГлава 55. Взаимодействие с бизнес-аудиториейГлава 56. Линейка сервисов APIГлава 57. Ключевые показатели эффективности APIГлава 58. Идентификация пользователей и борьба с фродомГлава 59. Технические способы борьбы с несанкционированным доступом к APIГлава 60. Поддержка пользователей APIГлава 61. ДокументацияГлава 62. Тестовая средаГлава 63. Управление ожиданиями + СодержаниеВведениеГлава 1. О структуре этой книгиГлава 2. Определение APIГлава 3. Обзор существующих решений в области разработки APIГлава 4. Критерии качества APIГлава 5. API-first подходГлава 6. Обратная совместимостьГлава 7. О версионированииГлава 8. Условные обозначения и терминологияРаздел I. Проектирование APIГлава 9. Пирамида контекстов APIГлава 10. Определение области примененияГлава 11. Разделение уровней абстракцииГлава 12. Разграничение областей ответственностиГлава 13. Описание конечных интерфейсовГлава 14. Приложение к разделу I. Модельный APIРаздел II. Паттерны дизайна APIГлава 15. О паттернах проектирования в контексте APIГлава 16. Аутентификация партнёров и авторизация вызовов APIГлава 17. Стратегии синхронизацииГлава 18. Слабая консистентностьГлава 19. Асинхронность и управление временемГлава 20. Списки и организация доступа к нимГлава 21. Двунаправленные потоки данных. Push и poll-моделиГлава 22. Мультиплексирование сообщений. Асинхронная обработка событийГлава 23. Атомарность массовых измененийГлава 24. Частичные обновленияГлава 25. Деградация и предсказуемостьРаздел III. Обратная совместимостьГлава 26. Постановка проблемы обратной совместимостиГлава 27. О ватерлинии айсбергаГлава 28. Расширение через абстрагированиеГлава 29. Сильная связность и сопутствующие проблемыГлава 30. Слабая связностьГлава 31. Интерфейсы как универсальный паттернГлава 32. Блокнот душевного покоя[В разработке] Раздел IV. HTTP API и RESTГлава 33. О концепции HTTP API и терминологииГлава 34. Мифология RESTГлава 35. Составляющие HTTP запросов и их семантикаГлава 36. Преимущества и недостатки HTTP APIГлава 37. Принципы организации HTTP APIГлава 38. Работа с ошибками в HTTP APIГлава 39. Организация URL ресурсов и операций над ними в HTTP APIГлава 40. Заключительные положения и общие рекомендации[В разработке] Раздел V. SDK и UIГлава 41. О содержании разделаГлава 42. SDK: проблемы и решенияГлава 43. КодогенерацияГлава 44. UI-компонентыГлава 45. Декомпозиция UI-компонентов. MV*-подходыГлава 46. MV*-фреймворкиГлава 47. Backend-Driven UIГлава 48. Разделяемые ресурсы и асинхронные блокировкиГлава 49. Вычисляемые свойстваГлава 50. В заключениеРаздел VI. API как продуктГлава 51. Продукт APIГлава 52. Бизнес-модели APIГлава 53. Формирование продуктового виденияГлава 54. Взаимодействие с разработчикамиГлава 55. Взаимодействие с бизнес-аудиториейГлава 56. Линейка сервисов APIГлава 57. Ключевые показатели эффективности APIГлава 58. Идентификация пользователей и борьба с фродомГлава 59. Технические способы борьбы с несанкционированным доступом к APIГлава 60. Поддержка пользователей APIГлава 61. ДокументацияГлава 62. Тестовая средаГлава 63. Управление ожиданиями @@ -598,7 +598,7 @@ ul.references li p a.back-anchor { Это произведение доступно по лицензии Creative Commons «Attribution-NonCommercial» («Атрибуция — Некоммерческое использование») 4.0 Всемирная. Исходный код доступен на github.com/twirl/The-API-Book - Поделиться: facebook · twitter · linkedin · redditСодержаниеВведениеГлава 1. О структуре этой книгиГлава 2. Определение APIГлава 3. Обзор существующих решений в области разработки APIГлава 4. Критерии качества APIГлава 5. API-first подходГлава 6. Обратная совместимостьГлава 7. О версионированииГлава 8. Условные обозначения и терминологияРаздел I. Проектирование APIГлава 9. Пирамида контекстов APIГлава 10. Определение области примененияГлава 11. Разделение уровней абстракцииГлава 12. Разграничение областей ответственностиГлава 13. Описание конечных интерфейсовГлава 14. Приложение к разделу I. Модельный APIРаздел II. Паттерны дизайна APIГлава 15. О паттернах проектирования в контексте APIГлава 16. Аутентификация партнёров и авторизация вызовов APIГлава 17. Стратегии синхронизацииГлава 18. Слабая консистентностьГлава 19. Асинхронность и управление временемГлава 20. Списки и организация доступа к нимГлава 21. Двунаправленные потоки данных. Push и poll-моделиГлава 22. Мультиплексирование сообщений. Асинхронная обработка событийГлава 23. Атомарность массовых измененийГлава 24. Частичные обновленияГлава 25. Деградация и предсказуемостьРаздел III. Обратная совместимостьГлава 26. Постановка проблемы обратной совместимостиГлава 27. О ватерлинии айсбергаГлава 28. Расширение через абстрагированиеГлава 29. Сильная связность и сопутствующие проблемыГлава 30. Слабая связностьГлава 31. Интерфейсы как универсальный паттернГлава 32. Блокнот душевного покоя[В разработке] Раздел IV. HTTP API и RESTГлава 33. О концепции HTTP API и терминологииГлава 34. Мифология RESTГлава 35. Составляющие HTTP запросов и их семантикаГлава 36. Преимущества и недостатки HTTP APIГлава 37. Принципы организации HTTP APIГлава 38. Работа с ошибками в HTTP APIГлава 39. Организация URL ресурсов и операций над ними в HTTP APIГлава 40. Заключительные положения и общие рекомендации[В разработке] Раздел V. SDK и UIГлава 41. О содержании разделаГлава 42. SDK: проблемы и решенияГлава 43. КодогенерацияГлава 44. UI-компонентыГлава 45. Декомпозиция UI-компонентов. MV*-подходыГлава 46. MV*-фреймворкиГлава 47. Backend-Driven UIГлава 48. Разделяемые ресурсы и асинхронные блокировкиГлава 49. Вычисляемые свойстваГлава 50. В заключениеРаздел VI. API как продуктГлава 51. Продукт APIГлава 52. Бизнес-модели APIГлава 53. Формирование продуктового виденияГлава 54. Взаимодействие с разработчикамиГлава 55. Взаимодействие с бизнес-аудиториейГлава 56. Линейка сервисов APIГлава 57. Ключевые показатели эффективности APIГлава 58. Идентификация пользователей и борьба с фродомГлава 59. Технические способы борьбы с несанкционированным доступом к APIГлава 60. Поддержка пользователей APIГлава 61. ДокументацияГлава 62. Тестовая средаГлава 63. Управление ожиданиями + Поделиться: facebook · twitter · linkedin · redditСодержаниеВведениеГлава 1. О структуре этой книгиГлава 2. Определение APIГлава 3. Обзор существующих решений в области разработки APIГлава 4. Критерии качества APIГлава 5. API-first подходГлава 6. Обратная совместимостьГлава 7. О версионированииГлава 8. Условные обозначения и терминологияРаздел I. Проектирование APIГлава 9. Пирамида контекстов APIГлава 10. Определение области примененияГлава 11. Разделение уровней абстракцииГлава 12. Разграничение областей ответственностиГлава 13. Описание конечных интерфейсовГлава 14. Приложение к разделу I. Модельный APIРаздел II. Паттерны дизайна APIГлава 15. О паттернах проектирования в контексте APIГлава 16. Аутентификация партнёров и авторизация вызовов APIГлава 17. Стратегии синхронизацииГлава 18. Слабая консистентностьГлава 19. Асинхронность и управление временемГлава 20. Списки и организация доступа к нимГлава 21. Двунаправленные потоки данных. Push и poll-моделиГлава 22. Мультиплексирование сообщений. Асинхронная обработка событийГлава 23. Атомарность массовых измененийГлава 24. Частичные обновленияГлава 25. Деградация и предсказуемостьРаздел III. Обратная совместимостьГлава 26. Постановка проблемы обратной совместимостиГлава 27. О ватерлинии айсбергаГлава 28. Расширение через абстрагированиеГлава 29. Сильная связность и сопутствующие проблемыГлава 30. Слабая связностьГлава 31. Интерфейсы как универсальный паттернГлава 32. Блокнот душевного покоя[В разработке] Раздел IV. HTTP API и RESTГлава 33. О концепции HTTP API и терминологииГлава 34. Мифология RESTГлава 35. Составляющие HTTP запросов и их семантикаГлава 36. Преимущества и недостатки HTTP APIГлава 37. Принципы организации HTTP APIГлава 38. Работа с ошибками в HTTP APIГлава 39. Организация URL ресурсов и операций над ними в HTTP APIГлава 40. Заключительные положения и общие рекомендации[В разработке] Раздел V. SDK и UIГлава 41. О содержании разделаГлава 42. SDK: проблемы и решенияГлава 43. КодогенерацияГлава 44. UI-компонентыГлава 45. Декомпозиция UI-компонентов. MV*-подходыГлава 46. MV*-фреймворкиГлава 47. Backend-Driven UIГлава 48. Разделяемые ресурсы и асинхронные блокировкиГлава 49. Вычисляемые свойстваГлава 50. В заключениеРаздел VI. API как продуктГлава 51. Продукт APIГлава 52. Бизнес-модели APIГлава 53. Формирование продуктового виденияГлава 54. Взаимодействие с разработчикамиГлава 55. Взаимодействие с бизнес-аудиториейГлава 56. Линейка сервисов APIГлава 57. Ключевые показатели эффективности APIГлава 58. Идентификация пользователей и борьба с фродомГлава 59. Технические способы борьбы с несанкционированным доступом к APIГлава 60. Поддержка пользователей APIГлава 61. ДокументацияГлава 62. Тестовая средаГлава 63. Управление ожиданиями § @@ -3604,6 +3604,42 @@ PATCH /v1/orders/{id} +Это решение можно улучшить путём ввода явных управляющих конструкций вместо «магических значений» и введением мета-опций операции (скажем, фильтра по именам полей, как это принято в gRPC), например, так: +// Частично перезаписывает заказ: +// * сбрасывает адрес доставки +// в значение по умолчанию +// * не изменяет первый напиток +// * удаляет второй напиток +PATCH /v1/orders/{id}?⮠ + // мета-фильтр: какие поля + // переопределяются + field_mask=delivery_address,items +{ + // Специальное значение №1: + // обнулить поле + "delivery_address": { + // Префикс `__` нужен, чтобы + // избежать коллизий + // с реальными именами полей + "__operation": "reset" + }, + "items": [ + // Специальное значение №2: + // не выполнять никаких + // операций с объектом + { "__operation": "skip" }, + // Специальное значение №3: + // удалить объект + { "__operation": "delete" } + ] +} + +Такой подход выглядит более надёжным, но в реальности мало что меняет в постановке проблемы: + +«магические значения» заменены «магическими» префиксами; +фрагментация алгоритмов и нетранзитивность операций сохраняется. + +При этом формат перестаёт быть простым и интуитивно понятным, что с нашей точки зрения делает такое улучшение спорным. Более консистентное решение: разделить эндпойнт на несколько идемпотентных суб-эндпойнтов, имеющих независимые идентификаторы и/или адреса (чего обычно достаточно для обеспечения транзитивности независимых операций). Этот подход также хорошо согласуется с принципом декомпозиции, который мы рассматривали в предыдущем главе «Разграничение областей ответственности». // Создаёт заказ из двух напитков POST /v1/orders/ @@ -4530,7 +4566,7 @@ Content-Type: application/json 1. URL URL — единица адресации в HTTP API (некоторые евангелисты технологии даже используют термин «пространство URL» как синоним для Мировой паутины). Предполагается, что HTTP API должен использовать систему адресов столь же гранулярную, как и предметная область; иными словами, у любых сущностей, которыми мы можем манипулировать независимо, должен быть свой URL. Формат URL регулируется отдельным стандартом, который развивает независимое сообщество Web Hypertext Application Technology Working Group (WHATWG). Считается, что концепция URL (вместе с понятием универсального имени ресурса, URN) составляет более общую сущность URI (универсальный идентификатор ресурса). (Разница между URL и URN заключается в том, что URL позволяет найти некоторый ресурс в рамках некоторого протокола доступа, в то время как URN — «внутреннее» имя объекта, которое само по себе никак не помогает получить к нему доступ.) -URL принято раскладывать на составляющие, каждая из которых опциональна: +URL принято раскладывать на составляющие, каждая из которых опциональна. В стандарте перечислены разнообразные исторические наслоения (например, передача логинов и паролей в URL или использование не-UTF кодировки), которые мы опустим. В рамках дизайна HTTP API нам интересны следующие компоненты: схема (scheme) — протокол обращения (в нашем случае всегда https:); хост (host; домен или IP-адрес) — самая крупная единица адресации; @@ -4560,7 +4596,7 @@ Content-Type: application/json В HTTP-запросах, как правило (но не обязательно) схема, хост и порт опускаются (и считаются совпадающими с параметрами соединения). (Это соглашение, кстати, Филдинг считает самой большой проблемой дизайна протокола.) -NB: помимо указанных компонентов в стандарте перечислены разнообразные исторические наслоения (например, передача логинов и паролей в URL или использование не-UTF кодировки), которые нам в рамках вопросов дизайна API неинтересны. Также стандарт содержит правила сериализации, нормализации и сравнения URL, которые в целом полезно знать разработчику HTTP API. +Также стандарт содержит правила сериализации, нормализации и сравнения URL, которые в целом полезно знать разработчику HTTP API. 2. Заголовки Заголовки — это метаинформация, привязанная к запросу или ответу. Она может описывать какие-то свойства передаваемых данных (например, Content-Length), дополнительные сведения о клиенте или сервере (User-Agent, Date), или просто содержать поля, не относящиеся непосредственно к смыслу запроса/ответа (например, Authorization). Важное свойство заголовков — это возможность считывать их до того, как получено тело сообщения. Таким образом, заголовки могут, во-первых, сами по себе влиять на обработку запроса или ответа, и ими можно относительно легко манипулировать при проксировании — и многие сетевые агенты действительно это делают, добавляя или модифицируя заголовки по своему усмотрению (в частности, современные веб-браузеры добавляют к запросам целую коллекцию заголовков: User-Agent, Origin, Accept-Language, Connection, Referer, Sec-Fetch-* и так далее, а современное ПО веб-серверов, в свою очередь, автоматически добавляет или модифицирует такие заголовки как X-Powered-By, Date, Content-Length, Content-Encoding, X-Forwarded-For). @@ -4595,7 +4631,7 @@ Content-Type: application/json Возвращает представление ресурса да да -нет +не рекомендуется PUT @@ -4609,7 +4645,7 @@ Content-Type: application/json Удаляет ресурс нет да -нет +не рекомендуется POST @@ -4628,7 +4664,7 @@ Content-Type: application/json Важное свойство модифицирующих идемпотентных глаголов — это то, что URL запроса является его ключом идемпотентности. PUT /url полностью перезаписывает ресурс, заданный своим URL (/url), и, таким образом, повтор запроса не изменяет ресурс. Аналогично, повторный вызов DELETE /url должен оставить систему в том же состоянии (ресурс /url удалён). Учитывая, что метод GET /url семантически должен вернуть представление целевого ресурса /url, то, если этот метод реализован, он должен возвращать консистентное предыдущим PUT / DELETE представление. Если ресурс был перезаписан через PUT /url, GET /url должен вернуть представление, соответствующее переданном в PUT /url телу (в случае JSON-over-HTTP API это, как правило, просто означает, что GET /url возвращает в точности тот же контент, чтобы передан в PUT /url, с точностью до значений полей по умолчанию). DELETE /url обязан удалить указанный ресурс — так, что GET /url должен вернуть 404 или 410. -Идемпотентность и симметричность методов GET / PUT / DELETE влечёт за собой запрет GET и DELETE иметь тело запроса (поскольку этому телу невозможно приписать никакой осмысленной роли). Однако (по-видимому в связи с тем, что многие разработчики попросту не знают семантику этих методов) распространённое ПО веб-серверов обычно разрешает этим методам иметь тело запроса и транслирует его дальше к коду обработки эндпойнта (использование этой практики мы решительно не рекомендуем). +Идемпотентность и симметричность методов GET / PUT / DELETE влечёт за собой нежелательность для GET и DELETE запросов иметь тело (поскольку этому телу невозможно приписать никакой осмысленной роли). Однако (по-видимому в связи с тем, что многие разработчики попросту не знают семантику этих методов) распространённое ПО веб-серверов обычно разрешает этим методам иметь тело запроса и транслирует его дальше к коду обработки эндпойнта (использование этой практики мы решительно не рекомендуем). Достаточно очевидным образом ответы на модифицирующие запросы не кэшируются (хотя при определённых условиях закэшированный ответ метода POST может быть использован при последующем GET-запросе) и, таким образом, повторный POST / PUT / DELETE / PATCH запрос обязательно будет доставлен до конечного сервера (ни один промежуточный агент не имеет права ответить из кэша). В случае GET-запроса это, вообще говоря, неверно — гарантией может служить только наличие в ответе директив кэширования no-store или no-cache. Один из самых частых антипаттернов разработки HTTP API — это использование HTTP-глаголов в нарушение их семантики: @@ -4638,7 +4674,7 @@ Content-Type: application/json некоторые агенты считают себя вправе переходить по таким ссылкам без явного волеизъявления пользователя или разработчика; например, социальные сети и мессенджеры выполняют такие вызовы для генерации оформления ссылки, если пользователь пытается ей поделиться. -Размещение неидемпотентных операций за идемпотентными методами PUT / DELETE. хотя промежуточные агенты редко автоматически повторяют модифицирующие запросы, тем не менее это легко может сделать используемый разработчиком клиента или сервера фреймворк. Обычно эта ошибка сочетается с наличием у DELETE-запроса тела (чтобы всё-таки отличать, что конкретно нужно перезаписать или удалить), что является само по себе проблемой, так как любой сетевой агент вправе это тело проигнорировать. +Размещение неидемпотентных операций за идемпотентными методами PUT / DELETE. Хотя промежуточные агенты редко автоматически повторяют модифицирующие запросы, тем не менее это легко может сделать используемый разработчиком клиента или сервера фреймворк. Обычно эта ошибка сочетается с наличием у DELETE-запроса тела (чтобы всё-таки отличать, что конкретно нужно перезаписать или удалить), что является само по себе проблемой, так как любой сетевой агент вправе это тело проигнорировать. Несоблюдение требования симметричности операций GET / PUT / DELETE: например, после выполнения DELETE /url операция GET /url продолжает возвращать какие-то данные или PUT /url ориентируется не на URL, а на данные внутри тела запроса для определения сущности, над которой выполняется операция, и, таким образом, GET /url никак не может вернуть представление объекта, только что переданного в PUT /url. @@ -4715,8 +4751,86 @@ X-ApiName-Partner-Id: <partner_id> Чисто теоретически возможно использование kebab-case во всех случаях, но в большинстве языков программирования имена переменных и полей объектов в kebab-case недопустимы, что приведёт к неудобству работы с таким API. -Короче говоря, ситуация с кейсингом настолько плоха и запутанна, что консистентного и удобного решения попросту нет. В этой книге мы придерживаемся следующего правила: токены даются в том кейсинге, который является общепринятым для той секции запроса, в которой находится токен; если положение токена меняется, то меняется и кейсинг. (Мы далеки от того, чтобы рекомендовать этот подход всюду; наша общая рекомендация, скорее — не умножать энтропию и пытаться минимизировать такого рода коллизии.) -NB: вообще говоря, JSON исходно — это JavaScript Object Notation, а в языке JavaScript кейсинг по умолчанию - camelCase. Мы, тем не менее, позволим себе утверждать, что JSON давно перестал быть форматом данных, привязанным к JavaScript, и в настоящее время используется для организации взаимодействия агентов, реализованных на любых языках программирования. Использование snake_case, по крайней мере, позволяет легко перебрасывать параметр из query в тело и обратно, что, обычно, является наиболее частотным кейсом при разработке HTTP API. Впрочем, обратный вариант (использование camelCase в именах query-параметров) тоже допустим.Глава 36. Преимущества и недостатки HTTP APIГлава 37. Принципы организации HTTP APIГлава 38. Работа с ошибками в HTTP APIГлава 39. Организация URL ресурсов и операций над ними в HTTP APIГлава 40. Заключительные положения и общие рекомендации[В разработке] Раздел V. SDK и UIГлава 41. О содержании разделаГлава 42. SDK: проблемы и решенияГлава 43. КодогенерацияГлава 44. UI-компонентыГлава 45. Декомпозиция UI-компонентов. MV*-подходыГлава 46. MV*-фреймворкиГлава 47. Backend-Driven UIГлава 48. Разделяемые ресурсы и асинхронные блокировкиГлава 49. Вычисляемые свойстваГлава 50. В заключениеРаздел VI. API как продуктГлава 51. Продукт API +Короче говоря, ситуация с кейсингом настолько плоха и запутана, что консистентного и удобного решения попросту нет. В этой книге мы придерживаемся следующего правила: токены даются в том кейсинге, который является общепринятым для той секции запроса, в которой находится токен; если положение токена меняется, то меняется и кейсинг. (Мы далеки от того, чтобы рекомендовать этот подход всюду; наша общая рекомендация, скорее — не умножать энтропию и пытаться минимизировать такого рода коллизии.) +NB: вообще говоря, JSON исходно — это JavaScript Object Notation, а в языке JavaScript кейсинг по умолчанию - camelCase. Мы, тем не менее, позволим себе утверждать, что JSON давно перестал быть форматом данных, привязанным к JavaScript, и в настоящее время используется для организации взаимодействия агентов, реализованных на любых языках программирования. Использование snake_case, по крайней мере, позволяет легко перебрасывать параметр из query в тело и обратно, что, обычно, является наиболее частотным кейсом при разработке HTTP API. Впрочем, обратный вариант (использование camelCase в именах query-параметров) тоже допустим.Глава 36. Преимущества и недостатки HTTP API +После трёх вступительных глав с прояснением основных терминов и понятий (такова, увы, цена популярности технологии) у читателя может возникнуть резонный вопрос — а почему вообще существует такая дихотомия: какие-то API полагаются на стандартную семантику HTTP, а какие-то полностью от неё отказываются в пользу новоизобретённых стандартов. Например, если мы посмотрим на формат ответа в JSON-RPC, то мы обнаружим, что он легко мог бы быть заменён на стандартные средства протокола HTTP. Вместо +HTTP/1.1 200 OK + +{ + "jsonrpc": "2.0", + "id", + "error": { + "code": -32600, + "message": "Invalid request" + } +} + +сервер мог бы ответить просто 400 Bad Request (с передачей идентификатора запроса, ну скажем, в заголовке X-OurCoffeeAPI-Request-Id). Тем не менее, разработчики протокола посчитали нужным разработать свой собственный формат. +Такая ситуация (не только конкретно с JSON-RPC, а почти со всеми высокоуровневыми протоколами поверх HTTP) сложилась по множеству причин, включая разнообразные исторические (например, невозможность использовать многие возможности HTTP из ранних реализаций XMLHttpRequest в браузерах). Однако, новые варианты RPC-протоколов, использующих абсолютный минимум возможностей HTTP, продолжают появляться и сегодня. +Чтобы разрешить этот парадокс, обратим внимание на одно принципиальное различие между использованием протоколов уровня приложения (как в нашем примере с JSON-RPC) и чистого HTTP: если ошибка 400 Bad Request является прозрачной для практически любого сетевого агента, то ошибка в собственном формате JSON-RPC таковой не является — во-первых, потому что понять её может только агент с поддержкой JSON-RPC, а, во-вторых, что более важно, в JSON-RPC статус запроса не является метаинформацией. Протокол HTTP позволяет прочитать такие детали, как метод и URL запроса, статус операции, заголовки запроса и ответа, не читая тело запроса целиком. Для большинства протоколов более высокого уровня, включая JSON-RPC, это не так: даже если агент и обладает поддержкой протокола, ему необходимо прочитать и разобрать тело ответа. +Каким образом эта самая возможность читать метаданные нам может быть полезна? Современный стек взаимодействия между клиентом и сервером является (как и предсказывал Филдинг) многослойным. Мы можем выделить множество агентов разного уровня, которые, так или иначе, обрабатывают сетевые запросы и ответы: + +разработчик пишет код поверх какого-то фреймворка, который отправляет запросы; +фреймворк базируется на API языка программирования, компилятор или интерпретатор которого, в свою очередь, полагается на API операционной системы; +запрос доходит до сервера, возможно, через промежуточные HTTP-прокси; +сервер, в свою очередь, тоже представляет собой несколько слоёв абстракции в виде фреймворка, языка программирования и ОС; +перед конечным сервером, как правило, находится веб-сервер, проксирующий запрос, а зачастую и не один; +в современных облачных архитектурах HTTP-запрос, прежде чем дойти до конечного обработчика, пройдёт через несколько абстракций в виде прокси и гейтвеев. + +Главное преимущество, которое предоставляет следование букве стандарта HTTP — возможность положиться на то, что промежуточные агенты, от клиентских фреймворков до API-гейтвеев, умеют читать метаданные запроса и выполнять какие-то действия с их использованием — настраивать политику перезапросов и таймауты, логировать, кэшировать, шардировать, проксировать и так далее — без необходимости писать какой-то дополнительный код. +Если попытаться сформулировать главный принцип разработки HTTP API, то мы получим примерно следующее: лучше бы ты разрабатывал API так, чтобы промежуточные агенты могли читать и интерпретировать мета-информацию запроса и ответа. +В отличие от большинства альтернативных технологий, основной импульс развития которых исходит от какой-то одной крупной IT-компании (Facebook, Google, Apache Software Foundation), инструменты для HTTP API разрабатываются множеством различных компаний и коллабораций. Соответственно, вместо гомогенного, но ограниченного в возможностях фреймворка, для HTTP API мы имеем множество самых разных инструментов, таких как: + +различные прокси и API-гейтвеи (nginx, Envoy); +различные форматы описания спецификаций (в первую очередь, OpenAPI) и связанные инструменты для работы со спецификациями (Redoc, Swagger UI) и кодогенерации; +ПО для разработчиков, позволяющее удобным образом разрабатывать и отлаживать клиенты API (Postman, Insomnia) и так далее. + +Конечно, большинство этих инструментов применимы и для работы с API, реализующими альтернативные парадигмы. Однако именно способность промежуточных агентов считывать метаданные HTTP запросов позволяет легко строить сложные конвейеры типа экспортировать access-логи nginx в Prometheus и из коробки получить удобные мониторинги статус-кодов ответов в Grafana. +Отдельно заметим что HTTP API является на сегодняшний день выбором по умолчанию при разработке публичных API. В силу озвученных выше причин, как бы ни был устроен технологический стек партнёра, интегрироваться с HTTP API он сможет без особых усилий. При этом распространённость технологии понижает и порог входа, и требования к квалификации инженеров партёра. +Главным недостатком HTTP API является то, что промежуточные агенты, от клиентских фреймворков до API-гейтвеев, умеют читать метаданные запроса и выполнять какие-то действия с их использованием — настраивать политику перезапросов и таймауты, логировать, кэшировать, шардировать, проксировать и так далее — даже если вы их об этом не просили. Более того, так как стандарты HTTP являются сложными, концепция REST — непонятной, а разработчики программного обеспечения — неидеальными, то промежуточные агенты (и разработчики партнёра!) могут трактовать метаданные запроса неправильно. Особенно это касается каких-то экзотических и сложных в имплементации стандартов. Как правило, одной из причин разработки новых RPC-фреймворков декларируется стремление обеспечить простоту и консистентность работы с протоколом, чтобы таким образом уменьшить поле для потенциальных ошибок в реализации интеграции с API. +Вопросы производительности +В пользу многих современных альтернатив HTTP API — таких как GraphQL, gRPC, Apache Thrift — часто приводят аргумент о низкой производительности JSON-over-HTTP API по сравнению с рассматриваемой технологией; конкретнее, называются следующие проблемы: + +Избыточность формата: + +в JSON необходимо всякий раз передавать имена всех полей, даже если передаётся массив из большого количества одинаковых объектов; +большое количество технических символов — кавычек, скобок, запятых — и необходимость экранировать служебные символы в строках. + + +HTTP API в ответ на запрос к ресурсу возвращает представление ресурса целиком, хотя клиенту могут быть интересны только отдельные поля. +Низкая производительность операций сериализации и десериализации данных. +Передача бинарных данных требует дополнительного кодирования, например, через Base64. +Низкая производительность самого протокола HTTP (в частности, невозможность мультиплексирования нескольких запросов и ответов по одному соединению). + +Будем здесь честны: большинство существующих имплементаций HTTP API действительно страдают от указанных проблем. Тем не менее, мы берём на себя смелость заявить, что все эти проблемы большей частью надуманы, и их решению не уделяют большого внимания потому, что указанные накладные расходы не являются сколько-нибудь заметными для большинства вендоров API. В частности: + + +Если мы говорим об избыточности формата, то необходимо сделать важную оговорку: всё вышесказанное верно, если мы не применяем сжатие. Сравнения показывают, что использование gzip практически нивелирует разницу в размере JSON документов относительно альтернативных бинарных форматов (а есть ещё и специально предназначенные для текстовых данных архиваторы, например, brotli). + + +Вообще говоря, если такая нужда появляется, то и в рамках HTTP API вполне можно регулировать список возвращаемых полей ответа, это вполне соответствует духу и букве стандарта. Однако, мы должны заметить, что экономия трафика на возврате частичных состояний (которую мы рассматривали подробно в главе «Частичные обновления») очень редко бывает оправдана. + + +Если использовать стандартные десериализаторы JSON, разница по сравнению с бинарными форматами может оказаться действительно очень большой. Если, однако, эти накладные расходы являются проблемой, стоит обратиться к альтернативным десериализаторам — в частности, simdjson. Благодаря оптимизированному низкоуровневому коду simdjson показывает отличную производительность, которой может не хватить только совсем уж экзотическим API. + + +Вообще говоря, парадигма HTTP API подразумевает, что для бинарных данных (такие как изображения или видеофайлы) предоставляются отдельные эндпойнты. Передача бинарных данных в теле JSON-ответа необходима только в случаях, когда отдельный запрос за ними представляет собой проблему с точки зрения производительности. Такой проблемы фактически не существует в server-2-server взаимодействии и в протоколе HTTP 2.0 и выше. + + +Протокол HTTP 1.1 действительно неидеален с точки зрения мультиплексирования запросов. Однако альтернативные парадигмы организации API для решения этой проблемы опираются… на HTTP версии 2.0. Разумеется, и HTTP API можно построить поверх версии 2.0. + + +На всякий случай ещё раз уточним: JSON-over-HTTP API действительно проигрывает с точки зрения производительности современным бинарным протоколам. Мы, однако, берём на себя смелость утверждать, что производительность хорошо спроектированного и оптимизированного HTTP API достаточна для абсолютного большинства предметных областей, и реальный выигрыш от перехода на альтернативные протоколы окажется незначителен. +Преимущества и недостатки формата JSON +Как нетрудно заметить, большинство претензий, предъявляемых к концепции HTTP API, относятся вовсе не к HTTP, а к использованию формата JSON. В самом деле, ничто не мешает разработать API, которое будет использовать любой бинарный формат вместо JSON (включая те же Protocol Buffers), и тогда разница между Protobuf-over-HTTP API и gRPC сведётся только к (не)использованию подробных URL, статус-кодов и заголовков запросов и ответов (и вытекающей отсюда (не)возможности использовать то или иное стандартное программное обеспечение «из коробки»). +Однако, во многих случаях (включая настоящую книгу) разработчики предпочитают текстовый JSON бинарным Protobuf (Flatbuffers, Thrift, Avro и т.д.) по очень простой причине: JSON очень легко и удобно читать. Во-первых, он текстовый и не требует дополнительной расшифровки; во-вторых, имена полей включены в сам файл. Если сообщение в формате protobuf невозможно прочитать без .proto-файла, то по JSON-документу почти всегда можно попытаться понять, что за данные в нём описаны. В совокупности с тем, что при разработке HTTP API мы также стараемся следовать стандартной семантике всей остальной обвязки, в итоге мы получаем API, запросы и ответы к которому (по крайней мере в теории) удобно читаются и интуитивно понятны. +Помимо человекочитаемости у JSON есть ещё одно важное преимущество: он максимально формален. В нём нет никаких конструкций, которые могут быть по-разном истолкованы в разных архитектурах (с точностью до ограничений на длины чисел и строк), и при этом он удобно ложится в нативные структуры данных (индексные и ассоциативные массивы) почти любого языка программирования. С этой точки зрения у нас фактически не было никакого другого выбора, какой ещё формат данных мы могли бы использовать при написании примеров кода для этой книги. +В случае же разработки API менее общего назначения, мы рекомендуем подходить к выбору формата по тому же принципу: + +взвесить накладные расходы на подготовку и внедрение инструментов чтения бинарных форматов и расшифровки бинарных протоколов против накладных расходов на неоптимальную передачу данных; +оценить, хватит ли вам качественного, но ограниченного в возможностях набора программного обеспечения, идущего в комплекте с альтернативным форматом, или вам важнее возможность использовать широкий спектр инструментов, умеющих работать с HTTP API, пусть они и не всегда высокого качества; +прикинуть, насколько дорого вам обойдутся разработчики, способные работать с этим форматом. +Глава 37. Принципы организации HTTP APIГлава 38. Работа с ошибками в HTTP APIГлава 39. Организация URL ресурсов и операций над ними в HTTP APIГлава 40. Заключительные положения и общие рекомендации[В разработке] Раздел V. SDK и UIГлава 41. О содержании разделаГлава 42. SDK: проблемы и решенияГлава 43. КодогенерацияГлава 44. UI-компонентыГлава 45. Декомпозиция UI-компонентов. MV*-подходыГлава 46. MV*-фреймворкиГлава 47. Backend-Driven UIГлава 48. Разделяемые ресурсы и асинхронные блокировкиГлава 49. Вычисляемые свойстваГлава 50. В заключениеРаздел VI. API как продуктГлава 51. Продукт API Когда мы говорим об API как о продукте, необходимо чётко зафиксировать два важных тезиса. diff --git a/docs/API.ru.pdf b/docs/API.ru.pdf index c14798e..e08a7bc 100644 Binary files a/docs/API.ru.pdf and b/docs/API.ru.pdf differ diff --git a/docs/index.html b/docs/index.html index 7ea6520..3641575 100644 --- a/docs/index.html +++ b/docs/index.html @@ -112,7 +112,7 @@ Chapter 33. On the HTTP API Concept and Terminology Chapter 34. The REST Myth Chapter 35. Components of an HTTP Request and Their Semantics -Chapter 36. The HTTP API Advantages and Disadvantages +Chapter 36. Advantages and Disadvantages of HTTP APIs Chapter 37. HTTP API Organization Principles Chapter 38. Working with HTTP API Errors Chapter 39. Organizing the HTTP API Resources and Operations diff --git a/docs/index.ru.html b/docs/index.ru.html index 47703a8..5cff70c 100644 --- a/docs/index.ru.html +++ b/docs/index.ru.html @@ -112,7 +112,7 @@ Глава 33. О концепции HTTP API и терминологии Глава 34. Мифология REST Глава 35. Составляющие HTTP запросов и их семантика -Глава 36. Преимущества и недостатки HTTP API +Глава 36. Преимущества и недостатки HTTP API Глава 37. Принципы организации HTTP API Глава 38. Работа с ошибками в HTTP API Глава 39. Организация URL ресурсов и операций над ними в HTTP API
@@ -598,7 +598,7 @@ ul.references li p a.back-anchor { Это произведение доступно по лицензии Creative Commons «Attribution-NonCommercial» («Атрибуция — Некоммерческое использование») 4.0 Всемирная. Исходный код доступен на github.com/twirl/The-API-Book - Поделиться: facebook · twitter · linkedin · redditСодержаниеВведениеГлава 1. О структуре этой книгиГлава 2. Определение APIГлава 3. Обзор существующих решений в области разработки APIГлава 4. Критерии качества APIГлава 5. API-first подходГлава 6. Обратная совместимостьГлава 7. О версионированииГлава 8. Условные обозначения и терминологияРаздел I. Проектирование APIГлава 9. Пирамида контекстов APIГлава 10. Определение области примененияГлава 11. Разделение уровней абстракцииГлава 12. Разграничение областей ответственностиГлава 13. Описание конечных интерфейсовГлава 14. Приложение к разделу I. Модельный APIРаздел II. Паттерны дизайна APIГлава 15. О паттернах проектирования в контексте APIГлава 16. Аутентификация партнёров и авторизация вызовов APIГлава 17. Стратегии синхронизацииГлава 18. Слабая консистентностьГлава 19. Асинхронность и управление временемГлава 20. Списки и организация доступа к нимГлава 21. Двунаправленные потоки данных. Push и poll-моделиГлава 22. Мультиплексирование сообщений. Асинхронная обработка событийГлава 23. Атомарность массовых измененийГлава 24. Частичные обновленияГлава 25. Деградация и предсказуемостьРаздел III. Обратная совместимостьГлава 26. Постановка проблемы обратной совместимостиГлава 27. О ватерлинии айсбергаГлава 28. Расширение через абстрагированиеГлава 29. Сильная связность и сопутствующие проблемыГлава 30. Слабая связностьГлава 31. Интерфейсы как универсальный паттернГлава 32. Блокнот душевного покоя[В разработке] Раздел IV. HTTP API и RESTГлава 33. О концепции HTTP API и терминологииГлава 34. Мифология RESTГлава 35. Составляющие HTTP запросов и их семантикаГлава 36. Преимущества и недостатки HTTP APIГлава 37. Принципы организации HTTP APIГлава 38. Работа с ошибками в HTTP APIГлава 39. Организация URL ресурсов и операций над ними в HTTP APIГлава 40. Заключительные положения и общие рекомендации[В разработке] Раздел V. SDK и UIГлава 41. О содержании разделаГлава 42. SDK: проблемы и решенияГлава 43. КодогенерацияГлава 44. UI-компонентыГлава 45. Декомпозиция UI-компонентов. MV*-подходыГлава 46. MV*-фреймворкиГлава 47. Backend-Driven UIГлава 48. Разделяемые ресурсы и асинхронные блокировкиГлава 49. Вычисляемые свойстваГлава 50. В заключениеРаздел VI. API как продуктГлава 51. Продукт APIГлава 52. Бизнес-модели APIГлава 53. Формирование продуктового виденияГлава 54. Взаимодействие с разработчикамиГлава 55. Взаимодействие с бизнес-аудиториейГлава 56. Линейка сервисов APIГлава 57. Ключевые показатели эффективности APIГлава 58. Идентификация пользователей и борьба с фродомГлава 59. Технические способы борьбы с несанкционированным доступом к APIГлава 60. Поддержка пользователей APIГлава 61. ДокументацияГлава 62. Тестовая средаГлава 63. Управление ожиданиями + Поделиться: facebook · twitter · linkedin · redditСодержаниеВведениеГлава 1. О структуре этой книгиГлава 2. Определение APIГлава 3. Обзор существующих решений в области разработки APIГлава 4. Критерии качества APIГлава 5. API-first подходГлава 6. Обратная совместимостьГлава 7. О версионированииГлава 8. Условные обозначения и терминологияРаздел I. Проектирование APIГлава 9. Пирамида контекстов APIГлава 10. Определение области примененияГлава 11. Разделение уровней абстракцииГлава 12. Разграничение областей ответственностиГлава 13. Описание конечных интерфейсовГлава 14. Приложение к разделу I. Модельный APIРаздел II. Паттерны дизайна APIГлава 15. О паттернах проектирования в контексте APIГлава 16. Аутентификация партнёров и авторизация вызовов APIГлава 17. Стратегии синхронизацииГлава 18. Слабая консистентностьГлава 19. Асинхронность и управление временемГлава 20. Списки и организация доступа к нимГлава 21. Двунаправленные потоки данных. Push и poll-моделиГлава 22. Мультиплексирование сообщений. Асинхронная обработка событийГлава 23. Атомарность массовых измененийГлава 24. Частичные обновленияГлава 25. Деградация и предсказуемостьРаздел III. Обратная совместимостьГлава 26. Постановка проблемы обратной совместимостиГлава 27. О ватерлинии айсбергаГлава 28. Расширение через абстрагированиеГлава 29. Сильная связность и сопутствующие проблемыГлава 30. Слабая связностьГлава 31. Интерфейсы как универсальный паттернГлава 32. Блокнот душевного покоя[В разработке] Раздел IV. HTTP API и RESTГлава 33. О концепции HTTP API и терминологииГлава 34. Мифология RESTГлава 35. Составляющие HTTP запросов и их семантикаГлава 36. Преимущества и недостатки HTTP APIГлава 37. Принципы организации HTTP APIГлава 38. Работа с ошибками в HTTP APIГлава 39. Организация URL ресурсов и операций над ними в HTTP APIГлава 40. Заключительные положения и общие рекомендации[В разработке] Раздел V. SDK и UIГлава 41. О содержании разделаГлава 42. SDK: проблемы и решенияГлава 43. КодогенерацияГлава 44. UI-компонентыГлава 45. Декомпозиция UI-компонентов. MV*-подходыГлава 46. MV*-фреймворкиГлава 47. Backend-Driven UIГлава 48. Разделяемые ресурсы и асинхронные блокировкиГлава 49. Вычисляемые свойстваГлава 50. В заключениеРаздел VI. API как продуктГлава 51. Продукт APIГлава 52. Бизнес-модели APIГлава 53. Формирование продуктового виденияГлава 54. Взаимодействие с разработчикамиГлава 55. Взаимодействие с бизнес-аудиториейГлава 56. Линейка сервисов APIГлава 57. Ключевые показатели эффективности APIГлава 58. Идентификация пользователей и борьба с фродомГлава 59. Технические способы борьбы с несанкционированным доступом к APIГлава 60. Поддержка пользователей APIГлава 61. ДокументацияГлава 62. Тестовая средаГлава 63. Управление ожиданиями § @@ -3604,6 +3604,42 @@ PATCH /v1/orders/{id} +Это решение можно улучшить путём ввода явных управляющих конструкций вместо «магических значений» и введением мета-опций операции (скажем, фильтра по именам полей, как это принято в gRPC), например, так: +// Частично перезаписывает заказ: +// * сбрасывает адрес доставки +// в значение по умолчанию +// * не изменяет первый напиток +// * удаляет второй напиток +PATCH /v1/orders/{id}?⮠ + // мета-фильтр: какие поля + // переопределяются + field_mask=delivery_address,items +{ + // Специальное значение №1: + // обнулить поле + "delivery_address": { + // Префикс `__` нужен, чтобы + // избежать коллизий + // с реальными именами полей + "__operation": "reset" + }, + "items": [ + // Специальное значение №2: + // не выполнять никаких + // операций с объектом + { "__operation": "skip" }, + // Специальное значение №3: + // удалить объект + { "__operation": "delete" } + ] +} + +Такой подход выглядит более надёжным, но в реальности мало что меняет в постановке проблемы: + +«магические значения» заменены «магическими» префиксами; +фрагментация алгоритмов и нетранзитивность операций сохраняется. + +При этом формат перестаёт быть простым и интуитивно понятным, что с нашей точки зрения делает такое улучшение спорным. Более консистентное решение: разделить эндпойнт на несколько идемпотентных суб-эндпойнтов, имеющих независимые идентификаторы и/или адреса (чего обычно достаточно для обеспечения транзитивности независимых операций). Этот подход также хорошо согласуется с принципом декомпозиции, который мы рассматривали в предыдущем главе «Разграничение областей ответственности». // Создаёт заказ из двух напитков POST /v1/orders/ @@ -4530,7 +4566,7 @@ Content-Type: application/json 1. URL URL — единица адресации в HTTP API (некоторые евангелисты технологии даже используют термин «пространство URL» как синоним для Мировой паутины). Предполагается, что HTTP API должен использовать систему адресов столь же гранулярную, как и предметная область; иными словами, у любых сущностей, которыми мы можем манипулировать независимо, должен быть свой URL. Формат URL регулируется отдельным стандартом, который развивает независимое сообщество Web Hypertext Application Technology Working Group (WHATWG). Считается, что концепция URL (вместе с понятием универсального имени ресурса, URN) составляет более общую сущность URI (универсальный идентификатор ресурса). (Разница между URL и URN заключается в том, что URL позволяет найти некоторый ресурс в рамках некоторого протокола доступа, в то время как URN — «внутреннее» имя объекта, которое само по себе никак не помогает получить к нему доступ.) -URL принято раскладывать на составляющие, каждая из которых опциональна: +URL принято раскладывать на составляющие, каждая из которых опциональна. В стандарте перечислены разнообразные исторические наслоения (например, передача логинов и паролей в URL или использование не-UTF кодировки), которые мы опустим. В рамках дизайна HTTP API нам интересны следующие компоненты: схема (scheme) — протокол обращения (в нашем случае всегда https:); хост (host; домен или IP-адрес) — самая крупная единица адресации; @@ -4560,7 +4596,7 @@ Content-Type: application/json В HTTP-запросах, как правило (но не обязательно) схема, хост и порт опускаются (и считаются совпадающими с параметрами соединения). (Это соглашение, кстати, Филдинг считает самой большой проблемой дизайна протокола.) -NB: помимо указанных компонентов в стандарте перечислены разнообразные исторические наслоения (например, передача логинов и паролей в URL или использование не-UTF кодировки), которые нам в рамках вопросов дизайна API неинтересны. Также стандарт содержит правила сериализации, нормализации и сравнения URL, которые в целом полезно знать разработчику HTTP API. +Также стандарт содержит правила сериализации, нормализации и сравнения URL, которые в целом полезно знать разработчику HTTP API. 2. Заголовки Заголовки — это метаинформация, привязанная к запросу или ответу. Она может описывать какие-то свойства передаваемых данных (например, Content-Length), дополнительные сведения о клиенте или сервере (User-Agent, Date), или просто содержать поля, не относящиеся непосредственно к смыслу запроса/ответа (например, Authorization). Важное свойство заголовков — это возможность считывать их до того, как получено тело сообщения. Таким образом, заголовки могут, во-первых, сами по себе влиять на обработку запроса или ответа, и ими можно относительно легко манипулировать при проксировании — и многие сетевые агенты действительно это делают, добавляя или модифицируя заголовки по своему усмотрению (в частности, современные веб-браузеры добавляют к запросам целую коллекцию заголовков: User-Agent, Origin, Accept-Language, Connection, Referer, Sec-Fetch-* и так далее, а современное ПО веб-серверов, в свою очередь, автоматически добавляет или модифицирует такие заголовки как X-Powered-By, Date, Content-Length, Content-Encoding, X-Forwarded-For). @@ -4595,7 +4631,7 @@ Content-Type: application/json Возвращает представление ресурса да да -нет +не рекомендуется