1

I am currently working on an application that has multiple model objects that have a lot of relationships among each other.

For example, I have a user object and a group object that has multiple users in it.

The endpoints we expose for these objects are :

/users
/users/{uid}
/users/{uid}/groups
/users/{uid}/groups/{gid}

/groups
/groups/{gid}
/groups/{gid}/users
/groups/{gid}/users/{uid}

So far this is easy. Now let's say that each user and each group can have settings, messages, avatar, ....

Now we can expose endpoints such as

/users/{uid}/avatar
/groups/{uid}/avatar
/users/{uid}/messages
/users/{uid}/messages/{mid}
/groups/{gid}/messages
/groups/{gid}/messages/{mid}
/users/{uid}/settings
/groups/{gid}/settings

Even with only 6 relationships (user->avatar, group->avatar, user->messages, group->messages, user->setting, group->settings), the number of RequestMappings (endpoints) increases tremendously when we consider various CRUD operations (PUT, DELETE, POST, GET, ..)

If I add another resource, such as groupOfGroups that can contain multiple groups, the number of RequestMappings I will expose will increase exponentially

/gog/{gogid}/groups
/gog/{gogid}/groups/{gid}
/gog/{gogid}/groups/{gid}/users
/gog/{gogid}/groups/{gid}/users/{uid}
/gog/{gogid}/settings
/gog/{gogid}/messages
/gog/{gogid}/avatar

And I would also be adding RequestMappings for the CRUD operations.

To avoid code repetition , I have been using a catch all request mapping in each of the main controllers. For example, in the UserController, I would have the following requestMappings

GET /users
GET /users/{id}
POST /users
PUT /users/{id}
DELETE /users/{id}
/**

The /** mapping would catch /users/{uid}/messages/{mid} call. Inside the method that the /** mapping corresponds to, I would do the following

  1. Tokenize Request URL to get the /users/{uid} and the restOfTheUrl (in this case, /message/{mid} but could be /settings or /avatar)
  2. Check if the user with that uid exists and return 404 if he does not
  3. Add a requestAttribute with the key users and the value {uid}
  4. Forward to the restOfTheUrl

This will forward the request to the MessageController where I can use some QueryDSL builder to build the QueryDSL to only get the messages for whatever is in the request attrbitute (in this case it is the user with {uid}, but it could be a group with id {gid} or a groupOfGroups with id {gogid}

I want to know if this is the right solution, or if there is a better way of handling relationships without forwarding but also without repeating the code.

It wouldn't make sense , at least in my humble opinion, to have three endpoints

/users/{uid}/messages
/users/{uid}/settings
/users/{uid}/avatar

That check if the user exist and that would do the same functionality for groups and groupOfGroups but with the only difference being that we are checking if the group exists or the groupOfGroups exists.

It is important to note that tools like Swagger do not support this kind of approach of course, and even to forward, @RestController can not be used and only the more generic web mvc annotation @Controller can be used.

I am wondering what the best approach is to follow here.

Wael Awada
  • 1,506
  • 3
  • 18
  • 31

1 Answers1

0

It seems to me that you are trying to represent relationships (or perhaps also navigation) in URI path. Relationships in REST are represented as links, from https://stackoverflow.com/a/6328171/1536382 :

a team can be said to have a document resource (/team/{id}/players) that is a list of links to players (/player/{id}) on the team, and a player can have a document resource (/player/{id}/teams) that is a list of links to teams that the player is a member of

REST is not about how URI(s) look like, is more about sending to client the links to the operations available from a certain state (eg: if I am in /users/1 I can go to /users/1/messages) . As long as the server builds the links and the client just use them, it doesn't matter how the links are structured.

However, some of your URI(s) contain more informations they need to (and may require an uri-building (not rest) client to have knowledge of things it doesn't need to know): /gog/{gogid}/groups/{gid}/users/{uid} and /groups/{gid}/users/{uid} are the same as /users/{uid} since the data of the user can always be retrieved by {uid}.

An other possibility is that /groups/{gid}/users/{uid} actually means "a resource representing the association between an user and a group" including for example the date the user joined the group; if this is the case then for me this is an new resource, eg:membership, with its own ID, that could be mapped to /membership/{membership-id} and /groups/{gid}/users/{uid} could redirect to it. Memberships would be listed at /groups/{gid}/memberships

/users/{uid}/avatar and /groups/{gid}/avatar could be a redirect to their /avatar/{avatar-id}. If avatar were shared this could have a benefit when using HTTP caching.

Testo Testini
  • 2,200
  • 18
  • 29