Русский
Русский
English
Статистика
Реклама

Перевод Проектирование API почему для представления отношений в API лучше использовать ссылки, а не ключи

Привет, Хабр!

У нас выходит долгожданное второе издание книги "Веб-разработка с применением Node и Express".



В рамках исследования этой темы нами была найдена концептуальная статья о проектировании веб-API по модели, где вместо ключей и значений базы данных применяются ссылки на ресурсы. Оригинал из блога Google Cloud, добро пожаловать под кат.


Когда мы занимаемся моделированием информации, ключевой вопрос в том, как обозначить отношение и взаимоотношение между двумя сущностями. Описывать закономерности, наблюдаемые в реальном мире, в терминах сущностей и их взаимоотношений это фундаментальная идея, восходящая, как минимум, к Древней Греции. Она играет основополагающую роль и в современной IT-отрасли.

Например, в технологии реляционных баз данных взаимоотношения описываются с помощью внешнего ключа значения, сохраненного в одной строке таблицы, и указывающего иную строку либо в другой таблице, либо в той же самой таблице.

Не менее важно выражать отношения и в API. Например, в API для розничной торговли информационные сущности могут соответствовать клиентам, заказам, записям в каталоге, корзинам заказов, т.д. API банковского счета описывает, какому клиенту принадлежит данный счет, а также с каким счетом связан каждый долг или кредит.

Наиболее распространенный способ, применяемый разработчиками API для выражения взаимоотношений это предоставление ключей базы данных или прокси для них в полях тех сущностей, которые ассоциированы с этими ключами. Однако, как минимум в одном классе API (веб-ориентированных) такому подходу есть более предпочтительная альтернатива: использование веб-ссылок.

Согласно стандарту Internet Engineering Task Force (IETF), веб-ссылку можно представить как инструмент для описания отношений между страницами в вебе. Наиболее известные веб-ссылки те, что фигурируют на HTML-страницах и заключаются в элементы link или anchor, либо в заголовки HTTP. Но ссылки также могут фигурировать и в ресурсах API, а при использовании их вместо внешних ключей существенно сокращается объем информации, которую поставщику API приходится дополнительно документировать, а пользователю изучать.

Ссылка это элемент в одном веб-ресурсе, содержащий указание на другой веб-ресурс, а также название взаимоотношения между двумя ресурсами. Ссылка на другую сущность записывается в особом формате, именуемом уникальный идентификатор ресурса (URI), для которого существует стандарт IETF. В этом стандарте словом ресурс называется любая сущность, на которую указывает URI. Название типа отношения в ссылке можно считать аналогичным имени того столбца базы данных, в котором содержатся внешние ключи, а URI в ссылке аналогичен значению внешнего ключа. Наиболее полезны из всех URI те, по которым можно получить информацию о ресурсе, на который проставлена ссылка, при помощи стандартного веб-протокола. Такие URI называются унифицированный указатель ресурса (URL) и наиболее важной разновидностью URL для API являются HTTP URL.

В то время как ссылки не находят широкого применения в API, некоторые очень известные веб-API все-таки основаны на HTTP URL, используемых в качестве средства представления взаимоотношений. Таковы, например, Google Drive API и GitHub API. Почему так складывается? В этой статье я покажу, как на практике строится использование внешних ключей API, объясню их недостатки по сравнению с использованием ссылок, и расскажу, как преобразовать дизайн, использующий внешние ключи, в такой, где применяются ссылки.

Представление взаимоотношений при помощи внешних ключей


Рассмотрим популярное учебное приложение зоомагазин. В этом приложении хранятся записи для отслеживания информации о питомцах и их владельцах. У питомцев есть атрибуты, в частности, кличка, вид и порода. У владельцев есть имена и адреса. Каждый питомец взаимосвязан со своим владельцем, а обратное взаимоотношение позволяет найти всех питомцев конкретного владельца.

В типичном дизайне, основанном на использовании ключей, API зоомагазина предоставляет два ресурса, которые выглядят вот так:



Взаимоотношение между Лесси и Джо выражается так: в представлении Лесси Джо обозначен как обладатель имени и значения, соответствующих владельцу. Обратное взаимоотношение не выражено. Значение владельца равно 98765, это внешний ключ. Вероятно, это самый настоящий внешний ключ базы данных то есть, перед нами значение первичного ключа из какой-то строки какой-то таблицы базы данных. Но, даже если реализация API немного изменит значения ключей, она все равно сближается по основным характеристикам с внешним ключом.
Значение 98765 не слишком годится для непосредственного использования клиентом. В наиболее распространенных случаях клиенту, воспользовавшись этим значением, нужно составить URL, а в документации к API необходимо описать формулу для выполнения такого преобразования. Как правило, это делается путем определения шаблона URI, вот так:

