Let's say I have a CQRS system where my write model contains the business rules. My read model is simply a DTO; it is a collection of properties and "dumb".

Now if you were to create a REST api, it would use this read model and return it.

Now I want to add HATEOAS to this REST api. Meaning that instead of just returning the data (let's say this is a reservation), it would also return links for cancelling the reservation and updating it.

These links should of course only be added when the user that is requesting the reservation is allowed to do so; if the reservation is already cancelled or the user is not the person that made the reservation, the link should not be added.

I do not really understand how this could work with CQRS. Our read model only has dumb properties; code like "CanCancelReservation()" lives in the write model, a part of the system our rest api doesnt (shouldnt?) Have access to.

So how could we still use HATEOAS in this scenario?

Some ideas I have but am not really happy with:

  • the links could be generated in our write model and stored in the event -- this doesn't solve the problem that only authorized people can perform certain actions. (Because that also lives in the write domain, right) -- also, shouldnt the events and write model not be concerned with the HATEOAS part?
  • the business logic is shared and can be accessed by the api as well -- we use 2 different classes/models so this can be a bit ugly

I am curious for your ideas!

  • Do you really need HATEOAS? May 31, 2022 at 14:28
  • That is kinda beside the point, I think. I really like HATEOAS and yes I want it. May 31, 2022 at 15:08

3 Answers 3


Now if you were to create a REST api, it would use this read model and return it.

I don't think it would. Does an amazon product page return a dumb list of all product properties?

First, I think the design itself is strange. When you say "read model" I guess you mean the persisted data structure or some view thereof. The design in the code should reflect the business case at hand, not a technical detail. CQRS, or whatever persistence strategy is used, should not be visible in any of the objects. It's a detail.

Also, HATEOAS is not just about providing links. It's basically about building a web-site, where you can navigate through your workflow (defined by the server) from start to finish, just by choosing options provided by the server.

So the short answer to "how" is to hide implementation details more, then you may find the proper place to generate web-pages from.


Now if you were to create a REST api, it would use this read model and return it.

You cannot implement a complete, full-featured REST API and link it just to the read model.

When handling a GET request, your REST API would indeed fetch the relevant data through the read model, but for handling a PUT, POST or DELETE request, the REST API definitely needs to use the write model.

As the REST API needs to have access to both the read and write models anyway, there is no real obstacle to accessing (parts of) the write model also when handling a GET request in order to determine what metadata (e.g. links) can be included in the response.

On the other hand, if it can happen more frequently that the REST API has to decide if certain data from the read model needs to be shown or not, you might want to consider extending the read model with logic that can answer such questions. For example, which details the current user is allowed to see of another user.


Now I want to add HATEOAS to this REST api. Meaning that instead of just returning the data (let's say this is a reservation), it would also return links for cancelling the reservation and updating it.

So I think you have a misunderstanding of HATEOAS.

HATEOAS doesn't mean that you perform actions on a resource via hyperlinks to commands like cancelling or updating.

So if your URLs look something like this


you are not following HATEOAS.

Remember REST is REpresentational State Transfer. You change the current state of a resource (such as reservation/343) by transfering an updated state of that resource from the client to the server. The URL stays the same.

And this fits neatly with CQRS, since in HTTP there is a read request (GET) and requests that change the state of the resource (PUT, DELETE). Your server can neatly handle these requests through different paths in your application.

To simply read current state of the reservation from the server the client just does a GET reservation/343 returning some representation such as

  customer: 'customers/123',
  status: 'live'

To cancel this reservation you could just ask the server to delete it (DELETE reservation/343) or probably more commonly update it since most reservation systems want to keep a record of cancelled reservations

PUT reservation/343
   customer: 'customers/123',
   status: 'cancelled'

note here the client has changed the status field to cancelled and asked the server to update its version via a PUT.

The server will need some logic to figure out what has changed and do anything it needs to do because of this change (update a database, email a customer etc), but that is for the server to worry about. And most importantly all that logic for updating the server can be piped through your 'write' pipeline in the application.

You can tell easily what requests are going to potentially write to the system just be looking at the HTTP verb (GET vs PUT,DELETE etc). Maybe the client doesn't have authorisation to update the resource to cancelled? That check only has to be done when you get a PUT request from the server, the GET request can ignore it because with a GET the client is not attempting to update any state of the resource.

This is all standard REST.

So what does HATEOS mean if it doesn't mean making command URLs for actions to carry out on the resource.

Well look at the representation of the reservation. Notice that the customer field for the customer that booked the reservation isn't just an id. It is a hyperlink.

We could have just put an id there

   customer: 123,
   status: 'cancelled'

but then the client would have to know how to turn that id into a URL in order to navigate to the customer who made this reservation.

Instead when you use a link if the client wants to start at a reservation and then navigate to the customer that made the reservation it simply follows that hyper link to get to the customer. It doesn't have to know in advance how to construct that hyperlink from a customer id.

And, importantly, the server might change the URL scheme at some point, but the client won't care because it is just following the hyper link.

So say next week the server developers decide that the URL scheme needs and update. All of a sudden clients are getting this resource back

  customer: 'europe/customers/123',
  status: 'live'

The clients won't care. Yes the customer's URL has changed, but the client doesn't care because all it did was follow what ever URL was for that customer.

So HATEOAS simply says that any time you have another resource in the system represent it as a URL so that the client can navigate to it using hypertext links.

  • 2
    "HATEOAS doesn't mean that you perform actions on a resource via hyperlinks" - It is literally what it means though. You're partially right, you probably don't want to create actions as resources, or execute an action with "GET". But you forget, it is "hypertext", not just hyperlinks. It is also Forms or other means of offering navigation to the client. For example, how does your client know that it should PUT to reservation/343 with a specific json message? It's hardcoded isn't it? That is the opposite of HATEOAS. Jun 1, 2022 at 11:42
  • "It's hardcoded isn't it?" - Well the PUT command is defined by the HTTP standard and the representation of the reservation resource is defined by the content type that the server and client negotiated (the 'hypermedia'). The client has to understand the HTTP protocol and the hypermedia format of the resource representation. That is in fact the point, it doesn't have to understand anything else specific to the server's particulars. That is the 'hardcoded' bit. Jun 1, 2022 at 12:29
  • You're making a request at that point, so nothing yet has been negotiated. Your client assumes that the server will take the same representation in the PUT as you got from the GET. This is an assumption the client makes. It is something many do, sure, but nothing in REST nor HTTP says that this should work. Also, assuming you want to cancel the reservation, how does your client know that the server needs a PUT request with a json where some field is set to "cancelled"? HATEOAS says the client shouldn't know these things. It should only know how to follow a link or submit a form. Jun 1, 2022 at 12:49
  • .. to navigate I mean. Of course it must know all the media-types involved. And in one (or more) of those media-types there must be a description on how to follow links and submit forms. Jun 1, 2022 at 12:51
  • 1
    Sure, I get that. Let me try this differently. If the server implementation tomorrow requires the client to actually POST some message to some other URI for cancelling (for whatever reason), would your client still work? A HATEOAS-compatible client would actually still work, because it "only" knew how to use the hypertext-control labelled "cancel" in the original representation it got. It doesn't care where that control points to, what method it uses or what the actual content is. Why would it even? It's server implementation detail. Jun 1, 2022 at 13:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.