CQRS with OData and Actions?

I love the Actions feature in OData – which is hardly surprising given I was one of its designers. 

Here’s the main reason why I love it: Actions allow you move from a CRUD architecture style, where you query and modify data using the same model, to an architectural style where you have a clear separation between the model used to query and the model used to update. To me this feels a lot like Greg Young’s baby CQRS, or Command Query Responsibility Segregation.

I’ll admit I’m taking some liberties here because these two models are actually ‘merged’ into a single metadata document ($metadata) that describes them both, and you can share types between these two models… however this feels insignificant because the key benefits remain.

Why would you want to move from a CRUD style application to a CQRS style one?

Let’s look at a simple scenario, imagine you have Products that look like this:

public class Product
{
public int ID {get;set;}
public string Name {get;set;}
public Decimal Cost {get;set;}
public Decimal Price {get;set;}
}

And imagine you want to Discount Marmite (a product in your catalog) by 15%. Today using the CRUD style, the default in OData before Actions, there is only one option: you PUT a new version of the Marmite resource with the new Price to the URL that represents Marmite, i.e. something like this:

POST ~/Products(15) HTTP/1.1
Content-Type: application/json

{
// abbreviated for readability
“ID”: 15,
“Name”: “Marmite”,
“Cost”: 3.50,
“Price”: 4.25 // ($5 – 15%)
}

Notice to support this you have to allow PUT for Products. And this has some real issues:

  • People can now make changes that we don’t necessarily want to allow, i.e. modifying the Name & Cost or changing the Price too much.
    • Basically “Update Product” is NOT the same as “Discount Product”.
  • When a change comes through we don’t actually know it is a Discount. It just looks like an attempt to update a Product.
  • If you need information that is not part of Product to perform a Discount (perhaps a justification) there is no where to put that information.

More generally the CRUD model is painful because:

  • If you want to update lots of resources simultaneously, imagine for example that you want to discount every product in a particular category, you first have to retrieve every product in that category, and then you have to do a PUT for each of them. This of course introduces a lot of unnecessary latency and introduces consistency challenges (it is hard to maintain a transaction boundary across requests & it the longer the ‘transaction’ lasts the more likely a concurrency check will fail).
  • If you want to update something you have to allow it to be read somewhere.

Back to our scenario, it would be much better to disable PUT completely and create a Discount action, and advertise it’s availability in the Marmite resource (to keep your system as Hypermedia driven as possible):

{
“__metadata”: {
// abbreviated for simplicity
“Actions”: {
“#MyActions.Discount”: [{ “title”: “Discount Marmite”, “target”: “Products(15)/Discount”}]
}
  },
“ID”: 15,
“Name”: “Marmite”,
“Cost”: 3.50,
“Price”: 5.00
}

The name of the Action (i.e. #MyActions.Discount) is an ‘anchor’ into the metadata document that can be found at ~/$metadata that says you need to provide a percentage.

POST ~/Products(15)/Discount HTTP/1.1
Content-Type: application/json

{
“percentage”: 15
}

This is much better. Notice this doesn’t allow me to modify the Cost or the Name, and indeed can easily be validated to make sure the percentage is within an acceptable range, and it is semantically much clearer what is happening.

In fact by moving from a CRUD style architecture to one inspired by CQRS but based on actions you can:

  • Give targeted update capabilities that:
    • Allow only certain parts of the ‘Read’ model to be modified.
    • Allow things that are not even in the ‘Read’ model to be modified or provided if needed.
  • Selectively give users permissions to only the ‘Actions’ or Commands they need.
  • Log every Action and replay them at a later date to rebuild your data (i.e. Event Sourcing).
  • Capture what is requested (i.e. Discount the product) and respond immediately before the change has actually been made, safe in the knowledge you will eventually process the request and achieve “Eventual Consistency”.
  • Capture more information about what is happening (i.e. User X discounted Marmite by 15% is much better than User X updated Marmite).
  • Create Actions that manipulate a lot of entities simultaneously (i.e. POST ~/Categories(‘Yeast Spreads’)/Products/Discount…)

Of course simply separating the read/write models in OData doesn’t give you all of these advantages immediately, but at least it creates a foundation that can scale to support things like Event Sourcing or Eventual Consistency later if required.

Some of you may be thinking you can achieve many of these goals by having a more granular model that makes things like “Discount” a resource, and that would be true. However for most people using OData that way of thinking is foreign and more importantly the EDM foundations of OData get in the way a little too. So for me Actions seems like the right approach in OData.

I love this.
But what do you think?
-Alex