liveBook · Manning

Association resource

As explained in API Design Patterns

An association resource is an API resource that represents a relationship between specific resources and whose associated endpoints are fixed for the resource's lifetime.

An association resource represents a relationship between two specific resources as a first-class API resource. It is especially useful for many-to-many relationships, where each side may have many counterparts and the relationship may need its own metadata, such as a membership date or role. section 14.0 section 14.1

Figure 14.2 Using a join table to store many-to-many relationships

Use the join-table diagram to see how User, Group, and UserGroup resources expose both endpoints and the relationship between them. section 14.2

Model the relationship as its own resource when consumers must address an individual association or manage relationship-specific data. Creating the resource can mean joining; deleting it can mean leaving. Retrieving it exposes relationship metadata, while listing it supports collection-wide or filtered queries. section 14.2 section 14.6

Give the association resource these standard behaviors:

Choose a contextual name such as CourseRegistration, CourseEnrollment, Membership, or CourseMembership. Prefer a name that makes the joined resources and relationship understandable without forcing consumers to infer the domain meaning. section 14.3.1

Enforce uniqueness for the pair of associated resources: adding the same user to the same group twice should return HTTP 409 Conflict. Treat the cross-referencing fields as immutable; changing either side requires deleting the old association and creating a new one. section 14.3.3 section 14.3.4

Users, groups, and memberships

A Membership resource can connect user Jimmy to group 2 while storing role: "admin". CreateMembership({userId: "Jimmy", groupId: "2", role: "admin"}) creates the relationship. UpdateMembership can change the role, but userId and groupId remain fixed. ListUserGroups({userId: "Jimmy"}) and ListGroupUsers({groupId: 2}) provide convenient views of the relationship. section 14.3.4 section 14.3.5

Listing 14.4 Final API definition using association resources
abstract class GroupApi {
  static version = "v1";
  static title = "Group API";
 
  // ... Other methods left out for brevity.
 
  @post("/memberships")
  CreateMembership(req: CreateMembershipRequest): Membership;
 
  @get("/{id=memberships/*}")
  GetMembership(req: GetMembershipRequest): Membership;
 
  @patch("/{resource.id=memberships/*}")
  UpdateMembership(req: UpdateMembershipRequest): Membership;
 
  @delete("/{id=memberships/*}")
  DeleteMembership(req: DeleteMembershipRequest): void;
 
  @get("/memberships")
  ListMemberships(req: ListMembershipsRequest): ListMembershipsResponse;
 
  @get("/{groupId=groups/*}/users")                                    #1
  ListGroupUsers(req: ListGroupUsersRequest): ListGroupUsersResponse;
 
  @get("/{userId=users/*}/groups")                                     #2
  ListUserGroups(req: ListUserGroupsRequest): ListUserGroupsResponse;
}
 
interface Group {
  id: string;
  userCount: number;
  // ...                                                               #3
}
 
interface User {
  id: string;
  emailAddress: string;
  // ...                                                               #4
}
interface Membership {                                                 #5
  id: string;
  groupId: string;                                                     #6
  userId: string;
  role: string;                                                        #7
  expireTime: DateTime;
}
 
interface ListMembershipsRequest {
  parent: string;
  maxPageSize: number;
  pageToken: string;
}
 
interface ListMembershipsResponse {
  results: Membership[];
  nextPageToken: string;
}
 
interface CreateMembershipRequest {
  resource: Membership;                                                #8
}
 
interface GetMembershipRequest {
  id: string;
}
 
interface UpdateMembershipRequest {
  resource: Membership;
  fieldMask: FieldMask;
}
 
interface DeleteMembershipRequest {
  id: string;
}
 
// Optional from here on.
 
interface ListUserGroupsRequest {
  userId: string;
  maxPageSize: number;
  pageToken: string;
}
 
interface ListUserGroupsResponse {
  results: Group[];
  nextPageToken: string;
}
 
interface ListGroupUsersRequest {
  groupId: string;
  maxPageSize: number;
  pageToken: string;
}
 
interface ListGroupUsersResponse {
  results: User[];
  nextPageToken: string;
}

Use the complete API definition to compare standard membership methods with the optional ListGroupUsers and ListUserGroups aliases. section 14.3.7

Provide alias methods for common relationship questions when direct filtered listing is inconvenient, but recognize the trade-off: the pattern offers substantial flexibility at the cost of an extra resource, more methods, and separation between a resource's own data and its associations. section 14.2.1 section 14.4.1 section 14.4.2

Handle deletion of an associated resource deliberately. Prefer restricting deletion or leaving a dangling association rather than cascading or nulling references, which can trigger large chains of writes or deletions; restriction may return HTTP 412 Precondition Failed. section 14.3.6

Use an association resource when relationship metadata, independent relationship lifecycle, or flexible querying matters. Simpler add and remove methods hide the association but preserve only whether it exists, so they cannot represent dates, roles, or similar metadata. section 15.2 section 15.4.2

How API Design Patterns covers it

How deep it goes
Explains naming, standard methods, aliases, uniqueness, immutability, referential integrity, trade-offs, and a complete API definition.
Examples
Uses users, groups, memberships, course enrollments, filtered listings, duplicate creation, and immutable update requests.
What it assumes
Assumes resources and one-to-many relationships, then extends them to many-to-many relationships and join-table modeling.
What you can do after
Build an association-resource API with standard methods, uniqueness validation, aliases, immutable references, and deletion safeguards.
How it connects
Connects many-to-many relationships with join tables, standard methods, alias methods, uniqueness constraints, and referential integrity.

Related concepts

This explainer was generated by Manning's knowledge graph from the text of API Design Patterns. The section links point to the parts of the book it draws on.