СТАТЬЯ

Добавление и удаление пользовательских методов

Из статьи JJ Geewax Шаблоны проектирования API

В этой статье рассматриваются:

  • Как управлять отношениями "многие ко многим" неявно, без дополнительного ресурса ассоциации.
  • Преимущества и недостатки неявной, а не явной ассоциации
  • Как использовать пользовательские методы для связывания ресурсов
  • Устранение проблем с целостностью данных связанных ресурсов

Получите скидку 40 % на Шаблоны проектирования API, введя код fccgeewax2 в поле для кода скидки при оформлении заказа на сайте manning.com.

В этой статье мы рассмотрим альтернативный шаблон для моделирования отношений «многие ко многим», основанный на пользовательских методах «Добавить» и «Удалить» для связывания (и разъединения) двух ресурсов. Этот шаблон позволяет потребителям управлять отношениями «многие ко многим» без введения третьего ресурса ассоциации в качестве необходимого требования.

Мотивация

Иногда нам нужно отслеживать отношения между ресурсами, и иногда эти отношения могут быть сложными. В частности, нам часто приходится иметь дело с ситуациями, когда два ресурса могут «иметь много» друг друга, что известно как отношение «многие ко многим».

Хотя шаблон ресурса ассоциации предлагает гибкий и полезный способ моделирования этого типа отношений, кажется, стоит спросить, может ли быть более простой способ сделать это, если мы можем жить с некоторыми ограничениями. Учитывая некоторые ограничения, можем ли мы сделать API немного проще и понятнее? И если это правда, каковы конкретные ограничения для использования этого упрощения? Этот шаблон исследует более простую альтернативу шаблону ресурса ассоциации.

Обзор

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

На самом базовом уровне мы упрощаем API, полностью скрывая ресурс ассоциации от потребителей и вместо этого управляем отношениями, используя пользовательские методы «Добавить» и «Удалить». Эти методы действуют как ярлыки для создания и удаления ассоциаций между двумя рассматриваемыми ресурсами и скрывают все подробности об этих отношениях, за исключением того факта, что они существуют (или не существуют). В классическом примере пользователей, которые могут быть членами нескольких групп (и групп, которые явно содержат несколько пользователей), это означает, что мы можем использовать эти методы Add и Remove для представления пользователей, присоединяющихся (Добавить) или покидающих (Удалить) данную группу.

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

Далее, поскольку мы собираемся использовать пользовательские методы для добавления и удаления ассоциации между ресурсами, мы должны рассматривать один из ресурсов как «управляющий ресурс» — вроде как один является родителем другого. Более конкретно, нам нужно будет выбрать, «добавлять пользователей в группы» или «добавлять группы пользователям». Если первое, то пользовательские ресурсы — это те, которые передаются, а групповые ресурсы «управляют» отношениями. Если последнее, то пользователи управляют отношениями по мере того, как группы передаются друг другу. С точки зрения кода (в данном случае в стиле объектно-ориентированного программирования) управляющим ресурсом является тот, к которому присоединены методы «добавить» и «удалить», как показано в листинге 1.

Листинг 1. Фрагменты кода для двух вариантов выбора управляющего ресурса.

group.addUser(user);  #A
 user.addGroup(group);  #B

#A Когда группа управляет отношениями, мы добавляем пользователей в данную группу.

#B Когда пользователь управляет отношениями, мы добавляем группы данному пользователю.

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

Реализация

Как только мы определили «управляющий ресурс», мы можем определить пользовательские методы Add и Remove. Полное имя метода должно иметь форму Add<Managing-Resource><Associated-Resource> (и то же самое для Remove). Например, если у нас есть пользователи, которых можно добавлять и удалять из групп, пользователь является связанным ресурсом, а группа управляет ресурсом. Это означает, что имена методов AddGroupUser и RemoveGroupUser.

Эти методы должны принимать запрос, содержащий как родительский ресурс (в данном случае управляющий ресурс), так и идентификатор добавляемого или удаляемого ресурса. Обратите внимание, что мы используем только идентификатор, а не весь ресурс. Это связано с тем, что другая информация является посторонней и потенциально может ввести в заблуждение, если потребители поверят, что у них есть возможность связать два ресурса и обновить один из них одновременно. Сводка методов Add и Remove и их HTTP-эквивалентов показана ниже в Таблице 1.

Список связанных ресурсов

Чтобы перечислить различные ассоциации, мы полагаемся на настраиваемые стандартные методы List, которые выглядят как методы псевдонимов в методе ресурса ассоциации. Эти методы перечисляют различные связанные ресурсы, обеспечивая разбиение на страницы и фильтрацию результатов. Поскольку у нас есть два способа посмотреть на отношения (например, мы можем захотеть увидеть, какие пользователи являются членами данной группы, а также какие группы являются членами данной группы), нам понадобятся два разных метода для каждого сценариев.

Эти методы следуют тому же соглашению об именах, что и методы Add и Remove, с обоими ресурсами в имени метода. Используя наш пример с пользователями и группами, мы предоставляем два разных метода для перечисления различных пользователей и групп с учетом определенного условия: ListGroupUsers предоставляет список пользователей, принадлежащих к данной группе, и ListUserGroups предоставляет список групп, членом которых является данный пользователь. . Как и в случае с другими пользовательскими методами, они следуют аналогичному соглашению об именовании сопоставлений HTTP, но полагаются на неявную вложенную коллекцию, кратко изложенную ниже в таблице 2.

Целостность данных

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

Поведение в этих случаях будет похоже на удаление несуществующего ресурса и создание дублирующегося (и, следовательно, конфликтующего) ресурса. Это означает, что если мы попытаемся дважды добавить пользователя в одну и ту же группу, наш API должен ответить конфликтной ошибкой (например, 409 Conflict), а если мы попытаемся удалить пользователя из несуществующей группы, мы должны вернуть ошибку. выражение ошибочного предположения (например, 412 Precondition Failed), чтобы сигнализировать о том, что мы не можем выполнить запрошенную операцию.

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

Окончательное определение API

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

Листинг 2. Окончательное определение API с использованием шаблона добавления/удаления.

abstract class GroupApi {
   static version = "v1";
   static title = "Group service";
  
   @get("{id=users/*}")
   GetUser(req: GetUserRequest): User;
  
   // ... #A
  
   @get("{id=groups/*}")
   GetGroup(req: GetGroupRequest): Group;
  
   // ... #A
  
   @post("{parent=groups/*}/users:add")
   AddGroupUser(req: AddGroupUserRequest): void;  // #B
  
   @post("{parent=group/*}/users:remove")
   RemoveGroupUser(req: RemoveGroupUserRequest): void;  // #B
  
   @get("{parent=groups/*}/users")
   ListGroupUsers(req: ListGroupUsersRequest): ListGroupUsersResponse;  // #C
  
   @get("{parent=users/*}/groups")
   ListUserGroups(req: ListUserGroupsRequest): ListUserGroupsResponse;  // #D
 }
  
 interface Group {
   id: string;
   userCount: number;
  
   // #E
   // Note we do not in-line the users here since the list could still be very
   // long. To see the list of users, use ListGroupUsers().
 }
  
 interface User {
   id: string;
   emailAddress: string;
 }
  
 interface ListUserGroupsRequest {
   parent: string;
   maxPageSize?: string;
   pageToken?: string;
   filter?: string;
 }
  
 interface ListUserGroupsResponse {
   groups: Group[];
   nextPageToken: string;
 }
  
 interface ListGroupUsersRequest {
   parent: string;
   maxPageSize?: number;
   pageToken?: string;
   filter?: string
 }
  
 interface ListGroupUsersResponse {
   users: User[];
   nextPageToken: string;
 }
  
 interface AddGroupUserRequest {
   parent: string;
   user: string;
 }
  
  
 interface RemoveGroupUserRequest {
   parent: string;
   user: string;
 }

#A Для краткости мы собираемся опустить все другие стандартные методы для ресурсов пользователя и группы.

#B Мы используем AddGroupUser и RemoveGroupUser для управления тем, какие пользователи связаны с какими группами.

#C Чтобы узнать, какие пользователи входят в данную группу, мы используем неявное сопоставление подколлекции с ListGroupUsers методом.

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

#E Обратите внимание, что мы не встраиваем здесь пользователей, потому что список может быть длинным. Чтобы увидеть список пользователей, мы используем ListGroupUsers.

Компромиссы

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

Не взаимные отношения

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

Метаданные отношений

Используя настраиваемые методы добавления и удаления, а не ресурс ассоциации, мы отказываемся от возможности хранить метаданные о самой связи. Это означает, что мы не сможем хранить никакой информации, кроме факта наличия (или отсутствия) связи. Мы не можем отслеживать, когда была создана связь, или что-либо конкретное для этой связи, и вместо этого нам нужно поместить это в другое место.

Упражнения

  1. Когда вы предпочтете использовать настраиваемые методы добавления и удаления, а не ресурс ассоциации для моделирования отношения «многие ко многим» между двумя ресурсами?
  2. При связывании ресурсов Рецепта с ресурсами Ингредиентов, какой из них является управляющим ресурсом, а какой будет связанным ресурсом?
  3. Какой метод будет вызываться для перечисления ресурсов ингредиентов, составляющих конкретный рецепт?
  4. Каким должен быть результат при добавлении повторяющегося ресурса с помощью пользовательского метода Add?

Сводка

  • В сценариях, где ресурсы ассоциации слишком велики (например, нет необходимости в каких-либо дополнительных метаданных, ориентированных на отношения), использование пользовательских методов «Добавить и удалить» может быть более простым способом управления отношениями «многие ко многим».
  • Пользовательские методы Add и Remove позволяют добавлять или удалять связанный ресурс, т. е. подчиненный, в некоторой связи с управляющим ресурсом.
  • Список связанных ресурсов может быть выполнен с использованием стандартных методов List для метаресурсов.

Если вы хотите узнать больше о книге, ознакомьтесь с ней на платформе Мэннинга liveBook здесь.