/people/{person_id}

Обратное взаимоотношение любимцы принадлежат владельцу также можно предоставить в API, реализовав и документировав один из следующих шаблонов URI (различия между ними только стилистические, а не по существу):

/pets?owner={person_id}
/people/{person_id}/pets


В API, спроектированных по такому принципу, обычно требуется определять и документировать много шаблонов URI. Наиболее популярным языком для определения таких шаблонов является не тот, что задан в спецификации IETF, а язык OpenAPI (ранее известный под названием Swagger). До версии 3.0 в OpenAPI не существовало способа указать, какие значения полей могут быть вставлены в какие шаблоны, поэтому часть документации требовалось составлять на естественном языке, а что-то приходилось угадывать клиенту. В версии 3.0 OpenAPI появился новый синтаксис под названием links, призванный решить эту проблему, но для того, чтобы пользоваться этой возможностью последовательно, надо потрудиться.

Итак, при всей распространенности этого стиля, он требует от поставщика документировать, а от клиента выучить и использовать значительное количество шаблонов URI, не вполне хорошо описанных в нынешних спецификациях API. К счастью, есть вариант получше.

Представление взаимоотношений при помощи ссылок


Что, если бы ресурсы, показанные выше, были видоизменены следующим образом:



Основное отличие заключается в том, что в данном случае значения выражены при помощи ссылок, а не при помощи значений внешнего ключа. Здесь ссылки записаны обычным JSON, в формате пар имя/значение (ниже есть раздел, в котором обсуждаются и другие подходы к написанию ссылок в JSON).

Обратите внимание: обратное взаимоотношение, то есть, от питомца к владельцу, теперь тоже реализовано явно, поскольку к представлению Joel добавлено поле "pets".

Изменение "id" на "self", в сущности, не является необходимым или важным, но существует соглашение, что при помощи "self" идентифицируется ресурс, чьи атрибуты и взаимоотношения указаны другими парами имя/значение в том же объекте JSON. "self" это имя, зарегистрированное в IANA для этой цели.

С точки зрения реализации, заменить все ключи базы данных ссылками должно быть довольно просто сервер преобразует все внешние ключи базы данных в URL, так, что на клиенте ничего делать уже не требуется но сам API в таком случае существенно упрощается, а связанность между клиентом и сервером снижается. Многие шаблоны URI, которые были важны при первом дизайне, больше не требуются, и могут быть удалены из спецификации и документации API.

Теперь серверу ничто не мешает в любой момент изменить формат новых URL, не затронув клиентов (разумеется, сервер должен продолжать соблюдать все URL, сформулированные ранее). URL, передаваемый клиенту сервером, должен будет включать первичный ключ сущности, указанный в базе данных, плюс некоторую информацию о маршрутизации. Но, поскольку клиент просто повторяет URL, отзываясь серверу, и клиенту никогда не приходится выполнять синтаксический разбор URL, клиентам не обязательно знать, как форматировать URL. В результате снижается степень связанности между клиентом и сервером. Сервер даже может прибегнуть к обфускации собственных URL при помощи base64 или подобных кодировок, если хочет подчеркнуть клиентам, что те не должны гадать, каков формат URL, либо делать выводы о значении URL по их формату.

В предыдущем примере я использовал в ссылках относительную форму записи URI, например, /people/98765. Возможно, клиенту было бы немного удобнее (хотя, автору при форматировании этого поста было не слишком сподручно), если бы я выразил URI в абсолютной форме, напр. pets.org/people/98765. Клиентам необходимо знать лишь стандартные правила URI, определенные в спецификациях IETF, чтобы преобразовывать такие URI из одной формы в другую, поэтому выбор конкретной формы URI не так важен, как могло бы показаться на первый взгляд. Сравните эту ситуацию с описанным выше преобразованием из внешнего ключа в URL, для чего требовались конкретные знания об API зоомагазина. Относительные URL несколько удобнее для тех, кто занимается реализацией сервера, о чем рассказано ниже, но абсолютные URL, пожалуй, удобнее для большинства клиентов. Возможно, именно поэтому в API Google Drive и GitHub используются абсолютные URL.

