@@ -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. An 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 NotepadSection IV. HTTP APIs & the REST Architectural PrinciplesChapter 33. On the HTTP API Concept and TerminologyChapter 34. Advantages and Disadvantages of HTTP APIs Compared to Alternative TechnologiesChapter 35. The REST MythChapter 36. Components of an HTTP Request and Their SemanticsChapter 37. Organizing HTTP APIs Based on the REST PrinciplesChapter 38. Designing a Nomenclature of URLs. The CRUD OperationsChapter 39. Working with HTTP API ErrorsChapter 40. Final Provisions and General Recommendations[Work in Progress] Section V. SDKs & 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. The API as a ProductChapter 52. API Business ModelsChapter 53. Developing a Product VisionChapter 54. Communicating with DevelopersChapter 55. Communicating with Business OwnersChapter 56. An API Services RangeChapter 57. 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. Testing EnvironmentsChapter 63. Managing Expectations + Share: facebook · twitter · linkedin · redditTable of ContentsIntroductionChapter 1. On the Structure of This BookChapter 2. The API DefinitionChapter 3. An 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 NotepadSection IV. HTTP APIs & the REST Architectural PrinciplesChapter 33. On the HTTP API Concept and TerminologyChapter 34. Advantages and Disadvantages of HTTP APIs Compared to Alternative TechnologiesChapter 35. The REST MythChapter 36. Components of an HTTP Request and Their SemanticsChapter 37. Organizing HTTP APIs Based on the REST PrinciplesChapter 38. Designing a Nomenclature of URLs. The CRUD OperationsChapter 39. Working with HTTP API ErrorsChapter 40. Final Provisions and General Recommendations[Work in Progress] Section V. SDKs & UI LibrariesChapter 41. On the Content of This SectionChapter 42. SDKs: 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. The API as a ProductChapter 52. API Business ModelsChapter 53. Developing a Product VisionChapter 54. Communicating with DevelopersChapter 55. Communicating with Business OwnersChapter 56. An API Services RangeChapter 57. 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. Testing EnvironmentsChapter 63. Managing Expectations § @@ -5480,7 +5480,7 @@ Retry-After: 5 and include protection against these attack vectors at the webserver software level. The OWASP community provides a good cheatsheet on the best HTTP API security practices. -In conclusion, we would like to make the following statement: building an HTTP API is relying on the common knowledge of HTTP call semantics and drawing benefits from it by leveraging various software built upon this paradigm, from client frameworks to server gateways, and developers reading and understanding API specifications. In this sense, the HTTP ecosystem provides probably the most comprehensive vocabulary, both in terms of profoundness and adoption, compared to other technologies, allowing for describing many different situations that may arise in client-server communication. While the technology is not perfect and has its flaws, for a public API vendor, it is the default choice, and opting for other technologies rather needs to be substantiated as of today.[Work in Progress] Section V. SDKs & UI LibrariesChapter 41. On the Content of This Section +In conclusion, we would like to make the following statement: building an HTTP API is relying on the common knowledge of HTTP call semantics and drawing benefits from it by leveraging various software built upon this paradigm, from client frameworks to server gateways, and developers reading and understanding API specifications. In this sense, the HTTP ecosystem provides probably the most comprehensive vocabulary, both in terms of profoundness and adoption, compared to other technologies, allowing for describing many different situations that may arise in client-server communication. While the technology is not perfect and has its flaws, for a public API vendor, it is the default choice, and opting for other technologies rather needs to be substantiated as of today.[Work in Progress] Section V. SDKs & UI LibrariesChapter 41. On the Content of This Section As we mentioned in the Introduction, the term “SDK” (which stands for “Software Development Kit”) lacks concrete meaning. The common understanding is that an SDK differs from an API as it provides both program interfaces and tools to work with them. This definition is hardly satisfactory as today any technology is likely to be provided with a bundled toolset. However, there is a very specific narrow definition of an SDK: it is a client library that provides a high-level interface (usually a native one) to some underlying platform (such as a client-server API). Most often, we talk about libraries for mobile OSes or Web browsers that work on top of a general-purpose HTTP API. Among such client SDKs, one case is of particular interest to us: those libraries that not only provide programmatic interfaces to work with an API but also offer ready-to-use visual components for developers. A classic example of such an SDK is the UI libraries provided by cartographical services. Since developing a map engine, especially a vector one, is a very complex task, maps API vendors provide both “wrappers” to their HTTP APIs (such as a search function) and visual components to work with geographical entities. The latter often include general-purpose elements (such as buttons, placemarks, context menus, etc.) that can be used independently from the main functionality of the API. @@ -5490,7 +5490,207 @@ Retry-After: 5 Client libraries that contain visual components with which end users might interact. To avoid being wordy, we will use the term “SDK” for the former and “UI libraries” for the latter. -NB: Strictly speaking, a UI library might either include a client-server API “wrapper” or not (i.e., just provide a “pure” API to some underlying system engine). In this Section, we will mostly talk about the first option as it is the most general case and the most challenging one in terms of API design. Most SDK development patterns we will discuss are also applicable to “pure” libraries.Chapter 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. The API as a Product +NB: Strictly speaking, a UI library might either include a client-server API “wrapper” or not (i.e., just provide a “pure” API to some underlying system engine). In this Section, we will mostly talk about the first option as it is the most general case and the most challenging one in terms of API design. Most SDK development patterns we will discuss are also applicable to “pure” libraries.Chapter 42. SDKs: Problems and Solutions +The first question we need to clarify about SDKs (let us reiterate that we use this term to denote a native client library that allows for working with a technology-agnostic underlying client-server API) is why SDKs exist in the first place. In other words, why is using “wrappers” more convenient for frontend developers than working with the underlying API directly? +Several reasons are obvious: + + +Client-server protocols are usually designed to allow for the implementation of clients in any programming language. This implies that the data received from such an API will not be in the most convenient format. For example, there is no “datetime” type in JSON, and the dates need to be passed as strings. Similarly, most mainstream protocols don't support (de)serializing hash tables. + + +Most programming languages are imperative (and many of them are object-oriented) while most data formats are declarative. Working with raw data received from an API endpoint is inconvenient in terms of writing code. Client developers would prefer this data to be represented as objects (class instances). + + +Different programming languages imply different code styles (casing, namespace organization, etc.), while the practice of tailoring data formats in APIs to match the client's code style is very rare. + + +Platforms and/or programming languages usually prescribe how error handling should be organized — typically, through throwing exceptions or employing defer/panic techniques — which is hardly applicable to the concept of uniform APIs. + + +APIs are provided with instructions (human- or machine-readable) on how to repeat requests if the API endpoints are unavailable. This logic needs to be implemented by a client developer as client frameworks rarely provide it (and it would be very dangerous to automatically repeat potentially non-idempotent requests). Though this point might appear insignificant, it is in fact very important for every vendor of a popular API, as safeguards need to be installed to prevent API servers from overloading due to an uncontrollable spike of request repeats. This is achieved through: + +Reading the Retry-After header and avoiding retrying the endpoint earlier than the time stated by the server +Introducing exponentially growing intervals between consecutive requests. + +This is what client developers should do regarding server errors, but they often skip this part, especially if they work for external partners. + + +Having an SDK would resolve these issues as they are, so to say, trivial: to fix them, the principles of working with the API aren't changed. For every request and response, we construct the corresponding SDK method, and we only need to set rules for doing this transformation, i.e., adapting platform-independent API formats to specific platforms. Additionally, this transformation usually could be automated. +However, there are also non-trivial problems we face while developing an SDK for a client-server API: + + +In client-server APIs, data is passed by value. To refer to some entities, specially designed identifiers need to be used. For example, if we have two sets of entities — recipes and offers — we need to build a map to understand which recipe corresponds to which offer: +// Request 'lungo' and 'latte' recipes +const recipes = await api + .getRecipes(['lungo', 'latte']); +// Build a map that allows to quickly +// find a recipe by its identifier +const recipeMap = new Map(); +recipes.forEach((recipe) => { + recipeMap.set(recipe.id, recipe); +}); +// Request offers for latte and lungo +// in the vicinity +const offers = await api.search({ + recipes: ['lungo', 'latte'], + location +}); +// To show offers to the user, we need +// to take the `recipe_id` in the offer, +// find the recipe description in the map +// and enrich the offer data with +// the recipe data +promptUser( + 'What we have found', + offers.map((offer) => { + const recipe = recipeMap + .get(offer.recipe_id); + return {offer, recipe}; + })); + +This piece of code would be half as long if we received offers from the api.search SDK method with a reference to a recipe: +// Request 'lungo' and 'latte' recipes +const recipes = await api + .getRecipes(['lungo', 'latte']); +// Request offers for latte and lungo +// in the vicinity +const offers = await api.search({ + // Pass the references to the recipes, + // not their identifiers + recipes, + location +}); + +promptUser( + 'What we have found', + // Offer already contains a reference + // to the recipe + offers +); + + + +Client-server APIs are typically decomposed so that one response contains data regarding one kind of entity. Even if the endpoint is composite (i.e., allows for combining data from different sources depending on parameters), it is still the developer's responsibility to use these parameters. The code sample from the previous example would be even shorter if the SDK allowed for the initialization of all related entities: +// Request offers for latte and lungo +// in the vicinity +const offers = await api.search({ + recipes: ['lungo', 'latte'], + location +}); + +// The SDK requested latte and lungo +// data from the `getRecipes` endpoint +// under the hood +promptUser( + 'What we have found', + offers +); + +The SDK can also populate program caches for the entities (if we do not rely on protocol-level caching) and/or allow for the “lazy” initialization of objects. +Generally speaking, storing pieces of data (such as authorization tokens, idempotency keys, draft identifiers in two-phase commits, etc.) between requests is the client's responsibility, and it is rather hard to formalize the rules. If an SDK takes responsibility for managing the data, there will be far fewer mistakes in application code. + + +Receiving callbacks in client-server APIs, even if it is a duplex communication channel, is rather inconvenient to work with and requires object mapping as well. Even if a push model is implemented, the resulting client code will be rather bulky: +// Retrieve ongoing orders +const orders = await api + .getOngoingOrders(); +// Build order map +const orderMap = new Map(); +orders.forEach((order) => { + orderMap.set(order.id, order); +}); +// Subscribe to state change +// events +api.subscribe( + 'order_state_change', + (event) => { + // Find the corresponding order + const order = orderMap + .get(event.order_id); + // Take some actions, like + // updating the UI + // in the application + UI.update(order); + } +); + +If the API requires polling a state change endpoint, developers will additionally need to implement periodic requesting of the endpoint (and install safeguards to avoid overloading the server). +Meanwhile, this code sample already contains several mistakes: + +First, the list of orders is requested, and then the state change listener is added. If some order changes its state between those two calls, the application would never learn about this fact. +If an event comes with an identifier of some unknown order (created from a different device or in a different execution thread), the map lookup operation will return an empty result, and the listener will throw an exception that is not properly handled anywhere. + +Once again, we face a situation where an SDK lacking important features leads to mistakes in applications that use it. It would be much more convenient for a developer if an order object allowed for subscribing to its status updates without the need to learn how it works at the transport level and how to avoid missing an event. +const order = await api + .createOrder(…) + // No need to subscribe to + // the entire status change + // stream and filter it + .subscribe( + 'state_change', + (event) => { … } + ); + +NB: This code relies on the idea that the order is being updated in a consistent manner. Even if the state changes on the server side between the createOrder and subscribe calls, the order object's state will be consistent with the state_change events fired. Organizing this technically is the responsibility of the SDK developers. + + +Restoring after encountering business logic-bound errors is typically a complex procedure. As it can hardly be described in a machine-readable manner, client developers have to elaborate on the scenarios on their own. +// Request offers +const offers = await api + .search(…); +// The user selects an offer +const selectedOffer = + await promptUser(offers); + +let order; +let offer = selectedOffer; +let numberOfTries = 0; +do { + // Trying to create an order + try { + numberOfTries++; + order = await api + .createOrder(offer, …); + } catch (e) { + // If the number of tries exceeded + // some reasonable limit, it's + // better to give up + if (numberOfTries > TRY_LIMIT) { + throw new NoRetriesLeftError(); + } + // If the error is of the “offer + // expired” kind… + if (e.type == + api.OfferExpiredError) { + // Trying to get a new offer + offer = await api + .renewOffer(offer); + } else { + // Other errors + … + } + } +} while (!order); + +As we can see, the simple operation “try to renew an offer if needed” results in a bulky piece of code that is simultaneously error-prone and totally unnecessary as it doesn't introduce any new functionality visible to end users. For an application developer, it would be more convenient if this error (“offer expired”) were not exposed in the SDK, i.e., the SDK automatically renewed offers if needed. +Such situations also occur when working with APIs featuring eventual consistency or optimistic concurrency control — generally speaking, with any API where background errors are expected (which is rather a norm of life for client-server APIs). For frontend developers, writing code to implement policies like “read your writes” (i.e., passing tokens of the last known operation to subsequent queries) is essentially a waste of time. + + +Finally, one more important function that a customized SDK might fulfill is isolating the low-level API and changing the versioning paradigm. It is possible to fully conceal the underlying functionality (i.e., developers won't have direct access to the API) and ensure a certain freedom of working with the API inside the SDK up to seamlessly switching a major version. This approach for sure provides API vendors with more control over how partners' applications work. However, it requires investing more in developing the SDK and, more importantly, properly designing the SDK so that developers won't need to call the API directly due to the lack of access to some functionality or the inconvenience of working with the SDK. Moreover, the SDK itself should be robust enough to be able to handle the transition to a new major version of the API. + + +To summarize the above, a properly designed SDK, apart from maintaining consistency with the platform guidelines and providing “syntactic sugar,” serves three important purposes: + +Lowering the number of mistakes in client code by implementing helpers that cover unobvious and poorly formalizable aspects of working with the API +Relieving client developers of the duty to write code that is absolutely irrelevant to the tasks they are solving +Giving an API vendor more control over integrations. + +Code Generation +As we have seen, the list of tasks that an SDK developer faces (if they aim to create a quality product, of course) is quite considerable. Given that every target platform requires a separate SDK, it is not surprising that many API vendors seek to replace manual labor with automation. +One of the most potent approaches to such automation is code generation. It involves developing a technology that allows generating the SDK code in the target programming language for the target platform based on the API specification. Many modern data interchange protocols (gRPC, for instance) are shipped with generators of such ready-to-use clients in different programming languages. For other technologies (OpenAPI/Swagger, for instance) generators are being developed by the community. +Code generation allows for solving trivial problems such as adapting code style, (de)serializing complex types, etc. — in other words, resolving issues that are bound to the specifics of the platform, not the high-level business logic. The SDK developers can relatively inexpensively complement this machine “translation” with convenient helpers: realize automated repeating idempotent requests (with some retry time management policy), caching results, storing persistent data (such as authorization tokens) in the system storage, etc. +Such a generated SDK is usually referred to as a “client to an API.” The convenience of usage and the functional capabilities of code generation are so formidable that many API vendors restrict themselves to this technology, only providing their SDKs as generated clients. +However, for the aforementioned reasons, higher-level problems (such as receiving callbacks, dealing with business logic-bound errors, etc.) cannot be solved with code generation without writing some arbitrary code for the specific API. In the case of complex APIs with a non-trivial workcycle it is highly desirable that an SDK also solves high-level problems. Otherwise, an API vendor will end up with a bunch of applications using the API and making all the same “rookie mistakes.” This is, of course, not a reason to fully abolish code generation as it's quite convenient to use a generated client as a basis for developing a high-level SDK.Chapter 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. The 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 ed9b8ca..74b7e6a 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 2e4aba8..b5e616e 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 2abf58e..8e2de40 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. Преимущества и недостатки HTTP API в сравнении с альтернативными технологиямиГлава 35. Мифология RESTГлава 36. Составляющие HTTP запросов и их семантикаГлава 37. Организация HTTP API согласно принципам RESTГлава 38. Разработка номенклатуры URL ресурсов. CRUD-операцииГлава 39. Работа с ошибками в 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. Преимущества и недостатки HTTP API в сравнении с альтернативными технологиямиГлава 35. Мифология RESTГлава 36. Составляющие HTTP запросов и их семантикаГлава 37. Организация HTTP API согласно принципам RESTГлава 38. Разработка номенклатуры URL ресурсов. CRUD-операцииГлава 39. Работа с ошибками в 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. Преимущества и недостатки HTTP API в сравнении с альтернативными технологиямиГлава 35. Мифология RESTГлава 36. Составляющие HTTP запросов и их семантикаГлава 37. Организация HTTP API согласно принципам RESTГлава 38. Разработка номенклатуры URL ресурсов. CRUD-операцииГлава 39. Работа с ошибками в 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. Преимущества и недостатки HTTP API в сравнении с альтернативными технологиямиГлава 35. Мифология RESTГлава 36. Составляющие HTTP запросов и их семантикаГлава 37. Организация HTTP API согласно принципам RESTГлава 38. Разработка номенклатуры URL ресурсов. CRUD-операцииГлава 39. Работа с ошибками в 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. Управление ожиданиями § @@ -5464,7 +5464,7 @@ Retry-After: 5 и заложите защиту от этих векторов атак на уровне вашего серверного ПО. Организация OWASP предоставляет хороший обзор лучших security-практик для HTTP API. -В заключение хотелось бы сказать следующее: HTTP API — это способ организовать ваше API так, чтобы полагаться на понимание семантики операций как разнообразным программным обеспечением, от клиентских фреймворков до серверных гейтвеев, так и разработчиком, который читает спецификацию. В этом смысле экосистема HTTP предоставляет пожалуй что наиболее широкий (и в плане глубины, и в плане распространённости) по сравнению с другими технологиями словарь для описания самых разнообразных ситуаций, возникающих во время работы клиент-серверных приложений. Разумеется, эта технология не лишена своих недостатков, но для разработчика публичного API она является выбором по умолчанию — на сегодняшний день скорее надо обосновывать отказ от HTTP API чем выбор в его пользу.[В разработке] Раздел V. SDK и UIГлава 41. О содержании раздела +В заключение хотелось бы сказать следующее: HTTP API — это способ организовать ваше API так, чтобы полагаться на понимание семантики операций как разнообразным программным обеспечением, от клиентских фреймворков до серверных гейтвеев, так и разработчиком, который читает спецификацию. В этом смысле экосистема HTTP предоставляет пожалуй что наиболее широкий (и в плане глубины, и в плане распространённости) по сравнению с другими технологиями словарь для описания самых разнообразных ситуаций, возникающих во время работы клиент-серверных приложений. Разумеется, эта технология не лишена своих недостатков, но для разработчика публичного API она является выбором по умолчанию — на сегодняшний день скорее надо обосновывать отказ от HTTP API чем выбор в его пользу.[В разработке] Раздел V. SDK и UIГлава 41. О содержании раздела Как мы отмечали во Введении, аббревиатура «SDK» («Software Development Kit»), как и многие из обсуждавшихся ранее терминов, не имеет конкретного значения. Считается, что SDK отличается от API тем, что помимо программных интерфейсов содержит и готовые инструменты для работы с ними. Определение это, конечно, лукавое, поскольку почти любая технология сегодня идёт в комплекте со своим набором инструментов. Тем не менее, у термина SDK есть и более узкое значение, в котором он часто используется: это клиентская библиотека, которая предоставляет высокоуровневый (обычно, нативный) интерфейс для работы с некоторой нижележащей платформой (и в частности с клиент-серверным API). Чаще всего речь идёт о библиотеках для мобильных ОС или веб браузеров, которые работают поверх HTTP API сервиса общего назначения. Среди подобных клиентских SDK особо выделяются те, которые предоставляют не только программные интерфейсы для работы с API, но также и готовые визуальные компоненты, которые разработчик может использовать. Классический пример такого SDK — это библиотеки карточных сервисов; в силу исключительной сложности самостоятельной реализации движка работы с картами (особенно векторными) вендоры API карт предоставляют и «обёртки» к HTTP API (например, поисковому), и готовые библиотеки для работы с географическими сущностями, которые часто включают в себя и визуальные компоненты общего назначения — кнопки, метки, контекстные меню — которые могут применяться и совершенно самостоятельно вне контекста API (SDK) как такового. @@ -5474,7 +5474,211 @@ Retry-After: 5 клиентским библиотекам, предоставляющие визуальные компоненты, с которыми конечный пользователь может взаимодействовать напрямую. Во избежание нагромождения подобных оборотов мы будем называть первый тип инструментов просто «SDK», а второй — «UI-библиотеки». -NB: вообще говоря, UI-библиотека может как включать в себя обёртку над клиент-серверным API, так и предоставлять чистый API к графическому движку. В рамках этой книги мы будем говорить, в основном, о первом варианте как о более общем случае (и более сложном с точки зрения проектирования API). Многие паттерны дизайна SDK, которые мы опишем далее, применимы и к «чистым» библиотекам без клиент-серверной составляющей.Глава 42. SDK: проблемы и решенияГлава 43. КодогенерацияГлава 44. UI-компонентыГлава 45. Декомпозиция UI-компонентов. MV*-подходыГлава 46. MV*-фреймворкиГлава 47. Backend-Driven UIГлава 48. Разделяемые ресурсы и асинхронные блокировкиГлава 49. Вычисляемые свойстваГлава 50. В заключениеРаздел VI. API как продуктГлава 51. Продукт API +NB: вообще говоря, UI-библиотека может как включать в себя обёртку над клиент-серверным API, так и предоставлять чистый API к графическому движку. В рамках этой книги мы будем говорить, в основном, о первом варианте как о более общем случае (и более сложном с точки зрения проектирования API). Многие паттерны дизайна SDK, которые мы опишем далее, применимы и к «чистым» библиотекам без клиент-серверной составляющей.Глава 42. SDK: проблемы и решения +Первый вопрос об SDK (напомним, так мы будем называть нативную клиентскую библиотеку, предоставляющую доступ к technology-agnostic клиент-серверному API), который мы должны прояснить — почему вообще такое явление как SDK существует. Иными словами, почему использование обёртки для фронтенд-разработчика является более удобным, нежели работа с нижележащим API напрямую. +Некоторые причины лежат на поверхности: + + +Протоколы клиент-серверных API, как правило, разрабатываются так, что не зависят от конкретного языка программирования и, таким образом, без дополнительных действий полученные из API данные будут представлены в не самом удобном формате. Например в JSON нет типа данных «дата и время», и его приходится передавать в виде строки; или, скажем, поддержка (де)сериализации хэш-таблиц в протоколах общего назначения отсутствует. + + +Большинство языков программирования императивные (и чаще всего — объектно-ориентированные), в то время как большинство форматов данных — декларативные. Работать с сырыми данными, полученными из API, таким образом почти всегда неудобно с точки зрения написания кода, программистам зачастую было бы удобнее работать с полученными из API данными как с объектами. + + +Разные языки программирования предполагают разный стиль кодирования (кейсинг, организация неймспейсов и т.п.), в то время как концепция API не предполагает адаптацию форматирования под запрашивающую платформу. + + +Как правило, платформа/язык программирования диктуют свою парадигму работы с возникающими ошибками (в виде исключений и/или механизмов defer/panic), что опять же неприменимо в концепции универсального для всех клиентов сетевого API. + + +API идёт в комплекте с рекомендациями (машино- или человекочитаемыми) по организации перезапросов в случае недоступности эндпойнтов. Эту логику необходимо реализовать разработчику клиента, поскольку библиотеки работы с сетью её, как правило, не предоставляют (и в общем-то не могут этого делать для потенциально неидемпотентных запросов). Этот пункт, при всей видимой малозначительности, является критически важным для любого крупного API, поскольку именно на этом уровне разработчики API могут заложить предохранители от потенциальной перегрузки серверов API лавиной перезапросов, поскольку разработчики клиентов этой частью традиционно пренебрегают: +* читать заголовок Retry-After и не пытаться перезапросить эндпойнт раньше, чем указал сервер; +* ввести увеличивающие интервалы между перезапросами. + + +Наличие собственного SDK устранило бы указанные проблемы, которые в некотором смысле являются тривиальными: для их решения не требуетя изменять порядок работы с API (каждому вызову и каждому ответу в API однозначно соответствует какая-то конструкция на языке платформы, и достаточно описать правила построения такого соответствия) — достаточно адаптировать платформо-независимый формат API к правилам конкретного языка программирования, что часто можно автоматизировать. +Однако, помимо тривиальных проблем при разработке SDK к клиент-серверному API мы сталкиваемся и с проблемами более высокого порядка: + + +В клиент-серверных API данные передаются только по значению; чтобы сослаться на какую-то сущность, необходимо использовать какие-то внешние идентификаторы. Например, если у нас есть два набора сущностей — рецепты и предложения кофе — то нам необходимо будет построить карту рецептов по id, чтобы понять, на какой рецепт ссылается какое предложение: +// Запрашиваем информацию о рецептах +// лунго и латте +const recipes = await api + .getRecipes(['lungo', 'latte']); +// Строим карту, позволяющую обратиться +// к данным о рецепте по его id +const recipeMap = new Map(); +recipes.forEach((recipe) => { + recipeMap.set(recipe.id, recipe); +}); +// Запрашиваем предложения +// лунго и латте +const offers = await api.search({ + recipes: ['lungo', 'latte'], + location +}); +// Для того, чтобы показать предложения +// пользователю, нужно из каждого +// предложения извлечь id рецепта, +// и уже по id найти описание +promptUser( + 'Найденные предложения', + offers.map((offer) => { + const recipe = recipeMap + .get(offer.recipe_id); + return {offer, recipe}; + })); + +Указанный код мог бы быть вдвое короче, если бы мы сразу получали из метода api.search предложения с заполненной ссылкой на рецепт: +// Запрашиваем информацию о рецептах +// лунго и латте +const recipes = await api + .getRecipes(['lungo', 'latte']); +// Запрашиваем предложения +// лунго и латте +const offers = await api.search({ + // Передаём не идентификаторы + // рецептов, а ссылки на объекты, + // описывающие рецепты + recipes, + location +}); + +promptUser( + 'Найденные предложения', + // offer уже содержит + // ссылку на рецепт + offers +); + + + +Клиент-серверные API, как правило, стараются декомпозировать так, чтобы одному запросу соответствовал один тип возвращаемых данных. Даже если эндпойнт композитный (т.е. позволяет при запросе с помощью параметров указать, какие из дополнительных данных необходимо вернуть), это всё ещё ответственность разработчика этими параметрами воспользоваться. Код из примера выше мог бы быть ещё короче, если бы SDK взял на себя инициализацию всех нужных связанных объектов: +// Запрашиваем предложения +// лунго и латте +const offers = await api.search({ + recipes: ['lungo', 'latte'], + location +}); + +// SDK сам обратился к эндпойнту +// `getRecipes` и получил данные +// по лунго и латте +promptUser( + 'Найденные предложения', + offers +); + +При этом SDK может также заполнять программные кэши сущностей (если мы не полагаемся на кэширование на уровне протокола) и/или позволять «лениво» инициализировать объекты. +Вообще, хранение данных (таких, как токены авторизации, ключи идемпотентности при перезапросах, идентификаторы черновиков при двухфазных коммитах и т.д.) между запросам также является ответственностью клиента и с трудом поддаётся формализации. Если SDK возьмёт на себя эти функции, в коде приложений, использующих API, будет допущено намного меньше ошибок. + + +Получение обратных вызовов в клиент-серверном API, даже если это дуплексный канал, с точки зрения клиента выглядит крайне неудобным в разработке, поскольку вновь требует наличия карт объектов. Даже если в API реализована push-модель, код выходит чрезвычайно громоздким: +// Получаем текущие заказы +const orders = await api + .getOngoingOrders(); +// Строим карту заказов +const orderMap = new Map(); +orders.forEach((order) => { + orderMap.set(order.id, order); +}); +// Подписываемся на события +// изменения состояния заказов +api.subscribe( + 'order_state_change', + (event) => { + const order = orderMap + .get(event.order_id); + // Выполняем какие-то + // действия с заказом, + // например, обновляем UI + // приложения + UI.update(order); + } +); + +Если же API требует поллинга изменений состояний объектов, то разработчику придётся ещё где-то реализовать периодический опрос эндпойнта со списком изменений, и ещё следить за тем, чтобы не перегружать сервер запросами. +Кроме того, обратите внимание, что в вышеприведённом фрагменте кода [разработчиком приложения] допущены множественные ошибки: + +сначала получается список заказов, а затем происходит подписывание на их изменения; если между двумя этими вызовами какой-то из заказов изменился, приложение об этом не узнает; +если пришло событие изменения какого-то неизвестного приложению заказа (который, например, был создан с другого устройства или в другом потоке исполнения), поиск в карте заказов вернёт пустой результат, и обработчик события выбросит исключение, которое никак не обработано. + +И вновь мы приходим к тому, что недостаточно продуманный SDK приводит к ошибкам в работе использующих его приложений. Разработчику было бы намного удобнее, если бы объект заказа позволял подписаться на свои события, не задумываясь о том, как эта подписка технически работает и как не пропустить события: +const order = await api + .createOrder(…) + // Нет нужды подписываться + // на *все* события и потом + // фильтровать их по id + .subscribe( + 'state_change', + (event) => { … } + ); + +NB: код выше предполагает, что объект order изменяется консистентным образом: даже если между вызовами createOrder и subscribe состояние заказа успело измениться на сервере, состояние объекта order будет консистентно списку событий state_change, полученных наблюдателем. Как это организовать технически — как раз забота разработчика SDK. + + +Восстановление после ошибок в бизнес-логике, как правило, достаточно сложная операция, которую сложно описать в машиночитаемом виде. Разработчику клиента необходимо самому продумать эти сценарии. +// Получаем предложения +const offers = await api + .search(…); +// Пользователь выбирает +// подходящее предложение +const selectedOffer = + await promptUser(offers); +let order; +let offer = selectedOffer; +let numberOfTries = 0; +do { + // Пытаемся создать заказ + try { + numberOfTries++; + order = await api + .createOrder(offer, …); + } catch (e) { + // Если количество попыток + // пересоздания заказа превысило + // какое-то разумное значение + // следует бросить попытки + // восстановиться + if (numberOfTries > TRY_LIMIT) { + throw new NoRetriesLeftError(); + } + // Если произошла ошибка + // «предложение устарело» + if (e.type == + api.OfferExpiredError) { + // если попытки ещё остались, + // пытаемся получить новое + // предложение + offer = await api + .renewOffer(offer); + } else { + // Обработка других видов + // ошибок + … + } + } +} while (!order); + +Как мы видим, простая операция «попробовать продлить предложение» выливается в громоздкий код, в котором легко ошибиться, и, что ещё важнее, который совершенно не нужен разработчику приложения, поскольку он не добавляет никакой новой функциональности для конечного пользователя. Было бы гораздо проще, если бы этой ошибки вовсе не было в SDK, т.е. попытки обновления и перезапросы выполнялись бы автоматически. +Аналогичные ситуации возникают и в случае нестрого-консистентных API или оптимистичного управления параллелизмом — и вообще в любом API, в котором фон ошибок является ожидаемым (что в случае распределённых клиент-серверных API является нормой жизни). Для разработчика приложения написание кода, имплементирующего политики типа «read your writes» (т.е. передачу токенов последней известной операции в последующие запросы) — попросту напрасная трата времени. + + +Наконец, ещё одна важная функция, которая может быть доверена SDK — это изоляция нижележащего API и смена парадигмы версионирования. Доступ к функциональности API может быть скрыт (т.е. разработчики не будут иметь доступ к низкоуровневой работой с API), тем самым обеспечивая определённую свободу работы с API изнутри SDK, вплоть до бесшовного перехода на новые мажорные версии API. Этот подход, несомненно, предоставляет вендору API намного больше контроля над приложениями клиентов, но требует и намного больше ресурсов на разработку, и, что важнее, грамотного проектирования SDK — такого, чтобы у разработчиков не было необходимости обращаться к API напрямую в обход SDK по причине отсутствия в нём необходимых функций или их плохой реализации, и при этом SDK могу пережить смену мажорной версии низкоуровневого API. + + +Суммируя написанное выше, хорошо спроектированный SDK служит, помимо поддержания консистентности платформе и предоставления «синтаксического сахара», трём важным целям: + +снижение количества ошибок в клиентском коде путём имплементации хелперов, покрывающих неочевидные и слабоформализуемые аспекты работы с API; +избавление клиентских разработчиков от необходимости писать код, который им совершенно не нужен; +предоставление разработчику API большего контроля над интеграциями. + +Кодогенерация +Как мы убедились, список задач, стоящих перед разработчиком SDK (если, конечно, его целью является качественный продукт) — очень и очень значительный. Учитывая, что под каждую целевую платформу необходим отдельный SDK, неудивительно, что многие вендоры API стремятся полностью или частично заменить ручной труд машинным. +Одно из основных направлений такой автоматизации — кодогенерация, то есть разработка технологии, которая позволяет по спецификации API сгенерировать готовый код SDK на целевом языке программирования для целевой платформы. Многие современные стандарты обмена данными (в частности, gRPC) поставляются в комплекте с генераторами готовых клиентов на различных языках; к другим технологиям (в частности, OpenAPI/Swagger) такие генераторы пишутся энтузиастами. +Генерация кода позволяет решить типовые проблемы: стиль кодирования, обработка исключений, (де)сериализация сложных типов — словом все те задачи, которые зависят не от особенностей высокоуровневой бизнес-логики, а от конвенций конкретной платформы. Относительно недорого разработчик API может дополнить такой автоматизированный «перевод» правильными настройками используемых системных средств: обеспечить автоматические перезапросы для идемпотентных эндпойнтов (с реализацией какой-то политики), кэширование результатов, сохранение данных (например, токенов авторизации) в системном хранилище и т.д. Такой сгенерированный SDK часто называют термином «клиент к API». +Удобство использования и функциональные возможности кодогенерации столь привлекательны, что многие вендоры API только ей и ограничиваются, предоставляя свои SDK в виде сгенерированных клиентов. +Как мы, однако, видим из написанного выше, проблемы более высокого порядка — получение серверных событий, обработка ошибок в бизнес-логике и т.п. — никак не может быть покрыта кодогенерацией, во всяком случае — стандартным модулем без его доработки применительно к конкретному API. В случае нетривиальных API со сложным основным циклом работы очень желательно, чтобы SDK решал также и высокоуровневые проблемы, иначе вы просто получите множество разработанных поверх API приложений, раз за разом повторяющие одни и те же «детские ошибки». Тем не менее, это не повод отказываться от кодогенерации полностью — её можно использовать как базис, на котором будет разработан высокоуровневый 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 e7624a3..07ad8a5 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 e82520b..d140ddf 100644 --- a/docs/index.html +++ b/docs/index.html @@ -122,8 +122,8 @@ [Work in Progress] Section V. SDKs & UI Libraries - Chapter 41. On the Content of This Section -Chapter 42. The SDK: Problems and Solutions + Chapter 41. On the Content of This Section +Chapter 42. SDKs: Problems and Solutions Chapter 43. The Code Generation Pattern Chapter 44. The UI Components Chapter 45. Decomposing UI Components diff --git a/docs/index.ru.html b/docs/index.ru.html index be86ce2..cb98378 100644 --- a/docs/index.ru.html +++ b/docs/index.ru.html @@ -122,8 +122,8 @@ [В разработке] Раздел V. SDK и UI - Глава 41. О содержании раздела -Глава 42. SDK: проблемы и решения + Глава 41. О содержании раздела +Глава 42. SDK: проблемы и решения Глава 43. Кодогенерация Глава 44. UI-компоненты Глава 45. Декомпозиция UI-компонентов. MV*-подходы diff --git a/src/en/clean-copy/06-[Work in Progress] Section V. SDKs & UI Libraries/02.md b/src/en/clean-copy/06-[Work in Progress] Section V. SDKs & UI Libraries/02.md index c4aefb4..44a100c 100644 --- a/src/en/clean-copy/06-[Work in Progress] Section V. SDKs & UI Libraries/02.md +++ b/src/en/clean-copy/06-[Work in Progress] Section V. SDKs & UI Libraries/02.md @@ -192,7 +192,22 @@ However, there are also non-trivial problems we face while developing an SDK for As we can see, the simple operation “try to renew an offer if needed” results in a bulky piece of code that is simultaneously error-prone and totally unnecessary as it doesn't introduce any new functionality visible to end users. For an application developer, it would be more convenient if this error (“offer expired”) *were not exposed in the SDK*, i.e., the SDK automatically renewed offers if needed. Such situations also occur when working with APIs featuring eventual consistency or optimistic concurrency control — generally speaking, with any API where background errors are expected (which is rather a norm of life for client-server APIs). For frontend developers, writing code to implement policies like “read your writes” (i.e., passing tokens of the last known operation to subsequent queries) is essentially a waste of time. + + 5. Finally, one more important function that a customized SDK might fulfill is isolating the low-level API and changing the versioning paradigm. It is possible to fully conceal the underlying functionality (i.e., developers won't have direct access to the API) and ensure a certain freedom of working with the API inside the SDK up to seamlessly switching a major version. This approach for sure provides API vendors with more control over how partners' applications work. However, it requires investing more in developing the SDK and, more importantly, properly designing the SDK so that developers won't need to call the API directly due to the lack of access to some functionality or the inconvenience of working with the SDK. Moreover, the SDK itself should be robust enough to be able to handle the transition to a new major version of the API. -To summarize the above, a properly designed SDK, apart from maintaining consistency with the platform guidelines and providing “syntactic sugar,” serves two important purposes: +To summarize the above, a properly designed SDK, apart from maintaining consistency with the platform guidelines and providing “syntactic sugar,” serves three important purposes: * Lowering the number of mistakes in client code by implementing helpers that cover unobvious and poorly formalizable aspects of working with the API - * Relieving client developers of the duty to write code that is absolutely irrelevant to the tasks they are solving. \ No newline at end of file + * Relieving client developers of the duty to write code that is absolutely irrelevant to the tasks they are solving + * Giving an API vendor more control over integrations. + +#### Code Generation + +As we have seen, the list of tasks that an SDK developer faces (if they aim to create a quality product, of course) is quite considerable. Given that every target platform requires a separate SDK, it is not surprising that many API vendors seek to replace manual labor with automation. + +One of the most potent approaches to such automation is code generation. It involves developing a technology that allows generating the SDK code in the target programming language for the target platform based on the API specification. Many modern data interchange protocols (gRPC, for instance) are shipped with generators of such ready-to-use clients in different programming languages. For other technologies (OpenAPI/Swagger, for instance) generators are being developed by the community. + +Code generation allows for solving trivial problems such as adapting code style, (de)serializing complex types, etc. — in other words, resolving issues that are bound to the specifics of the platform, not the high-level business logic. The SDK developers can relatively inexpensively complement this machine “translation” with convenient helpers: realize automated repeating idempotent requests (with some retry time management policy), caching results, storing persistent data (such as authorization tokens) in the system storage, etc. + +Such a generated SDK is usually referred to as a “client to an API.” The convenience of usage and the functional capabilities of code generation are so formidable that many API vendors restrict themselves to this technology, only providing their SDKs as generated clients. + +However, for the aforementioned reasons, higher-level problems (such as receiving callbacks, dealing with business logic-bound errors, etc.) cannot be solved with code generation without writing some arbitrary code for the specific API. In the case of complex APIs with a non-trivial workcycle it is highly desirable that an SDK also solves high-level problems. Otherwise, an API vendor will end up with a bunch of applications using the API and making all the same “rookie mistakes.” This is, of course, not a reason to fully abolish code generation as it's quite convenient to use a generated client as a basis for developing a high-level SDK. \ No newline at end of file diff --git a/src/ru/clean-copy/06-[В разработке] Раздел V. SDK и UI-библиотеки/02.md b/src/ru/clean-copy/06-[В разработке] Раздел V. SDK и UI-библиотеки/02.md index 1e7f44d..e3c9394 100644 --- a/src/ru/clean-copy/06-[В разработке] Раздел V. SDK и UI-библиотеки/02.md +++ b/src/ru/clean-copy/06-[В разработке] Раздел V. SDK и UI-библиотеки/02.md @@ -198,6 +198,21 @@ Аналогичные ситуации возникают и в случае нестрого-консистентных API или оптимистичного управления параллелизмом — и вообще в любом API, в котором фон ошибок является ожидаемым (что в случае распределённых клиент-серверных API является нормой жизни). Для разработчика приложения написание кода, имплементирующего политики типа «read your writes» (т.е. передачу токенов последней известной операции в последующие запросы) — попросту напрасная трата времени. -Суммируя написанное выше, хорошо спроектированный SDK служит, помимо поддержания консистентности платформе и предоставления «синтаксического сахара», двум важным целям: + 5. Наконец, ещё одна важная функция, которая может быть доверена SDK — это изоляция нижележащего API и смена парадигмы версионирования. Доступ к функциональности API может быть скрыт (т.е. разработчики не будут иметь доступ к низкоуровневой работой с API), тем самым обеспечивая определённую свободу работы с API изнутри SDK, вплоть до бесшовного перехода на новые мажорные версии API. Этот подход, несомненно, предоставляет вендору API намного больше контроля над приложениями клиентов, но требует и намного больше ресурсов на разработку, и, что важнее, грамотного проектирования SDK — такого, чтобы у разработчиков не было необходимости обращаться к API напрямую в обход SDK по причине отсутствия в нём необходимых функций или их плохой реализации, и при этом SDK могу пережить смену мажорной версии низкоуровневого API. + +Суммируя написанное выше, хорошо спроектированный SDK служит, помимо поддержания консистентности платформе и предоставления «синтаксического сахара», трём важным целям: * снижение количества ошибок в клиентском коде путём имплементации хелперов, покрывающих неочевидные и слабоформализуемые аспекты работы с API; - * избавление клиентских разработчиков от необходимости писать код, который им совершенно не нужен. + * избавление клиентских разработчиков от необходимости писать код, который им совершенно не нужен; + * предоставление разработчику API большего контроля над интеграциями. + +#### Кодогенерация + +Как мы убедились, список задач, стоящих перед разработчиком SDK (если, конечно, его целью является качественный продукт) — очень и очень значительный. Учитывая, что под каждую целевую платформу необходим отдельный SDK, неудивительно, что многие вендоры API стремятся полностью или частично заменить ручной труд машинным. + +Одно из основных направлений такой автоматизации — кодогенерация, то есть разработка технологии, которая позволяет по спецификации API сгенерировать готовый код SDK на целевом языке программирования для целевой платформы. Многие современные стандарты обмена данными (в частности, gRPC) поставляются в комплекте с генераторами готовых клиентов на различных языках; к другим технологиям (в частности, OpenAPI/Swagger) такие генераторы пишутся энтузиастами. + +Генерация кода позволяет решить типовые проблемы: стиль кодирования, обработка исключений, (де)сериализация сложных типов — словом все те задачи, которые зависят не от особенностей высокоуровневой бизнес-логики, а от конвенций конкретной платформы. Относительно недорого разработчик API может дополнить такой автоматизированный «перевод» правильными настройками используемых системных средств: обеспечить автоматические перезапросы для идемпотентных эндпойнтов (с реализацией какой-то политики), кэширование результатов, сохранение данных (например, токенов авторизации) в системном хранилище и т.д. Такой сгенерированный SDK часто называют термином «клиент к API». + +Удобство использования и функциональные возможности кодогенерации столь привлекательны, что многие вендоры API только ей и ограничиваются, предоставляя свои SDK в виде сгенерированных клиентов. + +Как мы, однако, видим из написанного выше, проблемы более высокого порядка — получение серверных событий, обработка ошибок в бизнес-логике и т.п. — никак не может быть покрыта кодогенерацией, во всяком случае — стандартным модулем без его доработки применительно к конкретному API. В случае нетривиальных API со сложным основным циклом работы очень желательно, чтобы SDK решал также и высокоуровневые проблемы, иначе вы просто получите множество разработанных поверх API приложений, раз за разом повторяющие одни и те же «детские ошибки». Тем не менее, это не повод отказываться от кодогенерации полностью — её можно использовать как базис, на котором будет разработан высокоуровневый SDK. diff --git a/src/ru/drafts/06-Раздел V. SDK и UI-библиотеки/02.md b/src/ru/drafts/06-Раздел V. SDK и UI-библиотеки/02.md deleted file mode 100644 index c0a7d47..0000000 --- a/src/ru/drafts/06-Раздел V. SDK и UI-библиотеки/02.md +++ /dev/null @@ -1,212 +0,0 @@ -### SDK: проблемы и решения - -Первый вопрос, который мы должны себе задать при разработке SDK (напомним, так мы будем называть нативную клиентскую библиотеку, предоставляющую доступ к technology-agnostic клиент-серверному API) — почему вообще такое явление как SDK существует. Иными словами, почему использование обёртки для фронтенд-разработчика является более удобным, нежели работа с нижележащим API напрямую. - -Некоторые проблемы лежат на поверхности: - 1. Протоколы клиент-серверных API, как правило, разрабатываются так, что не зависят от конкретного языка программирования и, таким образом, без дополнительных действий полученные из API данные будут представлены в не самом удобном формате. Например в JSON нет типа данных «дата и время», и его приходится передавать в виде строки; или, скажем, поддержка (де)сериализации хэш-таблиц в протоколах общего назначения отсутствует. - - 2. Большинство языков программирования императивные (и чаще всего — объектно-ориентированные), в то время как большинство форматов данных — декларативные. Работать с сырыми данными, полученными из API, таким образом почти всегда неудобно с точки зрения написания кода, программистам зачастую было бы удобнее работать с полученными из API данными как с объектами. - - 3. Разные языки программирования предполагают разный стиль кодирования (кейсинг, организация неймспейсов и т.п.), в то время как концепция API не предполагает адаптацию форматирования под запрашивающую платформу. - - 4. Как правило, платформа/язык программирования диктуют свою парадигму работы с возникающими ошибками (в виде исключений и/или механизмов defer/panic), что опять же неприменимо в концепции универсального для всех клиентов сетевого API. - - 5. API идёт в комплекте с рекомендациями (машино- или человекочитаемыми) по организации перезапросов в случае недоступности эндпойнтов. Эту логику необходимо реализовать разработчику клиента, поскольку библиотеки работы с сетью её, как правило, не предоставляют (и в общем-то не могут этого делать для потенциально неидемпотентных запросов). Этот пункт, при всей видимой малозначительности, является критически важным для любого крупного API, поскольку именно на этом уровне разработчики API могут заложить предохранители от потенциальной перегрузки серверов API лавиной перезапросов, поскольку разработчики клиентов этой частью традиционно пренебрегают: - * читать заголовок Retry-After и не пытаться перезапросить эндпойнт раньше, чем указал сервер; - * ввести увеличивающие интервалы между перезапросами. - -Эти проблемы, однако, являются тривиальными — в том смысле, что они не требуют изменять порядок работы с API (каждому вызову и каждому ответу в API однозначно соответствует какая-то конструкция на языке платформы, и достаточно описать правила построения такого соответствия), а только лишь адаптировать платформо-независимый формат API к правилам конкретного языка программирования. Помимо тривиальных проблем, при разработке SDK к клиент-серверному API мы сталкиваемся и с проблемами более высокого порядка: - - 1. В клиент-серверных API данные передаются только по значению; чтобы сослаться на какую-то сущность, необходимо использовать какие-то внешние идентификаторы. Например, если у нас есть два набора сущностей — рецепты и предложения кофе — то нам необходимо будет построить карту рецептов по id, чтобы понять, на какой рецепт ссылается какое предложение: - - ``` - // Запрашиваем информацию о рецептах - // лунго и латте - const recipes = await api - .getRecipes(['lungo', 'latte']); - // Строим карту, позволяющую обратиться - // к данным о рецепте по его id - const recipeMap = new Map(); - recipes.forEach((recipe) => { - recipeMap.set(recipe.id, recipe); - }); - // Запрашиваем предложения - // лунго и латте - const offers = await api.search({ - recipes: ['lungo', 'latte'], - location - }); - // Для того, чтобы показать предложения - // пользователю, нужно из каждого - // предложения извлечь id рецепта, - // и уже по id найти описание - promptUser( - 'Найденные предложения', - offers.map((offer) => { - const recipe = recipeMap - .get(offer.recipe_id); - return recipe.name; - })); - ``` - - Указанный код мог бы быть вдвое короче, если бы мы сразу получали из метода `api.search` предложения с заполненной ссылкой на рецепт: - - ``` - // Запрашиваем информацию о рецептах - // лунго и латте - const recipes = await api - .getRecipes(['lungo', 'latte']); - // Запрашиваем предложения - // лунго и латте - const offers = await api.search({ - // Передаём не идентификаторы - // рецептов, а ссылки на объекты, - // описывающие рецепты - recipes, - location - }); - - promptUser( - 'Найденные предложения', - offers.map((offer) => { - return offer.recipe.name; - })); - ``` - - 2. Клиент-серверные API, как правило, стараются декомпозировать так, чтобы одному запросу соответствовал один тип возвращаемых данных. Даже если эндпойнт композитный (т.е. позволяет при запросе с помощью параметров указать, какие из дополнительных данных необходимо вернуть), это всё ещё ответственность разработчика этими параметрами воспользоваться. Код из примера выше мог бы быть ещё короче, если бы SDK взял на себя инициализацию всех нужных связанных объектов: - - ``` - // Запрашиваем предложения - // лунго и латте - const offers = await api.search({ - recipes: ['lungo', 'latte'], - location - }); - - promptUser( - 'Найденные предложения', - offers.map((offer) => { - // SDK сам обратился к эндпойнту - // `getRecipes` и получил данные - // по лунго и латте - return offer.recipe.name; - })); - ``` - - При этом SDK может также заполнять программные кэши сущностей (если мы не полагаемся на кэширование на уровне протокола) и/или позволять «лениво» инициализировать объекты: - - ``` - promptUser( - 'Найденные предложения', - offers.map((offer) => { - // SDK обратится за данными - // о рецепте, только если - // они действительно нужны - const recipe = await offer - .getRecipe() - return recipe.name; - })); - ``` - - **NB**: Как видно из примера выше, эта функциональность («ленивая инициализация») требует очень внимательного подхода к имплементации со стороны разработчиков SDK. Если SDK не включает в себя агрегацию запросов и кэширование результатов, то пример кода выше запросит рецепт в цикле на каждое предложение, то есть выполнит огромное количество лишней работы. Мы можем описать эту тонкость в документации, конечно, но вновь закон больших чисел работает против нас: среди множества разработчиков обязательно найдутся те, кто не прочитает документацию или просто не задумается о том, что он запрашивает с сервера одни и те же данные в цикле. - - 3. Получение обратных вызовов в клиент-серверном API, даже если это дуплексный канал, с точки зрения клиента выглядит крайне неудобным в разработке, поскольку вновь требует наличия карт объектов. Даже если в API реализована push-модель, код выходит чрезвычайно громоздким: - - ``` - // Получаем текущие заказы - const orders = await api - .getOngoingOrders(); - // Строим карту заказов - const orderMap = new Map(); - orders.forEach((order) => { - orderMap.set(order.id, order); - }); - // Подписываемся на события - // изменения состояния заказов - api.subscribe( - 'order_state_change', - (event) => { - const order = orderMap - .get(event.order_id); - // Выполняем какие-то - // действия с заказом, - // например, обновляем UI - // приложения - UI.update(order); - } - ); - ``` - - Если же API требует поллинга изменений состояний объектов, то разработчику придётся ещё где-то реализовать периодический опрос эндпойнта со списком изменений, и ещё следить за тем, чтобы не перегружать сервер запросами. - - Кроме того, обратите внимание, что в вышеприведённом фрагменте кода [разработчиком приложения] допущены множественные ошибки: - * сначала получается список заказов, а затем происходит подписывание на их изменения; если между двумя этими вызовами какой-то из заказов изменился, приложение об этом не узнает; - * если пришло событие изменения какого-то неизвестного приложению заказа (который, например, был создан с другого устройства или в другом потоке исполнения), поиск в карте заказов вернёт пустой результат. - - И вновь мы приходим к тому, что недостаточно продуманный SDK приводит к ошибкам в работе использующих его приложений. Разработчику было бы намного удобнее, если бы объект заказа позволял подписаться на свои события, не задумываясь о том, как эта подписка технически работает и как не пропустить события: - - ``` - const order = await api. - createOrder(…); - // Нет нужды подписываться - // на *все* события и потом - // фильтровать их по id - order.subscribe( - 'state_change', - (event) => { … } - ); - ``` - - **NB**: код выше предполагает, что объект `order` изменяется консистентным образом: даже если между вызовами `createOrder` и `subscribe` состояние заказа успело измениться на сервере, обработчик события `state_change` это изменение получит. Как это организовать технически — как раз забота разработчика SDK. - - 4. Восстановление после ошибок в бизнес-логике, как правило, достаточно сложная операция, которую сложно описать в машиночитаемом виде. Разработчику клиента необходимо самому продумать эти сценарии. - - ``` - // Получаем предложения - const offers = await api - .search(…); - // Пользователь выбирает - // подходящее предложение - const selectedOffer = - await promptUser(offers); - let order; - let offer = selectedOffer; - let numberOfTries = 0; - do { - // Пытаемся создать заказ - try { - numberOfTries++; - order = await api - .createOrder(offer, …); - } catch (e) { - // Если количество попыток - // пересоздания заказа превысило - // какое-то разумное значение - // следует бросить попытки - // восстановиться - if (numberOfTries > TRY_LIMIT) { - throw new NoRetriesLeftError(); - } - // Если произошла ошибка - // «предложение устарело» - if (e.type == - api.OfferExpiredError) { - // если попытки ещё остались, - // пытаемся получить новое - // предложение - offer = await api - .renewOffer(offer); - } else { - // Обработка других видов - // ошибок - … - } - } - } while (!order); - ``` - - Как мы видим, простая операция «попробовать продлить предложение» выливается в громоздкий код, в котором легко ошибиться, и, что ещё важнее, который совершенно не нужен разработчику приложения. Было бы гораздо проще, если бы этой ошибки *вовсе не было в SDK*, т.е. попытки обновления и перезапросы выполнялись бы автоматически. - - Аналогичные ситуации возникают и в случае нестрого-консистентных API или оптимистичного управления параллелизмом — и вообще в любом API, в котором фон ошибок является ожидаемым (что в случае распределённых клиент-серверных API является нормой жизни). Передача версии ресурса и/или последних известных идентификаторов в эндпойнты с политикой read-your-writes — техническая необходимость, вызванная стремлением вендора API удешевить эксплуатацию и увеличить пропускную способность. Для разработчика приложения написание кода, имплементирующего эти политики — попросту напрасная трата времени. - - 5. Хранение данных в постоянной памяти (таких, как токены авторизации, ключи идемпотентности при перезапросах, идентификаторы черновиков при двухфазных коммитах и т.д.) также является ответственностью клиента и с трудом поддаётся формализации при кодогенерации. diff --git a/src/ru/drafts/06-Раздел V. SDK и UI-библиотеки/03.md b/src/ru/drafts/06-Раздел V. SDK и UI-библиотеки/03.md deleted file mode 100644 index ebb6d7b..0000000 --- a/src/ru/drafts/06-Раздел V. SDK и UI-библиотеки/03.md +++ /dev/null @@ -1,15 +0,0 @@ -### Кодогенерация - -Как мы убедились в предыдущей главе, список задач, стоящих перед разработчиком SDK (если, конечно, его целью является качественный продукт) — очень и очень значительный. Учитывая, что под каждую целевую платформу необходим отдельный SDK, неудивительно, что многие вендоры API стремятся полностью или частично заменить ручной труд машинным. - -Одно из основных направлений такой автоматизации — кодогенерация, то есть разработка технологии, которая позволяет по спецификации API сгенерировать готовый код SDK на целевом языке программирования для целевой платформы. Многие современные стандарты обмена данными (в частности, gRPC) поставляются в комплекте с генераторами готовых клиентов на различных языках; к другим технологиям (в частности, OpenAPI/Swagger) такие генераторы пишутся энтузиастами. - -Генерация кода позволяет решить типовые проблемы, описанные в предыдущей главе: стиль кодирования, обработка исключений, (де)сериализацию сложных типов — словом все те задачи, которые зависят не от предметной области, а от особенностей конкретной платформы. Относительно недорого разработчик API может дополнить такой автоматизированный «перевод» правильными настройками используемых системных средств: обеспечить автоматические перезапросы для идемпотентных эндпойнтов (с реализацией какой-то политики), кэширование результатов, сохранение данных (например, токенов авторизации) в системном хранилище и т.д. Такой сгенерированный SDK часто называют термином «клиент к API». - -Удобство использования и функциональные возможности кодогенерации столь привлекательны, что многие вендоры API только ей и ограничиваются, предоставляя свои SDK в виде сгенерированных клиентов. - -**NB**: напомним, что кодогенерация по спецификации, при всех её достоинствах, имеет один очень существенный недостаток: она искажает понятие обратной совместимости, поскольку вводит ещё одну прослойку между спецификацией и кодом, который пишет разработчик. В общем случае, гарантировать, что обратно-совместимое изменение спецификации не приведёт к обратно-несовместимому изменению клиента к API [т.е. к тому, что написанный когда-то разработчиком код поверх кодогенерированного клиента будет корректно работать с новой версией клиента] — достаточно нетривиальная задача, равно как такая гарантия отсутствует и при переходе от одной версии библиотеки кодогенерации к другой. Как минимум это означает, что сгенерированные клиенты должны интенсивно тестироваться с целью выявления непредвиденных ошибок. - -Как мы, однако, видим из предыдущей главы, проблемы более высокого порядка — получение серверных событий, обработка ошибок в бизнес-логике и т.п. — никак не может быть покрыта кодогенерацией, во всяком случае — стандартным модулем без его доработки применительно к конкретному API. В случае нетривиальных API со сложным основным циклом работы очень желательно, чтобы SDK решал также и высокоуровневые проблемы, иначе вы просто получите множество разработанных поверх API приложений, раз за разом повторяющие одни и те же «детские ошибки». Тем не менее, это не повод отказываться от кодогенерации полностью — её можно использовать как базис, на котором будет разработан высокоуровневый SDK. - -В соответствии с парадигмой «айсберга» (см. главу «О ватерлинии айсберга») доступ к функциональности сгенерированного клиента может быть скрыт (т.е. разработчики не будут иметь доступ к низкоуровневой работой с API), тем самым обеспечивая определённую свободу работы с API изнутри SDK, вплоть до бесшовного перехода на новые мажорные версии API. Этот подход, несомненно, предоставляет вендору API намного больше контроля над приложениями клиентов, но требует и намного больше ресурсов на разработку, и, что важнее, грамотного проектирования SDK — такого, чтобы у разработчиков не было необходимости обращаться к API напрямую в обход SDK по причине отсутствия в нём необходимых функций или их плохой реализации. \ No newline at end of file diff --git a/src/ru/drafts/06-Раздел V. SDK и UI-библиотеки/04.md b/src/ru/drafts/06-Раздел V. SDK и UI-библиотеки/04.md index 6d7b1c8..9576c06 100644 --- a/src/ru/drafts/06-Раздел V. SDK и UI-библиотеки/04.md +++ b/src/ru/drafts/06-Раздел V. SDK и UI-библиотеки/04.md @@ -67,7 +67,7 @@ const searchBox = new SearchBox({ }); return res; } -} +}) ``` *Формально* этот подход корректен и никаких рекомендаций не нарушает. Но с точки зрения связности кода, его читабельности — это полная катастрофа, поскольку следующий разработчик, которого попросят заменить иконку *кнопке*, очень вряд ли пойдёт читать код *функции поиска предложений*.
@@ -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. Преимущества и недостатки HTTP API в сравнении с альтернативными технологиямиГлава 35. Мифология RESTГлава 36. Составляющие HTTP запросов и их семантикаГлава 37. Организация HTTP API согласно принципам RESTГлава 38. Разработка номенклатуры URL ресурсов. CRUD-операцииГлава 39. Работа с ошибками в 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. Преимущества и недостатки HTTP API в сравнении с альтернативными технологиямиГлава 35. Мифология RESTГлава 36. Составляющие HTTP запросов и их семантикаГлава 37. Организация HTTP API согласно принципам RESTГлава 38. Разработка номенклатуры URL ресурсов. CRUD-операцииГлава 39. Работа с ошибками в 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. Управление ожиданиями § @@ -5464,7 +5464,7 @@ Retry-After: 5 и заложите защиту от этих векторов атак на уровне вашего серверного ПО. Организация OWASP предоставляет хороший обзор лучших security-практик для HTTP API.