One issue about the restful service API designing. I'm not sure how is a proper way to do it. Please give me some suggestions.
My scenario is like this. I have a user resource and permission resource.
http://www.sample.com/rest/users
http://www.sample.com/rest/permissions
User can have multiple permission; one permission can be used for many users; it is many to many relationship.
Normally, we can say a permission belongs to a user, so we have an API like:
http://www.sample.com/rest/users/{userId}/permissions
When we want to build a relationship between the permission and an user, here are two options.
we can first use POST: http://www.sample.com/rest/permissions
with a permission body, then POST:
http://www.sample.com/rest/users/{userId}/permissions
with a set
of permission ids. I'm not sure if there is any other rest APIs
designing like this.
we can use only one API like:
http://www.sample.com/rest/users/{userId}/permissions
with a
permission object content. In this method, we do two things I
descript in option 1. The downside is that we cannot never reuse the
created permission, it looks like one user can have multiple
permissions, but one permission only used by one user, which obey
our first designing. But it is really simple to user.
If you have any experience on this topic, any suggestions are welcome.
Another way is to think of the Users and Permissions as separated ‘spaces’ or resources. You should be able to manage them individually using CRUD.
To create user
- POST: /users/
- return: {userid}
To create a permission
- POST: /permissions/
- return: {permid}
Then come to mapping, you should use PUT instead since it is not creating a new resource but to map the two resources
Add user to Permissions space
- PUT: /permissions/users/{userId}/
Or add users to permissions space
- PUT: /permissions/users/
- BODY: {[userid1, userid2, …]}
Add permissions to Users space
- PUT: /users/permissions/{permId}/
Or add permissions to Users space
- PUT: /users/permissions
- BODY: {[permid1, permid2, … ]}
I would use the separate permission URL if the permission object is complex enough to look like a separate entity (or if you need to share the permission object between users). If the permission “object” is just an rwx
string or something like that, it’s not worth introducing a standalone entity. On the other hand, if the permissions object is something more complex (for example a list of permissions associated with a list of folders), it may make sense to store it as a separate entity and use a separate URL for that.
Resources
First let's address what 'resources' and 'collections' you would have.
/users
and /permissions
a nice simple start, each of these is a collection of all the users and all the permissions respectively.
/users/{id}
and /permissions/{id}
, each of these is a single resource for a user or permission respectively.
- A user might have fields like "name", "sign-up-date", "id" etc.
- A permission might have fields like "name", "creation-date", "id" etc.
So far nice and simple. Regardless where you go from here, these two types of resources still exist as stand alone entities, a user can have no permissions and a permission can have no users. But now we need to link these together? First, let's show a concept that seems blow most people away with it's simplicity...
/users/{id}/name
can be used to return just the name of a user based on the ID. You don't have to support this behaviour, and if you did then really you shouldn't directly return the name as part of the 'user' resource (you should link to this specific name resource).
This concept can also be applied to the permissions. So we also add /users/{id}/permissions
, both as a resource that you can call GET
on, but also as a 'link' that is in the resource you GET
via /users/{id}
(and thus the hyper part of html starts to be used). So what does this return? It could be something like:
/permission/3
/permission/666
/permission/7
Are those resources 'under' the user, no. Do they need to be, no. So what if you GET
one of these permission resources?
name=admin
id=666
created-date=12/3/45
users=/permission/666/users
And there we go, with that last line, just like a user has a collection of permissions, a permission has a collection of users with it. (I think you can work out what that would look like)
Creation of Resources
In the first section I covered how these two types of resources relate to each other. Your question, however, is more about how to create these resources. Obviously, you could allow the operator (I'll say operator to avoid confusion with the 'user' resource) to create a user whilst also defining the permissions they should have:
{ "name" : "Frank",
"permissions" : [
...
]
}
The server will generate the 'id' and 'sign-up-date' for you. The list of permissions could allow for the 'id' or the 'name' to be used, the latter would require that the server ensures they are unique and also has the server do a look up. The only question at this point is, if the 'permission' is a new name... what does the server do? It could easily just create it, but what if it was a typo, or just a capitalisation issue?
Really, it should be IDs, and these should have to exist prior to creation of a user. A GUI could help an operator work out what those IDs are (present a list of 'role' names, but know how to send the POST
request with IDs).
As a side note, notice how nothing has stopped a user from being created with no permissions, and that the collection of permissions a user has can be modified later on as required.
Answering the Actual Question
It boils down two simple things. Firstly, you can have many URIs refer to the same resource; /permissions/666
might be the canonical URI, but if a user has that permission, you might also find it via /user/{userID}/permissions/666
, those two URIs can refer to the same resource on the server.
The second thing to think about, and this really does make the answer seem obvious (especially in this use case). How often are you creating permissions? You might make maybe 10, but then assign a few of them to thousands of users. So it seems shamefully silly to attempt to work out how to handle creating a permission and user resource in one request. And as I think I've shown, reusing it is not a problem.
TL;DR
You proposed solution number one is the way to go.
A much more challenging topic to cover is how to remove a permissions. Either from a single users or remove it entirely. But I've gone on long enough, so perhaps another answer can focus in on that detail specifically.