RestBucks on .Net: Ordering Coffee

Previous posts

Welcome to the second part of the RestBucks on .Net series. Today, I am going to take the first use case of the example: “Ordering Coffee”.

What we know?

We know (is on the book) that the customer will POST (http verb) an xml payload as follows:

2011-06-01_0900

This is what we call in REST a REPRESENTATION. It is not the resource itself but just a representation of it.

I have intentionally highlighted three different areas:

  • (green) Location: this indicate whether the order will be consumed onsite or not. It only accept two different values; “takeAway” or “inShop”.
  • (yellow) Product name: this is the name of the product.
  • (red) Product preferences: each product accept different preferences.

I took this one step further from the book and we are going to suppose that at any moment of time, the coffee shop can change their menu and/or the possible preferences for a product.

Also notice that an order might have many items.

The order will be POSTed to an URI like “http://restbucks.com/orders”.

In plural; in rest you create things on collections (plural). To GET/PUT/DELETE an order you generally do something like “http://restbucks.com/order/id” (singular)

The domain

From the little piece of information on the above section, we can trace some core concepts:

Entity Attributes
Product Name, Possible preferences
Order Location, Lines
Order line Product, Preferences, Quantity

And then we can build an object-oriented :) domain model:

ClassDiagram1

This is a pretty anemic domain model and it doesn’t have all the properties and methods that the current version on codeplex has. But it serve to us for the purpose of our specs so far.

Persistence

For this example I have chosen to use a relational database thru an ORM. I have used the last version of the more mature ORM in .Net; NHibernate. For mapping the domain I have used only conventions with few particular exceptions.

The first thing I did is to write some persistence tests; like this one:

[Test]
public void CanStoreACustomization()
{
    long customizationId;
    using (var session = sessionFactory.OpenSession())
    using(var tx = session.BeginTransaction())
    {
        var repository = new Repository<Customization>(session);
        var customization = new Customization { Name = "Milk", PossibleValues = { "skim", "semi", "whole" } };
        repository.MakePersistent(customization);
        customizationId = customization.Id;
        tx.Commit();
    }
    using (var session = sessionFactory.OpenSession())
    {
        var repository = new Repository<Customization>(session);
        var customization = repository.GetById(customizationId);
        customization.Satisfy(c => c.Name == "Milk" && c.PossibleValues.SetEquals(new[] { "skim", "semi", "whole" }));
    }
}

This kind of tests check that the properties are properly persisted, and for instance that inserting a customization automatically inserts the PossibleValues collection.

Each test destroy and recreate the whole database schema (we use a separated database for testing purposes as explained here).

It is not my plan to post all the tests I wrote, you can have a look to these two class here. The result of these tests is the Customize method in this tiny class. The other method (Generate) define only few general purpose conventions.

The representation

Now, that we have our Order class you might wonder how and where to de-serialize the chunk of xml we see at the beginning.

This broadly discussed but at some point there is some level of agreement; Entities are not DTO (and representations are more or less like DTOs).

We have different ways to de-serialize the representation. For this example I chose to write a typed class and use the standard .net xml serialization (another way might be to de-serialize into a dynamic ExpandoObject).

2011-06-01_1145

As you can see, this is quite different to our domain entity and in the long run the difference will be bigger because Representations doesn’t have methods.

After I wrote those classes with their properties; the tests that we have to fix is this one:

[Test]
public void CanDeserialize()
{
    var xml =
        @"<?xml version=""1.0""?>
<order xmlns:xsd=""http://www.w3.org/2001/XMLSchema"" xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" xmlns=""http://restbucks.com"">
  <location>inShop</location>
  <cost>100.4</cost>
  <items>
    <item>
      <name>latte</name>
      <quantity>0</quantity>
      <size>large</size>
      <milk>skim</milk>
    </item>
  </items>
</order>";
    var representation = XmlUtil.FromXmlString<OrderRepresentation>(xml);
    representation.Satisfy(r =>
                           r.Items.Any(i => i.Name == "latte"
                                            && i.Preferences.Any(p => p.Key == "size")
                                            && i.Preferences.Any(p => p.Key == "milk")));
}

To pass this test we need a bunch of attributes and implementing the IXmlSerializable interface for the preferences dictionary.
I am not going to paste here the full implementation of those two classes but you can explore here and here.

The resource handler

This is the way I like to call the class that has the [ServiceContract] attribute in the current bits of the wcf WebApi. It is not the resource it self, for me the resource is the Order class (the domain object) and I don’t like the name of “service” in REST.

The main purpose of the resource handler is to expose an Http interface to our application. In order to do this the resource handler can take dependencies on different services that our internal API expose; e.g. a repository.

To make things clear, there is specific logic that belongs to our domain model; e.g. customer can’t pay an order that he has previously canceled. On the other hand there is specific logic that belongs to the handler; e.g. returns a 404-Not found response when the customer does a request for an order that doesn’t exist in our repository.

This test validate that if the customer does a POST of a product that doesn't exist, then the resource handler must return 400 and a meaningful ReasonPhrase:

[Test]
public void WhenAProductDoesNotExist_ThenReturn400AndTheProperREasonPhrase()
{
    var resourceHandler = CreateResourceHandler();
    var orderRepresentation = new OrderRepresentation
    {
        Items = { new OrderItemRepresentation { Name = "beer" } }
    };

    var result = resourceHandler.Create(orderRepresentation);

    result.Satisfy(rm => rm.StatusCode == HttpStatusCode.BadRequest
                      && rm.ReasonPhrase == "We don't offer beer");
}

This test does another validation:

