REST – GET-less resources(actions): yes or no?

01.03.2011 22:33Comments
I’ve been reading a lot about hypermedia lately and I must admit that I’m pretty sure that hypermedia is the way to go in REST services. Here’s a pretty cool maturity level explanation by Martin Fowler. In this post you can see how the Level 3 - “Hypermedia Controls” is the way to achieve the glory of REST. Basically, REST is all about resources and state representation. The beauty of REST is how you can literally GET a representation of a resource’s state in your preferred format, and provided with enough hypermedia, you can “navigate” to other resources. So what happens when I need to change the state of a resource? With a few variations on each, I have two options:
  • Make a PUT to the resource with the changed stated on it. The problem with this approach is that you might end up sending a big representation of a resource to change just one field. Even if you only send the “modified” field, you could be potentially using the same url for performing different actions on the same resource (somewhat tunneling).
  • Make a POST to a different resource that represents this change of state. This one is easier to explain with an example. If I want to step the order http://localhost/orders/5 from new to pending status, I could POST to the url http://localhost/pendingorders, thus creating a new resource: http://localhost/pendingorders/5 which is nothing but the order with a different status. This second approach, while a bit more elegant RESTfulwise, it could lead to numerous “fictitious” resources, that the only thing they handle is a change of state (potentially firing some process).

Once upon a time…

When using SOAP/RPC web services, it’s a common practice to have a “service” business layer. This is almost a perfect match as you can usually match the name of the service contract operation with the name on a service/manager method.
Code Snippet
  1. [ServiceContract]
  2. public class OrderService
  3. {
  4.     [OperationContract]
  5.     public GetOrderResponse GetOrder(GetOrderRequest request)
  6.     {
  7.         var id = request.OrderId;
  8.         var orderManager = new OrderManager();
  9.         var order = orderManager.GetOrder(id);
  10.         var response = new GetOrderResponse() { Order = order };
  11.         return response;
  12.     }
  13.  
  14.     [OperationContract]
  15.     public StepToPendingResponse StepToPending(StepToPendingRequest order)
  16.     {
  17.         var orderEntity = order.ToEntity();
  18.         var orderManager = new OrderManager();
  19.         orderManager.StepToPending(orderEntity);
  20.         return new StepToPendingResponse();
  21.     }
  22. }
So your business logic would be in the Manager/Service classes, and your business entities would be pretty much like DTOs. In these type of services, you always POST to the same URL, so there’s no way to “navigate” the service. And this is one of the things that makes REST services so appealing.

REST and DTOs…

On the other hand, when we are using REST services, you have the feeling of actual navigating resources, and operating on them. Take this url for example http://localhost/Orders, you can be pretty certain than it will return a list of orders. And if you had access to http://localhost/Orders/5, you can infer that it will return the order with an id of 5. What’s even more interesting, is that if this address existed http://localhost/orders/5/customer/address/city/state/country, you could be pretty certain that it will return the country where the customer of the order with an id of 5 lives. This sense of navigation is very similar to navigating classes in OOP. Like this: image This gives us the hint that we might be able to model our REST service with classes instead of ServiceContracts. Let’s see how this could work. We could define our Order class like so:
Code Snippet
  1. public class Order
  2. {
  3.     public int Id { get; set; }
  4.     public List<OrderItem> Items { get; set; }
  5.     public Customer Customer { get; set; }
  6.     public DateTime SubmittedOn { get; set; }
  7.     public OrderStatus Status { get; set; }
  8. }
  9.  
  10. public enum OrderStatus
  11. {
  12.     New,
  13.     InProcess,
  14.     Cancelled,
  15.     Completed
  16. }
When browsing http://localhost/Store/Orders/5 something like this could rendered:
Code Snippet
  1. <Order>
  2.   <Id>1</Id>
  3.   <SubmittedOn>1/1/0001 12:00:00 AM</SubmittedOn>
  4.   <Status>New</Status>
  5.   <link rel="OrderItemCollection" href="Items" />
  6.   <link rel="Customer" href="Customer" />
  7. </Order>
Where the href are relative urls for http://localhost/Store/orders/1/Items and http://localhost/Store/orders/1/Customer respectively (they could be absolute as well). Now suppose we want to let the user cancel this order. If we want to take the second approach, we would need a new resource that represents this change of state, something like http://localhost/orders/5/cancellation which could return 404 status code if the order is not cancelled, and where you can post to if you want to cancel the order. Now how much sense does this make in the OOP world far away from REST and it’s resources? Does it make sense to have a property on the Order for exposing this cancellation logic? How would that look?
Code Snippet
  1. public class Order
  2. {
  3.     public int Id { get; set; }
  4.     public List<OrderItem> Items { get; set; }
  5.     public Customer Customer { get; set; }
  6.     public DateTime SubmittedOn { get; set; }
  7.     public OrderStatus Status { get; set; }
  8.     public Cancellation Cancellation { get; set; }
  9. }
  10.  
  11. /// <summary>
  12. /// What now???
  13. /// </summary>
  14. public class Cancellation
  15. {
  16. }

REST and DDD (Domain Driven Design)

So it occurs to me, that we have a perfect fit for this cancellation logic in OOP world. This would be to have a “Cancel” method in the order class, and no longer have simple DTOs, but rather have business rules like in Domain Driven Design. Unfortunately we have a mismatch between REST and DDD, because even if we use POST for executing the method on the resource, how could we represent a method as a resource when doing a GET? We would have to expose in our service GET-less resources. Let’s see how this could look:
Code Snippet
  1. public class Order
  2. {
  3.     public int Id { get; set; }
  4.     public List<OrderItem> Items { get; set; }
  5.     public Customer Customer { get; set; }
  6.     public DateTime SubmittedOn { get; set; }
  7.     public OrderStatus Status { get; set; }
  8.     
  9.     public void Cancel()
  10.     {
  11.         this.Status = OrderStatus.Cancelled;
  12.     }
  13. }
Getting an order (http://localhost/orders/5) could be like this:
Code Snippet
  1. <Order>
  2.   <Id>1</Id>
  3.   <SubmittedOn>1/1/0001 12:00:00 AM</SubmittedOn>
  4.   <Status>New</Status>
  5.   <link rel="OrderItemCollection" href="Items" />
  6.   <link rel="Customer" href="Customer" />
  7.   <link rel="Order.Cancel" href="Cancel" method="POST" />
  8. </Order>
So notice how the link to the Cancel has a rel of type “Order.Cancel”, so that means that the type of resource is the Cancel of an order, and last but not least, checkout the “method” attribute that implies that you should POST to that url if you want to navigate to it. A GET on that Cancel method will make no different, and will show nothing interesting, and probably even throw a 405 (method not allowed) error code. So how bad is it to break this law in the REST world for the sake of readability and bringing the REST services closer to a Domain Driven world? Why not adopt the sense of GET-less resources (actions on resources like I call them) to make our (and our REST clients) lives easier? After all, can you not POST in a web with a FORM that points to a URL that has no GET handler?
So here’s the my question, GET-less Resources (actions): yes or no? Thoughts?
NOTE: “rel”, “href” and “method” attributes and their contents are just for illustration purpose, I’m not stating they SHOULD look like that.

comments powered by Disqus