Короче говоря, если использовать ссылки, а не внешние ключи, для выражения взаимоотношений между API, снижается объем информации, которую необходимо знать клиенту, чтобы работать с API, а также сокращается количество вариантов связанности, которые могут возникать между клиентами и серверами.

Подводные камни


Вот некоторые вещи, которые нужно предусмотреть, прежде, чем переходить к использованию ссылок.

Перед многими реализациями API поставлены обратные прокси, обеспечивающие безопасность, балансировку нагрузки и пр. Некоторые прокси любят переписывать URL. Когда в API для представления взаимоотношений используются внешние ключи, единственный URL, который требуется переписать в прокси это главный URL запроса. В HTTP этот URL разделяется между адресной строкой (первая строка заголовка) и заголовком хоста.

В API, использующем ссылки для выражения взаимоотношений, будут и другие URL в заголовках и в теле как запроса, так и отклика, и эти URL тоже понадобится переписывать. Есть несколько различных способов справиться с этим:

  1. Не переписывайте URL в прокси. Я стараюсь избегать переписывания URL, но в вашей среде может не быть такой возможности.
  2. В прокси аккуратно найдите и переназначьте им формат везде, где они фигурируют в запросе или в отклике. Я так никогда не делал, поскольку мне это кажется сложным, чреватым ошибками и неэффективным, но кто-то, возможно, так поступает.
  3. Записывайте все ссылки в относительном виде. Можно не только встроить во все прокси некоторые возможности по перезаписи URL; более того, относительные URL могут упростить использование одного и того же кода в тестировании и в продакшене, так как код не придется конфигурировать и знать для этого его хост-имя. Если писать ссылки с использованием относительных URL, то есть, с единственным ведущим слэшем, как я показал в примере выше, то возникают некоторые минусы как для сервера, так и для клиента. Но в таком случае в прокси появляется лишь возможность сменить хост-имя (точнее, те части URL, которые называются схемой и источником), но не путь. В зависимости от того, как построены ваши URL, вы можете реализовать в прокси некоторую возможность переписывать пути, если готовы писать ссылки с использованием относительных URL без ведущих слэшей, но я так никогда не делал, поскольку полагаю, что серверам будет сложно записывать такие URL как следует.


Относительными URL без ведущих слэшей также сложнее пользоваться клиентам, поскольку они должны работать со стандартизированной библиотекой, а не обходиться простым сцеплением строк для обработки этих URL, а также тщательно понимать и сохранять базовый URL.

Использование стандартизированной библиотеки для обращения с URL в любом случае, хорошая практика для клиентов, но на многих клиентах так не делается.
При использовании ссылок вам также, возможно, придется перепроверить версионирование вашего API. Во многих API принято ставить номера версий в URL, вот так:

/v1/pets/12345
/v2/pets/12345
/v1/people/98765
/v2/people/98765


Это версионирование такого рода, где данные по конкретному ресурсу можно одновременно просматривать более чем в одном формате здесь не идет речь о таких версиях, которые со временем заменяют друг друга по мере последовательного редактирования.

Эта ситуация весьма напоминает возможность просмотра одного и того же документа на нескольких естественных языках, для чего существует веб-стандарт; какая жалость, что нет подобного стандарта для версий. Присваивая каждой версии собственный URL, вы повышаете каждую версию по статусу до полноценного веб-ресурса. Нет ничего дурного с версионными URL такого рода, но они не подходят для выражения ссылок. Если клиент запрашивает Лесси в формате версии 2, это не означает, что он также хочет получить в формате 2 и информацию о Джо, хозяине Лесси, поэтому сервер не может выбрать, какой номер версии поставить в ссылку.

Возможно, формат 2 для описания владельцев даже не будет предусмотрен. Также нет концептуального смысла в том, чтобы использовать в ссылках конкретную версию URL ведь Лесси принадлежит не конкретной версии Джо, а Джо как таковому. Поэтому, даже если вы предоставляете URL в формате /v1/people/98765 и идентифицируете таким образом конкретную версию Джо, то также должны предоставлять URL /people/98765 для идентификации самого Джо, и именно второй вариант использовать в ссылках. Другой вариант определить только URL /people/98765 и позволить клиентам выбирать конкретную версию, включая для этого заголовок запроса. Для этого заголовка нет никакого стандарта, но, если называть его Accept-Version, то такой вариант хорошо сочетается с именованием стандартных заголовков. Лично я предпочитаю использовать для версионирования заголовок и избегаю ставить в URL номера версий. но URL с номерами версий популярны, и я часто реализую и заголовок. и версионные URL, так как легче реализовать оба варианта, чем спорить, какой лучше. Подробнее о версионировании API можете почитать в этой статье.

