1
0
mirror of https://github.com/twirl/The-API-Book.git synced 2025-01-05 10:20:22 +02:00

Стилистические правки

This commit is contained in:
Sergey Konstantinov 2015-04-05 14:06:32 +03:00
parent dceb85577e
commit 2932d03ab2

View File

@ -58,9 +58,9 @@
<p>API - это ничто иное, как средство связи контекстов. Например, когда вы реализуете какой-то алгоритм — вы используете API языка программирования. Компилятор языка программирования, в свою очередь, использует API команд процессора. Это API, в случае CISC-процессоров, в свою очередь построено поверх ещё более низкоуровнего API микрокоманд; и так далее. Только представьте себе, какое огромное количество API разных уровней должно отработать — и отработать правильно! — для того, чтобы вы смогли, скажем, увидеть страничку в браузере: тут и весь сетевой стек с его 6-7 уровнями абстракции; и серверное ПО — веб-сервер, генерация страницы, база данных, кэш; и клиентское ПО — браузер — имплементирующее буквально десятки разных API.</p>
<p>В современном мире роль API трудно преувеличить, и она продолжает расти. Всё новые и новые устройства — от часов и розеток до кораблей и ветряных турбин — включаются в «игру контекстов» и обзаводятся своим API. При этом, к сожалению, качество этих API всё больше оставляет желать лучшего.</p>
<p>В отличие от прикладного кода, которым будут пользоваться только ваши коллеги, разработка api подразумевает, что вашими программными интерфейсами будут пользоваться другие разработчики. Именно поэтому в разработке api проблемы, описанные в предыдущем разделе, стоят особенно остро. Хорошее api должно одновременно быть и качественно реализовано, и качественно спроектировано с точки зрения предоставляемых интерфейсов.</p>
<p>В отличие от прикладного кода, которым будут пользоваться только ваши коллеги, разработка API подразумевает, что вашими программными интерфейсами будут пользоваться другие разработчики. Именно поэтому в разработке API проблемы, описанные в предыдущем разделе, стоят особенно остро. Хорошее API должно одновременно быть и качественно реализовано, и качественно спроектировано с точки зрения предоставляемых интерфейсов.</p>
<p>Цель написания этой книги - посмотреть на разработку программного обеспечения под другим углом, дополнить известные принципы разработки программных продуктов принципами разработки api.</p>
<p>Цель написания этой книги - посмотреть на разработку программного обеспечения под другим углом, дополнить известные принципы разработки программных продуктов принципами разработки API.</p>
<h3>Об относительности критериев качества</h3>
<p>Несмотря на всё написанное выше, я вовсе не призываю вас подходить к каждой задаче с дополнительными наборами требований. Вовсе даже наоборот: я скорее призываю эти требования сокращать.</p>
@ -89,13 +89,13 @@
<p>Книга, которую вы держите в руках, состоит из трех больших разделов, назовем их условно "статическим", "динамическим" и "маркетинговым".</p>
<p>В первом разделе мы поговорим о проектировании api на стадии разработки концепции - как грамотно выстроить архитектуру, от крупноблочного планирования до конечных интерфейсов.</p>
<p>В первом разделе мы поговорим о проектировании API на стадии разработки концепции - как грамотно выстроить архитектуру, от крупноблочного планирования до конечных интерфейсов.</p>
<p>Второй раздел будет посвящён жизненному циклу api - как интерфейсы эволюционируют со временем и как развивать продукт так, чтобы отвечать потребностям пользователей.</p>
<p>Второй раздел будет посвящён жизненному циклу API - как интерфейсы эволюционируют со временем и как развивать продукт так, чтобы отвечать потребностям пользователей.</p>
<p>Наконец, третий раздел будет касаться больше не-разработческих сторон жизни api - поддержки, маркетинга, достижения контакта с аудиторией.</p>
<p>Наконец, третий раздел будет касаться больше не-разработческих сторон жизни API - поддержки, маркетинга, достижения контакта с аудиторией.</p>
<p>Первые два будут интересны скорее разработчикам, третий - и разработчикам, и менеджерам. При этом я настаиваю, что как раз третий раздел - самый важный для разработчика api. Ввиду того, что api - продукт для разработчиков, перекладывать ответственность за его маркетинг и поддержку на не-разработчиков неправильно: никто кроме вас самих не понимает так хорошо продуктовые свойства вашего api.</p>
<p>Первые два будут интересны скорее разработчикам, третий - и разработчикам, и менеджерам. При этом я настаиваю, что как раз третий раздел - самый важный для разработчика API. Ввиду того, что API - продукт для разработчиков, перекладывать ответственность за его маркетинг и поддержку на не-разработчиков неправильно: никто кроме вас самих не понимает так хорошо продуктовые свойства вашего API.</p>
<h2>Проектирование API</h2>
@ -108,17 +108,17 @@
<li>Описание конечных интерфейсов.</li>
</ul>
<p>Этот алгоритм строит api сверху вниз, от общих требований и сценариев использования до конкретной номенклатуры сущностей; фактически, двигаясь этим путем, вы получите на выходе готовое api - за что мы и ценим этот подход.</p>
<p>Этот алгоритм строит API сверху вниз, от общих требований и сценариев использования до конкретной номенклатуры сущностей; фактически, двигаясь этим путем, вы получите на выходе готовое API - за что мы и ценим этот подход.</p>
<p>Поскольку я являюсь убеждённым приверженцем практического подхода, здесь и далее я буду рассматривать проектирование api на конкретных примерах. В качестве такого примера я выбрал гипотетическое api некоей метеорологической службы. На всякий случай, предупрежу заранее, что пример является выдуманным от начала до конца; если бы мне предложили заниматься таким api в реальности - я бы прежде всего потратил время на детальное исследование предметной области, и наверняка пришёл бы совсем к другим решениям в итоге. Но для наших учебных целей такой умозрительный пример вполне допустим, поскольку я ставлю своей целью, прежде всего, объяснение того, как надо думать и не стремлюсь вложить в голову читателя какие-то конкретные решения и рецепты успеха.</p>
<p>Поскольку я являюсь убеждённым приверженцем практического подхода, здесь и далее я буду рассматривать проектирование API на конкретных примерах. В качестве такого примера я выбрал гипотетическое API некоей метеорологической службы. На всякий случай, предупрежу заранее, что пример является выдуманным от начала до конца; если бы мне предложили заниматься таким API в реальности - я бы прежде всего потратил время на детальное исследование предметной области, и наверняка пришёл бы совсем к другим решениям в итоге. Но для наших учебных целей такой умозрительный пример вполне допустим, поскольку я ставлю своей целью, прежде всего, объяснение того, как надо думать и не стремлюсь вложить в голову читателя какие-то конкретные решения и рецепты успеха.</p>
<h3>Определение области применения</h3>
<p>Первый вопрос, на который мы должны ответить, принимая решение о разработке api - "зачем?" Кто и как будет пользоваться нашей программной связью контекстов?</p>
<p>Первый вопрос, на который мы должны ответить, принимая решение о разработке API - "зачем?" Кто и как будет пользоваться нашей программной связью контекстов?</p>
<p>Рассмотрим следующий пример. Допустим, мы умеем предсказывать погоду и располагаем возможностью предоставлять данные о погоде в машиночитаемом виде. Стоит ли нам разрабатывать api? Давайте подумаем.</p>
<p>Рассмотрим следующий пример. Допустим, мы умеем предсказывать погоду и располагаем возможностью предоставлять данные о погоде в машиночитаемом виде. Стоит ли нам разрабатывать API? Давайте подумаем.</p>
<p>Во-первых, необходимо понять, какую такую функциональность мы можем предоставлять потребителям, что они воспользуются api, а не будут разрабатывать сами?</p>
<p>Во-первых, необходимо понять, какую такую функциональность мы можем предоставлять потребителям, что они воспользуются API, а не будут разрабатывать сами?</p>
<p>По возрастанию сложности самостоятельной реализации такого функционала это будут: получение информации о текущей погоде; получение информации о прогнозе; получение информации о климате какой-то местности; получение информации о "внутреннем устройстве" предметной области - зоны высокого и низкого давления, атмосферные фронты и их движение. </p>
@ -129,25 +129,25 @@
<p>Для удовлетворения первой группы потребностей потребители будут сами приходить на ресурсы, принадлежащие нашим потенциальным клиентам - популярные локальные веб-сайты (например, на городские порталы за краткосрочным прогнозом погоды или на туристические агрегаторы за информацией о климате) или разного рода приложения для "умных" устройств (телефонов, часов, планшетов, телевизоров и даже домов) или специализированные новостные ресурсы. Вторую группу потребностей будут закрывать компании, производящие программное обеспечение для диспетчерских служб - либо, возможно, проприетарные решения самих транспортных компаний.</p>
<p>Допустим мы исследовали и рынок, и собственные технические ресурсы и пришли к выводу, что (а) мы готовы эту функциональность предоставлять, (б) пользователям она необходима и (в) мы способны извлечь для себя какую-то выгоду из предоставления такого api (подробнее обо всем этом я расскажу в третьем разделе). Тем самым мы обозначили пункт "что" - какую функциональность мы физически можем предоставить и зачем.</p>
<p>Допустим мы исследовали и рынок, и собственные технические ресурсы и пришли к выводу, что (а) мы готовы эту функциональность предоставлять, (б) пользователям она необходима и (в) мы способны извлечь для себя какую-то выгоду из предоставления такого API (подробнее обо всем этом я расскажу в третьем разделе). Тем самым мы обозначили пункт "что" - какую функциональность мы физически можем предоставить и зачем.</p>
<p>Следующий этап - описание пользовательских сценариев. Ответив на вопросы "что" и "зачем", нужно теперь описать ответ на вопрос "как" - как потребители api будут им пользоваться.</p>
<p>Следующий этап - описание пользовательских сценариев. Ответив на вопросы "что" и "зачем", нужно теперь описать ответ на вопрос "как" - как потребители API будут им пользоваться.</p>
<p>Очевидное решение, которое приходит в голову сразу - это выполнить наше api в виде веб-сервиса: разработчик обращается по сформированному определённым образом url и получает погодную информацию. В общем-то, всю имеющуюся у нас информацию можно через такой сервис предоставлять; он будет универсален - такое обращение можно реализовать на любой платформе, и, таким образом, оно покроет все кейсы. Разве нет?</p>
<p>Очевидное решение, которое приходит в голову сразу - это выполнить наше API в виде веб-сервиса: разработчик обращается по сформированному определённым образом url и получает погодную информацию. В общем-то, всю имеющуюся у нас информацию можно через такой сервис предоставлять; он будет универсален - такое обращение можно реализовать на любой платформе, и, таким образом, оно покроет все кейсы. Разве нет?</p>
<p>Теперь давайте подойдём к вопросу с другой стороны. А как было бы удобно пользоваться нашим api, скажем, веб-мастеру-администратору небольшого локального форума? Скорее всего, ему требуется решение, которое не требует навыков разработки, чтобы было достаточно мышкой нащёлкать параметры информатора о прогнозе погоды. Нет, мы можем, конечно презрительно отмахнуться от таких непрофессионалов; но, тем самым, мы лишим сами себя значительной доли рынка.</p>
<p>Теперь давайте подойдём к вопросу с другой стороны. А как было бы удобно пользоваться нашим API, скажем, веб-мастеру-администратору небольшого локального форума? Скорее всего, ему требуется решение, которое не требует навыков разработки, чтобы было достаточно мышкой нащёлкать параметры информатора о прогнозе погоды. Нет, мы можем, конечно презрительно отмахнуться от таких непрофессионалов; но, тем самым, мы лишим сами себя значительной доли рынка.</p>
<p>Аналогично, если мы посмотрим на потребности крупных диспетчерских компаний, то мы, вероятно, выясним, что у них уже есть какое-то автоматизированной рабочее место диспетчера, и идеальным для них решением был бы набор компонентов, которые можно интегрировать в их программный комплекс. Аналогично, у новостных ресурсов также есть какое-то готовое решение для управления контентом, и они предпочтут не заказывать дорогостоящую разработку модуля визуализации погоды, а готовое решение.</p>
<p>Иными словами, в начале разработки api перед нами всегда стоит вопрос: насколько приближено оно должно быть к конечному пользователю? Чем более высок уровень вашего api, чем проще оно позволяет реализовать пользовательские сценарии - тем более интересно оно будет разработчикам, но тем дороже оно обойдётся вам самим. Понятно, что невозможно написать модули и компоненты для всех существующих cms и фреймворков, и вам придётся где-то остановиться - исходя из ваших представлений о массовости тех или иных кейсов. Вполне возможно, что в нашей гипотетической ситуации мы в итоге можем прийти к решению делать только http api, так как выясним, что получим от виджетов меньше дохода, чем потратим на разработку, а у каждого из крупных клиентов есть свой отдел разработки и они не доверяют в business-critical задачах разработкам сторонних компаний.</p>
<p>Иными словами, в начале разработки API перед нами всегда стоит вопрос: насколько приближено оно должно быть к конечному пользователю? Чем более высок уровень вашего API, чем проще оно позволяет реализовать пользовательские сценарии - тем более интересно оно будет разработчикам, но тем дороже оно обойдётся вам самим. Понятно, что невозможно написать модули и компоненты для всех существующих cms и фреймворков, и вам придётся где-то остановиться - исходя из ваших представлений о массовости тех или иных кейсов. Вполне возможно, что в нашей гипотетической ситуации мы в итоге можем прийти к решению делать только http API, так как выясним, что получим от виджетов меньше дохода, чем потратим на разработку, а у каждого из крупных клиентов есть свой отдел разработки и они не доверяют в business-critical задачах разработкам сторонних компаний.</p>
<p>Однако, чтобы нам было, о чем говорить в дальнейших разделах, предположим, что мы решили закрывать следующие сценарии:</p>
<ul><li>общее http api для клиентов, готовых вести разработку самостоятельно;</li>
<ul><li>общее http API для клиентов, готовых вести разработку самостоятельно;</li>
<li>набор виджетов для мобильных и веб-приложений;</li>
<li>набор компонентов для встраивания в АРМы диспетчерских компаний.</li></ul>
<p>Перейдём теперь непосредственно к проектированию api.</p>
<p>Перейдём теперь непосредственно к проектированию API.</p>
<h3>Уровни абстракции</h3>
@ -155,18 +155,18 @@
<p>Вспомним, что программный продукт - это средство связи контекстов, средство преобразования терминов и операций одной предметной области в другую. Чем дальше друг от друга эти области отстоят - тем большее число промежуточных контекстов можно ввести. Например, модель OSI, которую часто приводят как эталон разделения уровней абстракции, насчитывает семь промежуточных этапов по дороге от аппаратного обеспечения к протоколам уровня приложений.</p>
<p>Если говорить о разделении уровней абстракции именно с точки зрения api, то оно очень желательно по нескольким причинам:</p>
<p>Если говорить о разделении уровней абстракции именно с точки зрения API, то оно очень желательно по нескольким причинам:</p>
<ul><li>Разделение проекта на несколько независимых частей; архитектура каждой из них, таким образом, становится проще, и упрощается интеграция;</li>
<li>с помощью такого разделения гораздо легче добиваться кроссплатформенности путём отделения платформо-зависимой логики в отдельный уровень (или уровни) абстракции.</li></ul>
<p>И главное:</p>
<ul><li>Упрощается задача для ваших клиентов; правильно разделённые уровни абстракции означают, что разработчикам не придется разбираться со всей номенклатурой сущностей вашего api - им достаточно будет работать только с объектами высокого уровня, отвечающими непосредственно за решение их задач.</li></ul>
<ul><li>Упрощается задача для ваших клиентов; правильно разделённые уровни абстракции означают, что разработчикам не придется разбираться со всей номенклатурой сущностей вашего API - им достаточно будет работать только с объектами высокого уровня, отвечающими непосредственно за решение их задач.</li></ul>
<p>Если мы вернёмся к нашему примеру с погодным api, то увидим, что один уровень абстракции выделился автоматически: http api. Мы можем построить прочие виды api поверх базового http. Можем ли мы выделить ещё какие-то уровни, скажем, внутри самого http api?</p>
<p>Если мы вернёмся к нашему примеру с погодным API, то увидим, что один уровень абстракции выделился автоматически: http API. Мы можем построить прочие виды API поверх базового http. Можем ли мы выделить ещё какие-то уровни, скажем, внутри самого http API?</p>
<h4>Построение иерархии абстракций</h4>
<p>Как обычно, мы начнём с вопроса "зачем" - если у нас не получается строгая иерархия, зачем нужна нестрогая? В первую очередь для того, чтобы вашим разработчикам было удобнее пользоваться вашим api. Представим, что мы спроектировали вот такой интерфейс для нашего http api:</p>
<p>Как обычно, мы начнём с вопроса "зачем" - если у нас не получается строгая иерархия, зачем нужна нестрогая? В первую очередь для того, чтобы вашим разработчикам было удобнее пользоваться вашим API. Представим, что мы спроектировали вот такой интерфейс для нашего http API:</p>
<ul>
<li>GET /weather/temperature/{city_id} - возвращает текущую температуру в городе с указанным идентификатором;</li>
@ -176,26 +176,26 @@
<p>Покрывает ли такой интерфейс наши сценарии использования? С одной стороны кажется, что да - каждый потребитель сможет получить нужный ему набор информации.</p>
<p>Но, с другой стороны, будет ли удобно вебмастеру, который хочет просто установить на свой сайт "прогноз погоды", пользоваться таким api? Понятно, что ему не нужны по отдельности ни температура, ни давление - ему нужна ровно та информация, которую захочет увидеть пользователь его ресурса.</p>
<p>Но, с другой стороны, будет ли удобно вебмастеру, который хочет просто установить на свой сайт "прогноз погоды", пользоваться таким API? Понятно, что ему не нужны по отдельности ни температура, ни давление - ему нужна ровно та информация, которую захочет увидеть пользователь его ресурса.</p>
<p>Оптимально было бы ввести ещё один метод:</p>
<ul><li>GET /weather/{city_id} - возвращает параметры погоды в указанном городе: температуру, давление, прогноз.</li></ul>
<p>Этот новый ресурс позволит пользователям api оперировать не в метеорологических терминах - а в терминах задачи, которую они решают. Тем самым мы спроектировали интерфейс высокого уровня, опирающийся на интерфейсы нижнего уровня абстракции.</p>
<p>Этот новый ресурс позволит пользователям API оперировать не в метеорологических терминах - а в терминах задачи, которую они решают. Тем самым мы спроектировали интерфейс высокого уровня, опирающийся на интерфейсы нижнего уровня абстракции.</p>
<p>Аналогично, если мы будем проектировать api для новостных ресурсов, то придём к необходимости выделения сущности "картина атмосферных фронтов"; при проектировании api для диспетчерских служб - к сущности "план опасных для полета зон", и так далее.</p>
<p>Аналогично, если мы будем проектировать API для новостных ресурсов, то придём к необходимости выделения сущности "картина атмосферных фронтов"; при проектировании API для диспетчерских служб - к сущности "план опасных для полета зон", и так далее.</p>
<p>Разделение уровней абстракции должно происходить вдоль трёх направлений:</p>
<ul><li>от сценариев использования к их внутренней реализации: высокоуровневые сущности и номенклатура их методов должны напрямую отражать сценарии использования api; низкоуровневый - отражать декомпозицию сценариев на составные части;</li>
<ul><li>от сценариев использования к их внутренней реализации: высокоуровневые сущности и номенклатура их методов должны напрямую отражать сценарии использования API; низкоуровневый - отражать декомпозицию сценариев на составные части;</li>
<li>от терминов предметной области пользователя к терминам предметной области исходных данных - в нашем случае от высокоуровневых понятий "прогноз погоды" и "движение атмосферных фронтов" к базовым "температура", "давление", "скорость ветра";</li>
<li>наконец, от структур данных, в которых удобно оперировать пользователю к структурам данных, максимально приближенных к "сырым" - в нашем случае от "атмосферных фронтов" и "магнитных бурь" - к сырым байтовый данным, описывающим поле атмосферного давления или магнитное поле Земли.</li>
</ul>
<p>Чем дальше находятся друг от друга программные контексты, которые соединяет наше api - тем более глубокая иерархия сущностей должна получиться у нас в итоге. Обращаю ваше внимание, что иерархия не должна быть самоцелью; цель - введение промежуточных ступеней погружения в тонкости предметной области. Избыточная сложность здесь не менее вредна, чем избыточная простота.</p>
<p>Чем дальше находятся друг от друга программные контексты, которые соединяет наше API - тем более глубокая иерархия сущностей должна получиться у нас в итоге. Обращаю ваше внимание, что иерархия не должна быть самоцелью; цель - введение промежуточных ступеней погружения в тонкости предметной области. Избыточная сложность здесь не менее вредна, чем избыточная простота.</p>
<h3>Уровни абстракции и декомпозиция</h3>
<p></p>Приближённость к сценариям использования - не единственная польза правильно построенной иерархии абстракций. Другим критерием качества архитектуры api здесь является снижение связности и декомпозиция объектов api.</p>
<p></p>Приближённость к сценариям использования - не единственная польза правильно построенной иерархии абстракций. Другим критерием качества архитектуры API здесь является снижение связности и декомпозиция объектов API.</p>
<p>Рассмотрим следующий пример организации функциональности карты атмосферных фронтов.</p>
@ -211,13 +211,13 @@
<p>Однако, очевидно, с точки зрения иерархии здесь допущена ошибка: высокоуровневые сущности (карта, атмосферные фронты и области повышенного и пониженного давления) связаны посредством технической сущности - визуальной геометрии объекта, которая по всем критериям должна находиться где-то в самом низу иерархии. Чем это может быть плохо для нас?</p>
<p>Предположим, завтра у нас появится клиент, которого не устраивает векторное описание геометрии, и которому нужна растровая картинка. Мы окажемся в сложном положении: либо мы создаем параллельное api именно для работы с растром, либо клиенту придётся забирать и растровую картинку, и векторную - вторую только для того, чтобы через нее получить идентификаторы погодных объектов. Если бы иерархия была выстроена правильно (карта погоды отдает идентификаторы объектов, а уже по идентификаторам можно получить геометрии) - достаточно было бы добавить режим "растровое изображение".</p>
<p>Предположим, завтра у нас появится клиент, которого не устраивает векторное описание геометрии, и которому нужна растровая картинка. Мы окажемся в сложном положении: либо мы создаем параллельное API именно для работы с растром, либо клиенту придётся забирать и растровую картинку, и векторную - вторую только для того, чтобы через нее получить идентификаторы погодных объектов. Если бы иерархия была выстроена правильно (карта погоды отдает идентификаторы объектов, а уже по идентификаторам можно получить геометрии) - достаточно было бы добавить режим "растровое изображение".</p>
<p>Аналогичные проблемы нас ждут, если в какой-то момент появятся объекты, содержащие более одной геометрии - либо, наоборот, несколько объектов научатся делить одну геометрию. В правильной архитектуре мы просто добавим новые типы ресурсов; в неправильной нам вообще не удастся решить эту ситуацию без слома обратной совместимости.</p>
<p>Оба этих примера эксплуатируют одну и ТЦ же мысль: в иерархии уровней абстракции верхние уровни являются, как правило, более устойчивыми к разного рода изменениям. В самом деле, если объекты верхнего уровня описывают сценарии использования - они гораздо менее вероятно изменятся с развитием технологии. Потребность "мне нужно узнать прогноз погоды на завтра" никак не меняется уже много тысяч лет.</p>
<p>Чем универсальнее ваше api в смысле широты тех контекстов, которые вы связываете, тем больше альтернативных реализаций одного и того функционала вам придётся реализовать (см. Пример с растровыми и векторными изображениями) и тем "шире" окажется ваша иерархия уровней абстракции: у вас появится множество объектов, принадлежащих одному уровню; в достаточно большом api неизбежно возникнут поддеревья иерархии - каждая реализация будет опираться на свой, полностью или частично, независимый слой логики.</p>
<p>Чем универсальнее ваше API в смысле широты тех контекстов, которые вы связываете, тем больше альтернативных реализаций одного и того функционала вам придётся реализовать (см. Пример с растровыми и векторными изображениями) и тем "шире" окажется ваша иерархия уровней абстракции: у вас появится множество объектов, принадлежащих одному уровню; в достаточно большом API неизбежно возникнут поддеревья иерархии - каждая реализация будет опираться на свой, полностью или частично, независимый слой логики.</p>
<h3>Изоляция уровней абстракции</h3>
@ -249,11 +249,11 @@
<p>Чем же нам грозит подобный неправильный интерфейс? Давайте разберёмся.</p>
<p>Мы заставляем высокоуровневый объект преобразовывать геокоординаты в пиксельные. Очевидно, для этого source должен будет обратиться в map и выяснить множество информации, совершенно source ненужной - проекцию и масштаб карты, например. Таким образом, в нашей схеме source обладает знанием о всех прочих объектах нашего api - map, vehicle, overlay.</p>
<p>Мы заставляем высокоуровневый объект преобразовывать геокоординаты в пиксельные. Очевидно, для этого source должен будет обратиться в map и выяснить множество информации, совершенно source ненужной - проекцию и масштаб карты, например. Таким образом, в нашей схеме source обладает знанием о всех прочих объектах нашего API - map, vehicle, overlay.</p>
<p>Чем плоха такая сильная связность? Перечислим основные неприятности, которые поджидают нас на этом пути.</p>
<p>Во-первых, мы задумали source как объект доступа к пользовательским данным. Так как у каждого пользователя обязательно будет своя специфика доступа к данным, то ситуации, когда пользователю нужно будет написать полностью или частично собственную реализацию source, будут возникать регулярно. Прикиньте теперь объём кода, который для этого потребуется: вместо того, чтобы написать адаптер для выдачи пар идентификатор-координаты, пользователю нужно будет реализовать взаимодействие с каждым компонентом api!</p>
<p>Во-первых, мы задумали source как объект доступа к пользовательским данным. Так как у каждого пользователя обязательно будет своя специфика доступа к данным, то ситуации, когда пользователю нужно будет написать полностью или частично собственную реализацию source, будут возникать регулярно. Прикиньте теперь объём кода, который для этого потребуется: вместо того, чтобы написать адаптер для выдачи пар идентификатор-координаты, пользователю нужно будет реализовать взаимодействие с каждым компонентом API!</p>
<p>В той же ситуации окажемся и мы, если решим сами реализовать какие-то типовые варианты source. В лучшем случае нам придется заняться редактором для выделения общих компонент по взаимодействию с другими сущностями, в худшем - пользоваться методом copy-paste.</p>
@ -271,7 +271,7 @@
<li>чрезмерное усложнение реализации альтернативных вариантов чересчур сильно связанных сущностей;</li>
<li>распространение ошибок по связанным сущностям и общее снижение отказоустойчивости системы;</li>
<li>усложнение работы с системой для внешних разработчиков, необходимость обладания экспертизой в областях, вообще говоря, напрямую не связанных с решаемой задачей;</li>
<li>усложнение тестирования кода как самого api, так и написанного поверх него;</li>
<li>усложнение тестирования кода как самого API, так и написанного поверх него;</li>
<li>необходимость постоянных рефакторингов и копи-паста кода.</li>
</ul>
@ -287,15 +287,15 @@
<li>Overlay - нижнего.</li>
</ul>
<p>Каким же образом нам нужно организовать связи между этими объектами так, чтобы, с одной стороны, позволить нашему api быть максимально гибким и одновременно избежать проблем сильной связанности объектов?</p>
<p>Каким же образом нам нужно организовать связи между этими объектами так, чтобы, с одной стороны, позволить нашему API быть максимально гибким и одновременно избежать проблем сильной связанности объектов?</p>
<p>Для этого необходимо, в первую очередь, определить ответственность каждой сущности: в чём смысл её существования в рамках нашего api, какие действия объект должен уметь выполнять сам а какие - делегировать другим объектам. Фактически, нам нужно применить "зачем-принцип" к каждой отдельной сущности нашего api.</p>
<p>Для этого необходимо, в первую очередь, определить ответственность каждой сущности: в чём смысл её существования в рамках нашего API, какие действия объект должен уметь выполнять сам а какие - делегировать другим объектам. Фактически, нам нужно применить "зачем-принцип" к каждой отдельной сущности нашего API.</p>
<p>Из предыдущей главы мы выяснили, например, что ответственностью source является возврат пар идентификатор-координаты, и ничего более. Попробуем воспроизвести подобные рассуждения в отношении других объектов.</p>
<p>Для этого нам нужно пройти по нашему api и сформулировать в терминах предметной области, что представляет из себя каждый объект. Напомню, что из концепции уровней абстракции следует, что каждый уровень иерархии - это некоторая собственная промежуточная предметная область, ступенька, по которой мы переходим от описания задачи в терминах одного связываемого контекста ("карта погоды с движущимися по ней транспортными средствами") к описанию в терминах второго ("SVG-объект, заданный в вычисляемых извне пиксельных координатах").</p>
<p>Для этого нам нужно пройти по нашему API и сформулировать в терминах предметной области, что представляет из себя каждый объект. Напомню, что из концепции уровней абстракции следует, что каждый уровень иерархии - это некоторая собственная промежуточная предметная область, ступенька, по которой мы переходим от описания задачи в терминах одного связываемого контекста ("карта погоды с движущимися по ней транспортными средствами") к описанию в терминах второго ("SVG-объект, заданный в вычисляемых извне пиксельных координатах").</p>
<p>В этом смысле проще всего описать объект Vehicle - это программная сущность, представляющая собой абстракцию над объектом реального мира. Её задачей, очевидно, является описание характеристик реального объекта, которые нужны для решения поставленных задач. В нашем случае это идентификатор (или иной способ опознания объекта) и его географическое положение. Если в нашем api появятся какие-то расширенные сведения об реальном объекте - скажем, название или скорость, - очевидно, они будут привязаны к тому же объекту Vehicle.</p>
<p>В этом смысле проще всего описать объект Vehicle - это программная сущность, представляющая собой абстракцию над объектом реального мира. Её задачей, очевидно, является описание характеристик реального объекта, которые нужны для решения поставленных задач. В нашем случае это идентификатор (или иной способ опознания объекта) и его географическое положение. Если в нашем API появятся какие-то расширенные сведения об реальном объекте - скажем, название или скорость, - очевидно, они будут привязаны к тому же объекту Vehicle.</p>
<p>Кроме того, ещё одной задачей Vehicle является описание собственного представления в интерфейсе - скажем, параметров иконки, если наш Vehicle должен выглядеть для пользователя интерфейса как значок на карте.</p>
@ -305,7 +305,7 @@
<p>Если внимательно посмотреть на каждый объект, то мы увидим, что, в итоге, каждый объект оказался в смысле своей ответственности составным: Vehicle одновременно "знает" и про объект реального мира, и про его виртуальное отображение на карте; карта "знает" свою область картографирования - фактически, область на реальной Земле, которую схематически отображает, - и при этом должна предоставлять некоторый контекст для отображения виртуальных графических фигур, и так далее.</p>
<p>Ничего удивительного в этом, конечно же, нет — поскольку api в целом связывает разные контексты, в нём всегда будут объекты, объединяющие термины разных предметных областей. Наша задача - декомпозировать объекты так, чтобы, с одной стороны, разработчикам было удобно и понятно пользоваться нашей иерархией абстракций, а нам, с другой стороны, было удобно такую архитектуру поддерживать.</p>
<p>Ничего удивительного в этом, конечно же, нет — поскольку API в целом связывает разные контексты, в нём всегда будут объекты, объединяющие термины разных предметных областей. Наша задача - декомпозировать объекты так, чтобы, с одной стороны, разработчикам было удобно и понятно пользоваться нашей иерархией абстракций, а нам, с другой стороны, было удобно такую архитектуру поддерживать.</p>
<h3>Декомпозиция интерфейсов</h3>
@ -326,57 +326,15 @@
<p>Объект Map в этом случае реализует оба интерфейса, однако связанные сущности работают уже не с конкретным объектом Map, а с некоторой реализацией абстрактного интерфейса, ничего не зная о прочих свойствах этого объекта.</p>
<p>NB. Во многих языках программирования нет поддержки интерфейсов, абстрактных классов и/или множественного наследования. Однако выделять интерфейсы нам это не мешает, поскольку мы всегда можем "договориться", что объект source имеет право пользоваться только вот этим набором свойств и методов. Конечно, контролировать соблюдение этой договоренности в достаточно развесистом api довольно сложно, но, поверьте автору, вполне возможно, тем более, что тестами и/или статическим анализом кода соблюдение договоренностей об интерфейсах можно проверить почти всегда.</p>
<p>NB. Во многих языках программирования нет поддержки интерфейсов, абстрактных классов и/или множественного наследования. Однако выделять интерфейсы нам это не мешает, поскольку мы всегда можем "договориться", что объект source имеет право пользоваться только вот этим набором свойств и методов. Конечно, контролировать соблюдение этой договоренности в достаточно развесистом API довольно сложно, но, поверьте автору, вполне возможно, тем более, что тестами и/или статическим анализом кода соблюдение договоренностей об интерфейсах можно проверить почти всегда.</p>
<p>Разделение контекстов - не единственная причина, по которой выделение интерфейсов критически важно при проектировании api. Предъявление к входящим параметрам требования только удовлетворять интерфейсу существенно упрощает создание альтернативных реализаций ваших объектов, в том числе в целях тестирования. Теперь чтобы протестировать объект source достаточно написать mock на IGeoContext, а не весь класс map целиком. Аналогично, если мы захотим использовать наши оверлеи для показа их, скажем, в качестве какой-то инфографики или на абстрактном плане местности, нам не придётся переделывать для этого класс Map - достаточно будет альтернативной реализации IGraphicalContext.</p>
<p>Разделение контекстов - не единственная причина, по которой выделение интерфейсов критически важно при проектировании API. Предъявление к входящим параметрам требования только удовлетворять интерфейсу существенно упрощает создание альтернативных реализаций ваших объектов, в том числе в целях тестирования. Теперь чтобы протестировать объект source достаточно написать mock на IGeoContext, а не весь класс map целиком. Аналогично, если мы захотим использовать наши оверлеи для показа их, скажем, в качестве какой-то инфографики или на абстрактном плане местности, нам не придётся переделывать для этого класс Map - достаточно будет альтернативной реализации IGraphicalContext.</p>
<p>При выделении интерфейсов важно также понимать, что интерфейс, в отличие от его реализации, должен быть минимально достаточным и не должен включать в себя вспомогательные методы. Например, если класс map имеет как метод для получения всей области картографирования в виде четырехугольника getBBox, так и методы получения каждого из углов по отдельности - getLeftBottom, getRightTop, например, — то интерфейс IGeoContext должен содержать что-то одно. Нет никакого смысла загромождать интерфейс альтернативными реализациями одной и той же функциональности — это затрудняет чтение и усложняет написание собственных реализаций. Если только нет каких-то показаний с точки зрения производительности, следует отдать предпочтение максимально общему методу - в нашем случае getBBox.</p>
<!--
<h2>О проектировании API</h2>
<h3>Архитектура против реализации</h3>
<p>Поскольку API — обязательство, то к качеству его реализации предъявляются, как правило, повышенные требования — во-первых, в силу важности API как инструмента для бизнес-логики потребителей; во-вторых, в силу того, что ошибка в API может привести к гораздо более разрушительным последствиям, чем ошибка в end-user интерфейсе.</p>
<p></p>
<p>Попробуем объяснить на конкретном примере. Допустим, мы располагаем огромным и хорошо структурированным сервисом, предоставляющем метаинформацию, скажем, о . (Лично я никогда не делал подобных сервисов, но уже много лет испытываю в них нужду.)</p>
<p><em>Зачем</em> такому сервису API?</p>
<p>Чтобы люди могли написать свои новые-кленовые аудиоплееры, использующие ваш сервис? Вряд ли.</p>
- решать проблемы
- не делать конкурентов самому себе (если ты не амазон)
- множество разнородных задач:
@ -384,22 +342,7 @@
- сделать хорошо разработчику
<h3>О неважности кода</h3>
- в правильном апи любой код заменяем и он неважен
<h3>Уровни абстракции</h3>
< p>Концепция уровней абстракции довольно широко известна. Классическим примером разделения уровней является, скажем, модель OSI, описывающая 7 уровней сетевых взаимодействий.</p>
<p>К сожалению, в реальных системах выделить уровни абстракции гораздо сложнее, нежели в таких чистых случаях; множество успешных и существующих API и вовсе не оперируют такими терминами. Классический случай — веб-платформа, в которой правильное разделение абстракций фактически никогда не существовало, и лишь Extensible Web Manifesto впервые постулировал необходимость не экстенсивно наращивать номенклатуру методов, а строить иерархию абстракций.</p>
<p>Связано это, по-видимому, со сложностью подобного разделения в абсолютном большинстве предметных областей. Классический алгоритм «берём все объекты и разбиваем их на слои» в реальном мире работает очень слабо: всегда есть сущности, которые не ложатся в иерархию, будучи либо чересчур глобальными, либо, напротив, чересчур утилитарными.</p>
<p>Тем не менее, говорить об иерархии абстракций можно и нужно.</p>
<h4>Иерархия кейсов</h4>
<p>Самый простой из критериев деления уровней абстракции — это отделение сущностей, работающих в терминах, понятных конечному пользователю, от сущностей, понятных лишь разработчику. Такое дерево объектов можно построить даже для столь неструктурированной платформы, как веб: высший уровень абстракции реализуют те объекты, которые напрямую взаимодействуют с пользователем — скажем, интерфейс HTMLAudioElement. Методы, как play(), pause(), свойство controls — напрямую соответствуют конкретным кейсам использования сущности пользователем. Напротив, базовые сущности низкоуровневого WebAudio API — аудио буферы и потоки данных — для непрограммиста никакого интереса не представляют.</p>
<p>«Абстрактность» при таком подходе означает иерархию кейсов: есть набор кейсов пользователя (проиграть видео, поставить на паузу, прибавить громкость), которые покрываются сущностями верхнего уровня; однако верхний уровень иерархии, таким образом, сам предъявляет некоторый набор кейсов: обеспечить воспроизведение из некоторого источника, обеспечить микширование данных, обеспечить синхронизацию, встроить плеер в определённое место на странице — которым будет удовлетворять следующий «слой» иерархии — аудиоконтексты в WebAudio API и методы работы с DOM-деревом. Процесс, таким образом, можно повторять итеративно вплоть до «железа».</p>
<p>Что важно, этот подход работает не только для API, предоставляющих некоторые интерфейсы взаимодействия с конечным пользователем. Даже API доступа к базе данных, таким образом, хорошо разбивается на уровни абстракции, исходя из кейсов использования API.</p>
<h4>Иерархия знаний</h4>
<p>Уровни абстракций можно выделять и снизу вверх: самыми низкоуровневыми сущностями объявляются те, которые работают в терминах нижележащей платформы. Интерфейсы, оперирующие байтами, буферами и флагами — самые примитивные. Чем более специфичный, т.е. требующий знаний об устройстве платформы, код требуется написать — тем ниже уровень абстракции. Напротив, объекты, интерфейс которых не содержит никаких знаний о среде исполнения, образуют верхний уровень. В том же примере с аудио в вебе: сущности, работающие напрямую с «сырыми» данными — аудио буферы — низкоуровневые. Сущности, оперирующие буферами — различные операции над звуком — стоят чуть выше: чтобы работать с ними, уже не нужно знать о разрядности или частоте дискретизации. Ещё чуть выше — функциональность группировки операций и аудио контексты; и так далее. На вершине пирамиды — HTMLAudioElement, интерфейс которого не содержит никаких знаний и легко может быть быть применён не к воспроизведению звука в вебе, а, скажем, к API патефона, если кто-то вдруг задумает его сделать.</p>
<p>Иными словами, тот объект выше, интерфейс которого не поменяется, если вся нижележащая логика полностью изменится вплоть до смены платформы и физических принципов работы.</p>
<h3>Информационный контекст</h3>
<p>Как альтернативу традиционному разделению на уровни абстракции можно рассмотреть вашу иерархию сущностей с точки зрения информации, которая через эту иерархию протекает. Любое API, в конечном счёте, предоставляет доступ к некоторой информации.</p>
<p></p>
<h3>Code style</h3>
<h3>Code style</h3>
— "так здесь принято" vs здравый смысл
<h3>Именование</h3>
<p>Если говорить и чисто программной составляющей API, то, пожалуй, именно именование важнее всего. Всё остальное, в конечном счёте, можно подправить или переписать, но вот номенклатура сущностей — это лицо вашего API, это фасад, с которым будет работать ваш пользователь.</p>