CQRS & REST

- 4 mins

Assuming:

CQRS will tell you to divide your application in two major architectural components: commands & queries. A command represents an user intent - all changes in the state of the application should be initiated exclusively by commands. Queries, on the other hand, represent questions an user can ask about the application state.

When it comes to exposing a REST-like API, it is important that a CQRS application makes it explicit the separation between commands and queries, so that clients can dynamically build task-based user interfaces. Assuming a JSON over HTTP API, that explicit separation could be accomplished by exposing commands as resources.

  GET /orders/de305d54-75b4-431b-adb2-eb6b9e546014/commands
  {
    "links": [
      {
        "rel": "Place",
        "href": "/orders/de305d54-75b4-431b-adb2-eb6b9e546014/commands/place"
      },
      {
        "rel": "Cancel",
        "href": "/orders/de305d54-75b4-431b-adb2-eb6b9e546014/commands/cancel"
      },
      {
        "rel": "Modify",
        "href": "/orders/de305d54-75b4-431b-adb2-eb6b9e546014/commands/modify"
      }
    ]
  } 

In the example above, all the possible commands that can be sent in the context of a given resource (/orders/{id}) are available as a nested resource collection (/commands).

Verbs

One caveat that might sound a little odd is the fact that the command URI contains the command name - which is usually a verb denoting an action to be performed on the parent entity. In some extent, that differs from regular REST where resources are always named after nouns and stateful operations on them are always performed by using the standard HTTP methods (POST, PUT, PATCH, DELETE).

In CQRS, though, the standard HTTP methods are not flexible enough to represent all possible commands associated to an entity. The example above demonstrates that: an existing order can be placed, canceled, or modified. In traditional REST style, these operations would end up being performed by sending a PUT (or PATCH) request to the order resource, which results in not enough context to identify the user intent.

In the proposed approach however, the point of view is slightly different: instead of modifying the /orders/{id} resource, we would “send a new command” using POST with a command URI and a (optional) request body. Like this:

  POST /orders/de305d54-75b4-431b-adb2-eb6b9e546014/commands/place

That gives us the flexibility of defining custom commands as well as making it easy to API clients to discover them, as the command name is part of the URI. Additionally, commands are preferably asynchronous operations, so that the result of the request above would ideally be a 202 (Accepted). Sometimes it is also useful to return a unique id for newly created resources or command status.

Embedded Commands

In many cases, it’d be useful for API clients to request a resource and get back the command list as part of the response, like this:

  GET /orders/de305d54-75b4-431b-adb2-eb6b9e546014
  {
    "created_at": "2016-02-01",
    "status": "opened",
    "links": [ ],
    "commands": [
      {
        "rel": "Place",
        "href": "/orders/de305d54-75b4-431b-adb2-eb6b9e546014/commands/place"
      },
      {
        "rel": "Cancel",
        "href": "/orders/de305d54-75b4-431b-adb2-eb6b9e546014/commands/cancel"
      },
      {
        "rel": "Modify",
        "href": "/orders/de305d54-75b4-431b-adb2-eb6b9e546014/commands/modify"
      }
    ]
  }

That would make possible for a Javascript client, for instance, to build an UI like this (assuming pseudo-angularjs):

  <ul>
    <li ng-repeat="command in order.commands">
      <button ng-click="send(command)">
        {{ command.rel }}
      </button>
    </li>
  </ul>

Conclusion

CQRS provides you with a flexible way to build scalable asyncronous systems, specially when combined with event sourcing. Addopting REST over HTTP as the delivery mechanism can be tricky in that context. The model described above is one way to do accomplish the combination of the two techniques. Let me know in case you have alternative ideas.

Vinicius Gomes

Vinicius Gomes

Software Developer

comments powered by Disqus
Vinicius Gomes © 2018
rss facebook twitter github youtube mail spotify instagram linkedin google pinterest medium