Многие думают, что создание API — специфичный процесс, и понимать его нужно лишь тем, кто профессионально занимается разработкой API-проектов. Но в реальности все мы так или иначе пишем API: для новых классов, сервисов, для коллег или заказчиков.
Под катом мы постарались подобрать для вас несколько практических советов о том, как создавать дизайн веб API, планировать изменения, управлять версионированием и взаимодействовать с пользователями API. Мы постарались выяснить, какие сложности могут возникнуть и какие типичные ошибки обычно совершаются. А также прочитать о различных интересных фактах из истории и об ожидаемых изменениях в будущем.
Наш сегодняшний собеседник Дилан Битти (Dylan Beattie) имеет высокую репутацию на StackOverflow, а значит умеет хорошо, интересно и, что немаловажно, правильно отвечать на вопросы. Большой опыт позволяет ему на собственных примерах рассказать что-то из прошлого, а профессия архитектора держит в тонусе и приносит новые знания о трендах и передовых технологиях.
– Какие API вы разрабатываете? В какой именно момент дизайн API вдруг переходит в разработку ПО?
Дилан Битти: Это очень интересный вопрос, потому как я думаю, что одним из самых больших заблуждений в сфере разработки софта является то, что дизайн API — это что-то, что происходит отдельно от всего остального. Конечно же, есть определенные типы API-проектов, такие как HTTP APIs, которые будут открыты публично — и здесь есть смысл рассматривать дизайн как отдельную часть работы. Но правда в том, что большинство разработчиков, собственно, создает API постоянно, — они просто не осознают сами, что они это делают. Каждый раз, когда вы пишете public method в одном из ваших классов или выбираете имя для таблицы базы данных, вы создаете интерфейс — в обычном ежедневном английском значении этого слова — это то, что в конце концов будет использоваться другими разработчиками в определенный момент времени в будущем. Другие люди вашей команды будут использовать ваши классы и методы. Другие команды будут использовать вашу схему данных или формат сообщений.
Что интересно, так это то, что если разработчики понимают, что код, над которым они работают, скорее всего станет частью API, то их слишком сильно клонит в одну и ту же сторону. Они начинают реализовывать edge case-ы и всякие другие штуки, которые особо им и не нужны, просто потому, что они могут понадобится кому-нибудь после. Я думаю, что тут нужно тонко чувствовать грань, и мне кажется, что ключ к пониманию этой грани в том, что нужно быть предельно конкретным в создании набора функций, который вам необходим именно сейчас. Но важно делать этот набор насколько только возможно повторно применяемым и самоописываемым. У Pieter Hintjens есть хорошее эссе под названием Ten Rules for Good API Design, которое дает более подробное представление об идеях подобного типа.
Самый большой API-проект, над которым я работаю на данный момент, это тот, который я делаю в английской компании Spotlight. Это гипермедийное API, раскрывающее различную информацию о профессиональных актерах. Предложения работы в актерских проектах, в кино и на телевидении и различные другие данные, используемые в индустрии кастинга. Мы строим этот API на архитектурном стиле, известном как REST, — и если вы не совсем в курсе, что такое REST, то вы просто обязаны прийти на мое выступление на конференции DotNext в Санкт-Петербурге и узнать об этом.
Существуют различные паттерны для создания HTTP API — есть REST, есть GraphQL, есть такие штуки, как SOAP и RPC. Но для меня самая большая привлекательность REST состоит в том, что ограничения RESTful-стиля ведут к натуральному ослаблению связей в концептах и операциях, которые ваш API должен поддерживать, что позволяет с большей легкостью вносить изменения и развивать дизайн в течение продолжительного времени.
– Одно из самых известных программ, «убитых» обратной совместимостью, это IE. Этот браузер имел слишком много приложений, для которых необходимо было сохранять совместимость с предыдущими версиями. Проблема была решена простым добавлением нового приложения под названием Edge, которое обновляемо и поддерживает все новые стандарты. Как не попасть в ловушку обратной совместимости? Может быть, стоит использовать модульность, которая в свою очередь не использует слои? Или, может быть, как-то заменить API с помощью RESTful API, Service Oriented Architecture или чего-либо еще?
Дилан Битти: Я начал создавать веб-приложения давно. Свою первую страницу написал за пару лет до появления Internet Explorer. Тогда, когда единственными браузерами были NCSA Mosaic и Erwise. Это завораживает — смотреть назад в историю веба и осознавать то, что веб, который существует сейчас, был смоделирован и подвержен влиянию таких вещей как Internet Explorer. И вы совершенно правы; одна из причин, почему Microsoft представила полностью новый браузер Edge в последних версиях Windows, это то, что обязательства Internet Explorer поддерживать обратную совместимость сделали работу по реализации новых веб-стандартов на существующей базе кода IE очень сложной.
Часть причин, по которым эта обратная совместимость в IE существует, заключается в том, что около 2000-ого года произошел небольшой сдвиг в способе разработки корпоративных IT-систем.
Существует бесчисленное количество компаний, у которых есть собственные приложения для различных видов бизнес-операций: контроля акционерного капитала, учета товара, HR, менеджмента проектов и других. В 1980-х и ранних 1990-х большинство из них использовали систему с центральным мейнфреймом, и сотрудники применяли что-то вроде терминального эмулятора, чтобы подключится к центральному серверу. Но после первой волны доткомов в конце 1990-х компании осознали, что у большинства их компьютеров сейчас есть веб-браузер и подключение к сети, и они могут заменить старые терминальные приложения мэйнфрейма новыми веб-приложениями.
Windows на тот момент занимала колоссальную долю рынка, и Internet Explorer был браузером по-умолчанию на большинстве Windows PC, так что многие организации построили intranet-приложения, которые работали только с определенной версией Internet Explorer. Иногда они делали это для того, чтобы получить преимущества специфических фич, таких как ActiveX-поддержка; но чаще, я думаю, они делали это, просто чтобы сэкономить деньги за счет того, что им не нужно было проводить кросс-браузерное тестирование. Так произошло и с некоторыми довольно большими коммерческими приложениями; даже в 2011 году Microsoft Dynamics CRM все еще не поддерживал никакие другие браузеры кроме Internet Explorer.
Таким образом и у нас появилось большое количество компаний, которые инвестировали время и деньги в создание приложений, работающих с Internet Explorer. Эти приложения не были созданы с использованием веб-стандартов или прогрессивного улучшения или же с какой-либо попыткой создать совместимость с последующими версиями — они явным образом разрабатывались для одной конкретной версии браузера, работающего на операционной системе. И каждый раз, когда Microsoft выпускала новую версию Internet Explorer, эти приложения «падали» — а компании не хотели инвестировать в обновление их устаревших интранет-приложений и винили во всем браузер. Но история не закончена: сейчас в 2017 году Microsoft все еще поставляет IE11, у которого есть режим совместимости, где он переключается к движку IE9, но при этом отправляет user agent string, что он IE7. Сейчас все, кого я знаю, используют Google Chrome или Safari для серфинга в сети, но при этом на рабочем столе у многих все еще «живет» ярлык IE, через который можно войти в одну из legacy-систем.
Итак… возвращаясь к вопросу о том, как Microsoft мог бы избежать этой ловушки. Я думаю, что возможностей было много. Как вариант — изначально создавать IE, используя модульный движок рендеринга, так чтобы последующие версии могли выборочно запускать соответствующий движок для прорисовки конкретного веб-сайта или приложения. Они могли бы приложить больше усилий для поддержки веб-стандартов, которые существовали на тот момент, вместо того чтобы реализовывать ad-hoc поддержку таких вещей, как MARQUEE тэг и ActiveX плагины — это помогло бы впоследствии избежать головной боли при поддержке этих эзотерических фич в новых версиях. Но тогда все это ничего не значило. При разработке первых версий Internet Explorer важно было не создать великолепное приложение с первоклассной поддержкой веб-стандартов, а убить Netscape Navigator и выиграть в разделе рынка. И это сработало.
– Предположим, что некто собирается представить новый API. Он собирает некоторые требования, предлагает версию и получает feedback. Процесс выглядит нехитрым и простым. Но есть ли какие-то подводные камни на пути?
Дилан Битти: Всегда! Требования будут изменены — это факт. Одна из самых больших ошибок, которую вы можете совершить, — попробовать предвидеть эти изменения и сделать свой дизайн «future-proof». Теоретически это может окупиться, но чаще всего вы оказываетесь с еще более сложным дизайном на руках, поскольку старались заложить в него какие-либо будущие изменения. Часто подводные камни — это то, что находится вне вашего контроля. Например, изменится закон, и вам потребуется иначе предоставить данные. Или произойдут изменения в других системах вашей организации. Или один из ваших облачных хостинг-провайдеров заявит, что они перевели какую-то необходимую вам функцию в разряд устаревших.
Лучше всего выбрать из возможных вариантов интерфейса что-то простое и пригодное к использованию, сделать и сдать это. Чтобы максимально быстро перейти к этапу, когда ваш API работает стабильно, нет никакого внешнего технического долга, и команда может двигаться к следующей задаче. Так что если вы после этого вдруг столкнетесь с какой-либо непредвиденной проблемой, то у вас при этом будет стабильная кодовая база, готовая к использованию в вашем решении, и команда, у которой будет время и желание работать для того, чтобы все решить. А если по какому-нибудь стечению обстоятельств вам не попадется никакой подводный камень, вы сможете просто перейти на следующий пункт в своем бэклоге.
– Вот мы выпустили версию v1.0 нашего API и на подходе v1.1. Наверное, многие из нас создадут и http://example.com/v1/test, и http://example.com/v1.1/test или что-то в этом роде. Какие практики, по вашему мнению, могут помочь разработчику сделать дизайн v1.1 API лучшим по отношению к v1.0?
Дилан Битти: Было бы хорошо прочитать о концептах семантического версионирования (SemVer) и потратить время на то, чтобы действительно понять различия между major, minor и patch версиями. SemVer говорит, что у вас не должно быть никаких критических изменений между версиями 0.x и 0.1, так что самая важная часть — это понять что будет являться критическим изменением для вашего конкретного API.
Если вы работаете с HTTP API, которые возвращают JSON, например, то типичным не критическим изменением будет добавление нового поля данных в один из ваших ресурсов. Ожидается, что клиенты, которые используют версию 1.1, увидят дополнительное поле и смогут получить все преимущества от этого, в то время как клиенты, которые все еще используют версию 1.0, смогут не учитывать нераспознанное свойство. Это еще один довольно близкий вопрос о том, как вы должны управлять версионированием ваших API. Одно из самых популярных решений — представлять URL через роутинг — api.example.com/v1/ вместо api.example.com/v1.1
Но если вы соблюдаете ограничения RESTful систем, то вам необходимо понять, будет ли изменение в версии представлять изменение лежащего в основе ресурса или же представления. Помните, что URI это Uniform Resource Identifier, и мы действительно не должны изменять URI, который мы используем, чтобы направить на тот же ресурс.
Например, у нас есть ресурс api.example.com/images/monalisa. Мы можем запросить этот ресурс как JPEG (Accept: image/jpeg) или как PNG (Accept: image/png), или спросить сервер, есть ли у него plain-text представление этого ресурса (Accept: application/json) – но это все только различные представления одного и того же лежащего в основе ресурса, и они все должны иметь одинаковый URI.
Или, скажем, вы полностью заменили CRM-систему, используемую в вашей организации, и в этом случае «версия 1» клиента представляет запись, которая используется в старой CRM-системе, и «версия 2» представляет того же клиента, но после мигрирования на полностью новую платформу. В таком случае, пожалуй, имеет смысл рассматривать их как различные ресурсы и дать им различные URI.
Версионирование — штука сложная. Проще всего никогда ничего не изменять.
– .NET Core — что вы думаете о его API?
Дилан Битти: .NET Core был впервые анонсирован в 2015-ом году и изначально должен был называться .NET Core 5.0. Ожидалось, что это будет упрощенная альтернатива .NET Framework и Common Language Runtime. Это была потрясающая идея — создать все условия для облегчения портирования .NET Core на другие платформы. Правда при этом все равно сохранилась немалая разница между API, открытым в .NET Core, и «стандартным» .NET/CLR API, на котором построено большинство приложений.
Я думаю — и это только моя интерпретация, основанная на том, что я читал, и на разговорах с различными людьми, — идея в состоит том, что .NET Core будет предоставлять фундаментальные блоки и такие вещи, как threading, доступ к файловой системе, доступ по сети, а вендоры платформы вместе с open source комьюнити уже станут разрабатывать модули и пакеты, которые в итоге по функциональным возможностям достигнут уровня, предоставляемого решениями вроде Java Class Library или .NET Framework. В принципе, это отличная идея, но она создает и что-то вроде замкнутого круга: никто не хочет создавать библиотеки для платформы без пользователей, и при этом никто не хочет использовать платформу без библиотек.
Так что было принято решение, что кросс-платформенный .NET требует стандартной спецификации API, которая предоставит библиотеки, ожидаемые пользователями и разработчиками на различных поддерживаемых платформах. Это и есть .NET Standard 2.0, который уже полностью поддерживается .NET Framework 4.6.1 и будет полностью поддерживаться в следующих версиях .NET Core и Xamarin. Конечно же, .NET Core 1.1 уже вышел и работает хорошо, и вы можете использовать его уже сейчас, чтобы создавать веб приложения C#, несмотря на то, пользуетесь вы Windows или Linux или macOS, что очень круто. Но я думаю, что следующий релиз .NET Core вызовет у множества создателей фреймворков и пакетов желание мигрировать проекты на .NET Core, что в свою очередь упростит для разработчиков и организаций миграцию их собственных приложений.
– Гибкость API vs точность API. Можно создать дизайн API-метода с возможностью получить множество различных типов значений. Но можно и создать метод с некоторыми правилами в параметрах. Оба пути верные. Где граница между этими двумя возможностями? Когда мы должны делать строгий API, а когда создавать гибкий API дизайн?
Дилан Битти: При реализации API, в котором сигнатуры метода гибкие, все, что вы делаете, это отправляете сложность куда-то еще в вашем стеке. Скажем, мы создаем API для нахождения лыжных путевок, и у нас есть выбор между DoSearch(SearchCriteria criteria) и DoSearch(string resortName, string countryCode, int minAltitude, int maxDistanceToSkiList).
Первый из этих методов довольно легко расширяем. Мы ведь можем расширить определение объекта SearchCriteria без смены сигнатуры метода. Но в таком случае мы не просто изменяем конкретный метод — мы еще изменяем и поведение системы.
В противоположность мы можем добавить новые аргументы в сигнатуру нашего второго DoSearch метода. Если мы работаем с таким языком как C#, то мы можем предоставить аргументам значения по умолчанию. И мы ничего не сломаем в проекте, добавляя новые аргументы в метод, если при этом зададим разумные значения по умолчанию этим самым аргументам.
В определенный момент вам нужно будет общаться с пользователями API и сообщить им, какие поисковые параметры поддерживаются вашим API. И вот здесь есть несколько способов сделать это. Если вы создаете .NET API, которое устанавливается как NuGet пакет и используется из кода, то использование XML-комментариев к вашим методам и параметрам — это отличный способ объяснить вашим пользователям, что они должны задать, когда они делают запросы к вашему API. Если же ваш API — это HTTP-сервис, то обратите внимание на такие гипермедийные форматы как SIREN, с помощью которых можно обозначить, какие параметры и диапазоны поддерживаются.
Думаю, в следующее десятилетие мы с вами увидим полностью иную категорию API, приводимую в движение системами машинного обучения, где множество повсеместно принятых правил API-дизайна не будут применяться. Меня совершенно не удивит, если мы вдруг получим API поиска лыжных путевок, где вам необходимо просто указать, что вам нужно, на нормальном языке. И тут даже не будет сигнатуры метода — вы просто вызываете что-то вроде DoSearch («ski chalet, in France or Italy, 1400m or higher, that sleeps 12 people in 8 bedrooms, available from 18-25 January 2018») — и лежащая в основе система сделает все за вас. Эти способы разработки с помощью машинного обучения — захватывающая вещь. Но их создатели хотят сделать еще несколько интересных для разработчиков и дизайнеров изменений, чтобы привлечь к себе дополнительное внимание.
В этом году Dylan Beattie снова посетит Россию и в мае выступит на конференции DotNext в Санкт-Петербурге, так что у вас есть шансы не только послушать его новый доклад, но и задать ему свои вопросы или передать привет лондонской .NET User Group.
Автор: alsion