Возможно, вам все равно потребуется документировать некоторые шаблоны URL


В большинстве веб-API URL нового ресурса выделяется сервером, когда новый ресурс создается при помощи метода POST. Если вы пользуетесь этим методом для создания ресурсов и указываете взаимоотношения при помощи ссылок, то вам не требуется публиковать шаблон для URI этих ресурсов. Однако, некоторые API позволяют клиенту контролировать URL нового ресурса. Позволяя клиентам контролировать URL новых ресурсов, мы значительно упрощаем многие паттерны написания скриптов API разработчикам клиентской части, а также поддерживаем сценарии, в которых API используется для синхронизации информационной модели с внешним источником информации. В HTTP для этой цели предусмотрен специальный метод: PUT. PUT означает создай ресурс по этому URL, если он еще не существует, а если он существует обнови его. Если ваш API позволяет клиентам создавать новые сущности при помощи метода PUT, то вы должны документировать правила составления новых URL, возможно, включив для этого шаблон URI в спецификацию API. Также можно предоставить клиентам частичный контроль над URL, включив первичное ключ-подобное значение в тело или заголовки POST. В таком случае не требуется шаблон URI для POST как такового, но клиенту все равно придется выучить шаблон URI, чтобы полноценно пользоваться достигаемой в результате предсказуемостью URI.

Другой контекст, в котором целесообразно документировать шаблоны URL это случай, когда API позволяет клиентам кодировать запросы в URL. Не каждый API позволяет вам запрашивать свои ресурсы, но такая возможность может быть очень полезна для клиентов, и естественно позволить клиентам кодировать запросы в URL и извлекать результаты методом GET. В следующем примере показано, почему.

В вышеприведенном примере мы включили следующую пару имя/значение в представление Джо:

"pets": "/pets?owner=/people/98765"

Клиенту, чтобы пользоваться этим URL, не требуется что-либо знать о его структуре кроме того, что он был записан в соответствии со стандартными спецификациями. Таким образом, клиент может получить по этой ссылке список питомцев Джо, не изучая для этого никакой язык запросов. Также отсутствует необходимость документировать в API форматы его URL но только в случае, если клиент сначала сделает запрос GET к /people/98765. Если же, кроме того, в API зоомагазина документирована возможность выполнения запросов, то клиент может составить такой же или эквивалентный URL запроса, чтобы извлечь питомцев интересующего его владельца, не извлекая перед этим самого владельца достаточно будет знать URI владельца. Возможно, даже важнее, что клиент может формировать и запросы, подобные следующим, что в ином случае было бы невозможно:

/pets?owner=/people/98765&species=Dog
/pets?species=Dog&breed=Collie


Спецификация URI описывает для этой цели часть HTTP URL, называемую "компонент запроса" это участок URL после первого ? и до первого #. Стиль запрашивания URI, который я предпочитаю использовать всегда ставить клиент-специфичные запросы в компонент запроса URI. Но при этом допустимо выражать клиентские запросы и в той части URL, которая называется путь. Так или иначе, необходимо описать клиентам, как составляются эти URL вы фактически проектируете и документируете язык запросов, специфичный для вашего API. Разумеется, также можно разрешить клиентам ставить запросы в теле сообщения, а не в URL, и пользоваться методом POST, а не GET. Поскольку существует практический лимит по размеру URL превышая 4k байт, вы всякий раз испытываете судьбу рекомендуется поддерживать POST для запросов, даже если вы уже поддерживаете GET.

Поскольку запросы такая полезная возможность в API, и поскольку проектировать и реализовывать языки запросов непросто, появились такие технологии, как GraphQL. Я никогда не пользовался GraphQL, поэтому не могу рекомендовать его, но вы можете рассмотреть его в качестве альтернативы для реализации возможности запросов в вашем API. Инструменты для реализации запросов в API, в том числе, GraphQL, лучше всего использовать в качестве дополнения к стандартному HTTP API для считывания и записи ресурсов, а не как альтернативу HTTP.

И кстати Как лучше всего писать ссылки в JSON?


