j3d
j3d

Reputation: 9724

Play Framework, REST, Routes, and Controllers

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

Answers (3)

Vidya
Vidya

Reputation: 30310

The first question is: how do I retrieve an user by email keeping my API complaint with the REST rules?

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.

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?... or should I create a specific controller for managing user addresses like this?

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

Rado Buransky
Rado Buransky

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

Robin Green
Robin Green

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

Related Questions