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
- Tokenize Request URL to get the /users/{uid} and the restOfTheUrl (in this case, /message/{mid} but could be /settings or /avatar)
- Check if the user with that uid exists and return 404 if he does not
- Add a requestAttribute with the key users and the value {uid}
- 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.