Hi,
I’m playing arround with Hateoas. As far as I understand Hateoas represents a way to embed the state of an application in the respose of a query.
A good example of what i’m thinking about is here : https://en.wikipedia.org/wiki/HATEOAS
Since a user of this queries a readmodel and a readmodel is not an aggregate the ínformation of what is currently possible (Commands to execute) must be transported to the readmodel. Currently the only way of transporting this information I can think of is embedding this in the eventstream of the aggreate that is consumed by the readmodel.
How would you model these events? Include the Urls for your commands? A field with mappinginformations and let the readmodel map it ? Something else ?
Would love to hear you thoughts…
BR Thomas
Thomas, thank you for reaching to us. That’s a good question. The answer is as always “it depends”
I see different scenarios for generating the links:
- hardcoded - e.g. some resources may always have the same set of links. E.g. if you’re returning the list of elements (e.g. a list of users), then each record in that list can have a link to its details (e.g. /users/{userId}/detail ). Such links do not have additional identifiers and are not based on the rules so that they may be added during the mapping,
- calculated - such can be made on the actual state of particular record (like state machine). E.g. deleted record won’t have the same set of commands as the regular one. If the reservation is not confirmed then you can cancel it, but if it’s confirmed then you cannot do that anymore etc. Such logic can be: calculated on-the-fly, applied during the projection logic, or transferred from the write model (as you suggested). As always it’s hard to say which one is better. “On-the-fly” allows you to update rules without changing the state. As always it depends if it’s more of a mapping or more complicated logic.
- related data - links may not only represent commands, but also related items and, e.g. queries. They’re usually made based on the related items identifiers (e.g. invoice may contain links to the customer details, to the company address, payment etc.). Both links to queries and commands should be made based on the identifiers that need to be sent in events. However, we need to be aware that in event sourcing we’re sending granulated events. So using the invoice example - we’ll get customer id in the OrderConfirmed event, payment id in the OrderPaid event. Then based on that projection can be applied.
- role/permission based - some operations may be based on user permissions. E.g. someone can view user details but cannot edit or delete the user. Such user should not get the link to read-only operations (e.g. get details) but not to the edit or delete. Usually, it’s safer to do it on-the-fly, as it’s more secure. We could have published event with user permission change, but this would need to probably update all the projections that user has access which would not be the best option.
- feature toggle based - similar to permission, but some user may have feature toggle on or off irrespective of the other logic.
What we need to remember is the multiple projections can handle the single event. Also, write model should not be aware of the read model logic and the API structure (so we should not send the exact links). Rules-engines also have tendencies for changing often - it’s hard to predict how it’ll evolve. The benefit of calculating it in write model and sending it in events is that we have centralised logic for that. The downside is that event structure may change quite often because of that.
How to transfer the data? From my perspective, the best would be trying to reuse as much as we can on the event data - so putting related ids etc. and making our event types informing about the specific operation. Based on that, we can perform the logic. We can also use custom metadata. Event Store allows that, see more in https://developers.eventstore.com/clients/dotnet/5.0/streams/stream-metadata.html#writing-metadata. We could, of course, set the exact links but then we’re tying the write model with the API structure, which is usually not the best idea.
I think that the best is to use different strategies for different cases. It’s also good to not mix them in one code, but create some pipeline that will check the different set of rules separately so, e.g. AllPossible => Filter Out based on permissions => Feature toggle => calculated etc.
I also recommend watching Scott Wlaschin talk “Designing with Capabilities” - https://www.youtube.com/watch?v=fi1FsDW1QeY. I think that it’s a great explanation of capabilities.
Thomas, what are your thoughts on that? Was it helpful?