Search code examples
node.jsrestcqrsevent-sourcing

CQRS with REST APIs


I am building a REST service over CQRS using EventSourcing to distribute changes to my domain across services. I have the REST service up and running, with a POST endpoint for creating the initial model and then a series of PATCH endpoints to change the model. Each end-point has a command associated with it that the client sends as a Content-Type parameter. For example, Content-Type=application/json;domain-command=create-project. I have the following end-points for creating a Project record on my task/project management service.

  • api.foo.com/project
    • Verb: POST
    • Command: create-project
    • What it does: Inserts a new model in the event store with some default values set
  • api.foo.com/project/{projectId}
    • Verb: PATCH
    • Command: rename-project
    • What it does: Inserts a project-renamed event into the event store with the new project name.
  • api.foo.com/project/{projectId}
    • Verb: PATCH
    • Command: reschedule-project
    • What it does: Inserts a project-rescheduled event into the event store with the new project due date.
  • api.foo.com/project/{projectId}
    • Verb: PATCH
    • Command: set-project-status
    • What it does: Inserts a project-status-changed event into the event store with the new project status (Active, Planning, Archived etc).
  • api.foo.com/project/{projectId}
    • Verb: DELETE
    • Command: delete-project
    • What it does: Inserts a project-deleted event into the event store

Traditionally in a REST service you would offer a PUT endpoint so the record could be replaced. I'm not sure how that works in the event-sourcing + CQRS pattern. Would I only ever use POST and PATCH verbs?

I was concerned I was to granular and that every field didn't need a command associated with it. A PUT endpoint could be used to replace pieces. My concern though was that the event store would get out of sync so I just stuck with PATCH endpoints. Is this level of granularity typical? For a model with 6 properties on it I have 5 commands to adjust the properties of the model.


Solution

  • Would I only ever use POST and PATCH verbs?

    Most of the time, you would use POST.

    PUT, and PATCH are defined with remote authoring semantics - they are methods used to copy new representations of a resource from the client to the server. For example, the client GETs a representation of /project/12345, makes local edits, and then uses PUT to request that the server accept the client's new representation of the resource as its own.

    PATCH, semantically, is a similar exchange of messages - the difference being that instead of sending the full representation of the resource, the client returns a "patch-document" that the server can apply to its copy to make the changes.

    Now, technically, the PATCH documentation does put any restrictions on what a "patch-document" is. In order for PATCH to be more useful that POST, however, we need patch document formats that are general purpose and widely recognized (for instance, application/merge-patch+json or application/json-patch+json).

    And that's not really the use case you have here, where you are defining command messages that are specific to your domain.

    Furthermore, remote authoring semantics don't align very well with "domain modeling" (which is part of the heritage of CQRS). When we're modeling a domain, we normally give the domain model the authority to decide how to integrate new information with what the server already knows. PUT and PATCH semantics are more like what you would use to write information into an anemic data store.

    On the other hand, it is okay to use POST

    POST serves many useful purposes in HTTP, including the general purpose of “this action isn’t worth standardizing.” -- Fielding, 2009

    It may help to recall that REST is the architectural style of the world wide web, and the only unsafe method supported by html is POST.

    So replacing your PATCH commands with POST, and you're on the right path.

    Fielding, 2008

    I should also note that the above is not yet fully RESTful, at least how I use the term. All I have done is described the service interfaces, which is no more than any RPC. In order to make it RESTful, I would need to add hypertext to introduce and define the service, describe how to perform the mapping using forms and/or link templates, and provide code to combine the visualizations in useful ways. I could even go further and define these relationships as a standard, much like Atom has standardized a normal set of HTTP relationships with expected semantics

    The same holds here - we aren't yet at "REST", but we have improved things by choosing standardized methods that are better aligned with our intended semantics.

    One final note -- you should probably replace your use of DELETE with POST as well. DELETE is potentially a problem for two reasons -- the semantics aren't what you want, and the standard delete payload has no defined semantics

    Expressed another way: DELETE is from the transferring documents over a network domain, not from your domain. A DELETE message sent to your resources should be understood to mean the same thing as a DELETE message sent to any other resource is understood. That's the uniform interface constraint at work: we all agree that the HTTP method tokens mean the same thing everywhere.

    Relatively few resources allow the DELETE method -- its primary use is for remote authoring environments, where the user has some direction regarding its effect -- RFC 7231

    As before: remote authoring semantics are not obviously a good fit for sending messages to a domain model.