В JSON, в отличие от HTML, нет встроенного механизма для выражения ссылок. Многие по-своему понимают, как ссылки должны выражаться в JSON, и некоторые подобные мнения публиковались в более или менее официальных документах, но в настоящее время нет стандартов, ратифицированных авторитетными организациями, которые бы это регламентировали. В вышеприведенном примере я выражал ссылки при помощи обычных пар имя/значение, написанных на JSON предпочитаю такой стиль и, кстати, этот же стиль используется в Google Drive и GitHub. Другой стиль, который вам, вероятно, встретится, таков:
  {"self": "/pets/12345", "name": "Lassie", "links": [   {"rel": "owner" ,    "href": "/people/98765"   } ]}

Лично я не вижу, чем хорош этот стиль, но некоторые его варианты пользуются определенной популярностью.

Есть и другой стиль написания ссылок на JSON, который мне нравится, и он выглядит так:
 {"self": "/pets/12345", "name": "Lassie", "owner": {"self": "/people/98765"}}


Польза этого стиля в том, что он явно дает: "/people/98765" это URL, а не просто строка. Я изучил этот паттерн по RDF/JSON. Одна из причин освоить этот паттерн вам так или иначе придется им пользоваться, всякий раз, когда вы захотите отобразить информацию об одном ресурсе, вложенную в другом ресурсе, как показано в следующем примере. Если использовать этот паттерн повсюду, код приобретает красивое единообразие:

{"self": "/pets?owner=/people/98765", "type": "Collection",  "contents": [   {"self": "/pets/12345",    "name": "Lassie",    "owner": {"self": "/people/98765"}   } ]}


Более подробно о том, как лучше всего использовать JSON для представления данных, рассказано в статье Terrifically Simple JSON.

Наконец, в чем же разница между атрибутом и взаимоотношением?


Думаю, большинство читателей согласится, что в JSON отсутствует встроенный механизм для выражения ссылок, но существует и такой способ трактовки JSON, который позволяет утверждать обратное. Рассмотрим следующий JSON:

{"self": "/people/98765", "shoeSize": 10}

Принято считать, что shoeSize это атрибут, а не взаимоотношение, а 10 это значение, а не сущность. Правда, не менее логично утверждать, что строка '10 фактически является ссылкой, записанной специальной нотацией, предназначенной для ссылок на числа, до 11-го целого числа, которое само по себе является сущностью. Если 11-е целое число совершенно полноценная сущность, а строка '10' лишь указывает на нее, то пара имя/значение '"shoeSize": 10' концептуально является ссылкой, хотя, здесь и не используются URI.

То же можно утверждать и относительно булевых значений, и строк, поэтому все пары имя/значение в JSON можно трактовать как ссылки. Если такое восприятие JSON кажется вам оправданным, то естественно использовать простые пары имя/значение в JSON как ссылки на те сущности, на которые также можно указывать при помощи URL.

В более общем виде этот аргумент формулируется как нет фундаментальной разницы между атрибутами и взаимоотношениями. Атрибуты это просто взаимоотношения между сущностью или другой абстрактной или конкретной сущностью, например числом или цветом. Но исторически сложилось, что к их обработке относились по-особенному. Признаться, это достаточно абстрактный вариант восприятия мира. Так, если вы покажете кому-либо черного кота и спросите, сколько тут объектов большинство людей ответит вам, что всего один. Немногие скажут, что видят два объекта кота и его черный цвет а также взаимоотношение между ними.

Ссылки попросту лучше


Веб-API, передающие ключи базы данных, а не просто ссылки, сложнее в изучении, а также сложнее в использовании для клиентов. Также API первого рода теснее связывают клиент и сервер, требуя в качестве общего знаменателя более подробную информацию, и всю эту информацию нужно документировать и читать. Единственное преимущество API первого рода в том, что они настолько распространены, программисты с ними освоились, знают, как их создавать и как потреблять. Если вы стремитесь предоставить клиентам высококачественные API, не требующие тонн документации и максимально обеспечивающие независимость клиента от сервера, то подумайте о том, чтобы предоставлять в ваших веб-API ссылки, а не ключи базы данных.
Источник: habr.com
К списку статей
Опубликовано: 03.02.2021 10:04:43
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Блог компании издательский дом «питер»

Программирование

Проектирование и рефакторинг

Api

Google api

Веб-api

Json

Веб-разработка

Книги

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru