Motivation#
- Many-to-many relationships are common and sometimes complex.
- The association-resource pattern is flexible but can be heavyweight.
- This chapter explores a simpler alternative: hide the association resource and manage relationships via custom
add
/remove
methods, accepting specific limitations.
Overview#
- Replace an explicit association resource with custom methods that create/delete associations and expose only the existence of the relationship.
- Consumers call concise methods (or HTTP endpoints) to add or remove associations; internal representation is hidden.
- You must pick a single managing resource (the resource that exposes
add
/remove
); the other resource is the associated/subordinate resource.
Implementation#
Naming convention:
Add<ManagingResource><AssociatedResource>
andRemove<ManagingResource><AssociatedResource>
.- Example:
AddGroupUser
/RemoveGroupUser
when Group manages users.
- Example:
Requests contain the parent (managing) resource and the identifier of the associated resource (not the full resource).
HTTP mapping examples:
POST /groups/1/users:add { "userId": "users/1" }
POST /groups/1/users:remove { "userId": "users/1" }
Listing associated resources#
Provide two list methods (one per direction) using implicit subcollections:
ListGroupUsers({ parent: "groups/1" })
→GET /groups/1/users
ListUserGroups({ parent: "users/1" })
→GET /users/1/groups
These support pagination, filtering, etc., like standard list endpoints.
Data integrity#
- Duplicate-add attempts: treat like creating a duplicate resource → return 409 Conflict.
- Removing a non-member: treat like deleting a non-existent resource → return 412 Precondition Failed (or another appropriate error signaling failed assumption).
- For clients whose only goal is existence, a success or a
409 Conflict
both indicate the association exists.
Final API (essence)#
- Minimal API surface:
GetUser
,GetGroup
,AddGroupUser
,RemoveGroupUser
,ListGroupUsers
,ListUserGroups
. - Resource interfaces keep lists out of-line (e.g.,
Group
may exposeuserCount
but not inline user list).
Trade-offs#
- Nonreciprocal relationship: must choose one managing resource (API is asymmetric).
- No relationship metadata: cannot store attributes tied to the association (join date, role, etc.). If metadata is required, use an explicit association resource instead.
Exercises (core answers)#
- Use add/remove when you only need to track existence and want a lightweight API.
- For
Recipe
/Ingredient
,Recipe
is the natural managing resource; method to list ingredients:ListRecipeIngredients
→GET /recipes/{id}/ingredients
. - Duplicate add should result in
409 Conflict
(or be interpreted by caller as “already present”).
Takeaway#
- The add/remove pattern is a simple, pragmatic way to manage many-to-many relationships when you can accept asymmetry and give up relationship-specific metadata.