Reputation: 9724
I've read some interesting tutorials about RESTful API design and the concepts behind it is quite clear... but let's put it into practice with Play now.
Suppose we want to implement a RESTful API that provides functionality for dealing with users. Let's start with the model. Here is the Address
class:
case class Address(
id: Int,
street: String,
zip: String,
city: String,
country: String
)
... here the User
class:
case class User(
id: Int,
email: String,
firstName: String,
lastName: String,
addresses: Array[Int]
// addresses: Array[Address] would this option be better?
)
... and finally the routes:
# Creates a new user
POST /users controllers.users.create
# Gets the user identified by the specified id
GET /users/:userId controllers.users.find(userId)
# Modifies the user identified by the specified id
PUT /users/:userId controllers.users.update(userId)
# Deletes the user identified by the specified id
DELETE /users/:userId controllers.users.delete(userId)
The first question is: how do I retrieve an user by email keeping my API complaint with the REST rules? The following wouldn't work because it conflicts with GET users/:userId
:
# Gets the user identified by the specified email address
GET /users/:email controllers.users.findByEmail(email)
The two options I've in mind so far are:
GET /users controllers.users.list(Option[email])
or
GET /users/:email/xxx controllers.users.findByEmail(email)
where xxx
should be a kind of virtual resource. Any suggestion for that?
My second and last question is: how should I manage user addresses? Should I get an User
, add the new Address
to User.addresses
, and then update the User
with PUT?
PUT /users/:userId controllers.users.update(userId)
... or should I create a specific controller for managing user addresses like this?
POST /users/:userId/addresses/ controllers.addresses.create(userId)
Personally I prefer the second option... but maybe there are better ones.
Upvotes: 3
Views: 1170
Reputation: 30310
I would say simply GET /users/email/:email controllers.users.findByEmail(email)
which returns a 200 if a user with the given E-mail exists and a 404 otherwise.
I can see the attraction, but I would arm wrestle @Robin on his idea. It is technically feasible, but it doesn't feel RESTful to me because of my interpretation of the Identification of Resources element of REST. Also, merging two or more possibilities for identifiers to be disambiguated on the server strikes me as brittle code that will force me to work on weekends eventually because user identifiers will come and go as requirements change--forcing me to modify the controller endpoint over and over. I may also be biased by seeing canonical method names like findByName
, findByEmail
, etc. but never findByYourGuessIsAsGoodAsMine
.
It's clear that the user is the interesting resource rather than an email (Note I am using email where you use address to distinguish from physical address in my mind). My routes would look something like users/:userId/emails/
as you have essentially and to manage them in the Users
controller. I see no reason--from either software engineering perspective or a ideological REST perspective--to add the overhead of an extra class.
Upvotes: 0
Reputation: 3294
From your question is not clear whether you want to implement "get" or "find". Usually "get" returns 404 if an user with that email doesn't exist, but I would expect that "find" returns 200 with no results. Personally I would use this:
GET /users/find controllers.users.find(email: Option[String])
Answer to the second question depends on whether you plan to be able to update an address only or you are going to always update an user as a whole. For example you can have a web page with one huge form with all user details & address and you save it all with one HTTP request.
Or another point of view might be whether an address is a standalone resource or is always used (create/read/update/delete) together with an user.
Upvotes: 1
Reputation: 33063
The following wouldn't work because it conflicts with GET users/:userId
It could still work if your user IDs are never going to be valid email addresses (during the lifetime of this version of the REST API!), so your code can distinguish between them - which is the case with your current types. Of course you can always introduce a new REST API version if you want to relax that assumption in future (e.g. if you wanted to make the user ids arbitrary strings), or if you decide you don't like combining the two types of queries like that.
Should I get an User, add the new Address to User.addresses, and then update the User with PUT?
By default, that's not transactional. However, if you use ETags, you can reject the User update if something else has changed the User in the meantime.
Upvotes: 0