[Test]
public void WhenItemHasQuantity0_ThenReturn400AndTheProperREasonPhrase()
{
    var resourceHandler = CreateResourceHandler();
    var orderRepresentation = new OrderRepresentation
    {
        Items = { new OrderItemRepresentation { Name = "latte", Quantity = 0 } }
    };

    var response = resourceHandler.Create(orderRepresentation);

    //NOTE: I am not sure if the proper response is NotFound or BadRequest. It is not clear in the book.
    response.Satisfy(rm => rm.StatusCode == HttpStatusCode.BadRequest
                        && rm.Content.ToStringContent() == "Item 0: Quantity should be greater than 0.");
}

and so forth. When the order is ok:

[Test]
public void WhenOrderIsOk_ThenInsertANewOrderWithTheProductsAndPrice()
{
    var orderRepository = new RepositoryStub<Order>();
    var handler = CreateResourceHandler(orderRepository);
    var orderRepresentation = new OrderRepresentation { Items = { new OrderItemRepresentation { Name = "latte", Quantity = 1 } } };

    //act
    handler.Create(orderRepresentation);

    var order = orderRepository.RetrieveAll().First();
    order.Satisfy(o => o.Items.Any(i => i.Product == latte && i.UnitPrice == 2.5m && i.Quantity == 1));
}

[Test]
public void WhenOrderIsOk_ThenInsertANewOrderWithTheDateTime()
{
    var orderRepository = new RepositoryStub<Order>();
    var handler = CreateResourceHandler(orderRepository);
    var orderRepresentation = new OrderRepresentation { Items = { new OrderItemRepresentation { Name = "latte", Quantity = 1 } } };

    //act
    handler.Create(orderRepresentation);

    var order = orderRepository.RetrieveAll().First();
    order.Date.Should().Be.EqualTo(DateTime.Today);
}

[Test]
public void WhenOrderIsOk_ThenInsertANewOrderWithTheLocationInfo()
{
    var orderRepository = new RepositoryStub<Order>();
    var handler = CreateResourceHandler(orderRepository);
    var orderRepresentation = new OrderRepresentation 
                                    { 
                                        Location = Location.InShop, 
                                        Items = { new OrderItemRepresentation { Name = "latte", Quantity = 1 } } 
                                    };

    //act
    handler.Create(orderRepresentation);

    var order = orderRepository.RetrieveAll().First();
    order.Location.Should().Be.EqualTo(Location.InShop);
}

The above tests specify that an order must be saved properly. As you can notice here our resource handler depends on a IRepository<T> and in those tests we are using RepositoryStub<T> which is an implementation of IRepository<T> with a simple in-memory collection.

The last test for this use case is very interesting:

[Test]
public void WhenOrderIsOk_ThenResponseHasStatus201AndLocation()
{
    var orderRepository = new Mock<IRepository<Order>>();
    orderRepository.Setup(or => or.MakePersistent(It.IsAny<Order[]>()))
                   .Callback<Order[]>(o => o.First().Id = 123);

    var handler = CreateResourceHandler(orderRepository.Object);

    //act
    var result = handler.Create(new OrderRepresentation { Items = { new OrderItemRepresentation { Name = "latte", Quantity = 1 }}});

    var expectedUriToTheNewOrder =
        resourceLinker.GetUri<OrderResourceHandler>(or => or.Get(0, null), new { orderId = "123" });

    result.Satisfy(r => r.StatusCode == HttpStatusCode.Created
                                  && r.Headers.Location.ToString() == expectedUriToTheNewOrder);
}

It means that after creating the order, the response status must be Created (201), and there must be a Location header.

The protocol is very clear on this (section 14.40):

The Location response-header field is used to redirect the recipient to a location other than the Request-URI for completion of the request or identification of a new resource. For 201 (Created) responses, the Location is that of the new resource which was created by the request. For 3xx responses, the location SHOULD indicate the server's preferred URI for automatic redirection to the resource. The field value consists of a single absolute URI.

A client application can pull the representation from the header location and get more details of the resource it just created, but that is for my next post :).

Finally; this is the implementation of the handler:

[ServiceContract, WithUriPrefix("orders")]
public class OrdersResourceHandler
{
    private readonly IRepository<Product> productRepository;
    private readonly IRepository<Order> orderRepository;
    private readonly IResourceLinker resourceLinker;

    public OrdersResourceHandler(
            IRepository<Product> productRepository,
            IRepository<Order> orderRepository,
            IResourceLinker resourceLinker)
    {
        this.productRepository = productRepository;
        this.orderRepository = orderRepository;
        this.resourceLinker = resourceLinker;
    }

    [WebInvoke(Method = "POST", UriTemplate = "")]
    public HttpResponseMessage Create(OrderRepresentation orderRepresentation)
    {
        var order = new Order
        {
            Date = DateTime.Today,
            Location = orderRepresentation.Location
        };

        foreach (var requestedItem in orderRepresentation.Items)
        {
            var product = productRepository.GetByName(requestedItem.Name);
            if (product == null)
            {
                return Responses.BadRequest(string.Format("We don't offer {0}", requestedItem.Name));
            }
            var orderItem = new OrderItem(product,
                                        requestedItem.Quantity,
                                        product.Price,
                                        requestedItem.Preferences);
            order.AddItem(orderItem);
        }

        orderRepository.MakePersistent(order);
        var uri = resourceLinker.GetUri<OrderResourceHandler>(
                                                    orderResource => orderResource.Get(0, null),
                                                    new { orderId = order.Id });
        return Responses.Created(uri);
    }
}

I hope you find this useful, and stay tuned for the next article.


blog comments powered by Disqus
  • Categories

  • Archives