jdumont
CQRS with Commanded - Return directly from aggregate
Afternoon all, I’m back with another ES/CQRS question that’s perhaps more philosophical (for lack of a better word) than technical.
Event-sourcing with Elixir and @slashdotdash 's Commanded actually seems a little different than it is in other languages thanks to the BEAM. Each of the aggregates in our app actually exist as an individual process, and they hang around until they are stopped.
I have an aggregate in my app that builds workouts — I’m back on this CrossFit app — and whilst they are being created they exist as a process and my projection in the db is effectively a mirror, just with some computed fields added which aren’t relevant during the workouts creation. Once the status of a workout changes from "draft" to "published" I’ll be stopping the aggregate as-per Commanded’s docs and the latest post from Bruno, and just the projection of it should suffice.
However, whilst I’m building the workout, it would be really handy to just return the current state of it from the aggregate, rather than the projection. My projection literally just copies out the aggregates state and I don’t need to rely on the projection for unique validations, so I don’t see a huge difference in just returning the aggregate rather than the projection. It should be faster as eventual-consistency wouldn’t be an issue and there should be no issues with validating commands as I’m quite literally working from the canonical source of truth.
This is why my question is more philosophical than technical. What I’ve just described goes against CQRS as I understand it, but it seems to make more logical sense?
Most Liked
benwilson512
I think @slashdotdash 's explanation does a good job of noting why this is at odds with the traditional CQRS mantra of having distinct read and write models.
The approach I took with GitHub - CargoSense/fable: Your events have a story to tell. · GitHub leans slightly more towards what you’re asking for by eschewing CQRS in favor of a simpler implementation of Event Sourcing.
Basically you have database table, say “shipments”, and Fable guarantees that each event you emit for a given shipment row is handled serially by that shipment row. There may well be other database tables that are affected by that same event (containers or products in that shipment) and in that way the “shipments” table acts a lot like an aggregate. All changes to the shipments table and the containers table should happen because you cut an event, and then the event handler for that event makes changes to the various tables based on the event.
However with Fable as written today you still just read from the database normally when it comes to querying data, and it doesn’t push you to maintain a separate read vs write model. This is either a feature or a big deficiency depending on how wedded you are to CQRS.
Sadly Fable is still alpha stage and missing any useful documentation
Nonetheless I think there’s a “market” for a middle ground for a more minimalist library that supplies event sourcing guarantees without requiring buy in to the entire CQRS world view.
jdumont
I’m going to go ahead and answer myself after half an hour playing around with this idea…I shouldn’t / can’t return the state of the aggregate directly. Commanded doesn’t seem to allow for it, which must be for good reason.
This is why I’ve enjoyed working with CQRS so far, whilst there’s a lot more procedure, it keeps me from shooting myself in the foot! ![]()
slashdotdash
Commanded allows you to directly access an aggregate’s state via the undocumented Commanded.Aggregates.Aggregate.aggregate_state/2 function:
alias Commanded.Aggregates.Aggregate
%BankAccount{..} = Aggregate.aggregate_state(BankAccount, account_number)
The reason why this isn’t exposed publicly is because an aggregate’s state should be an internal implementation detail. Instead, CQRS promotes the concept of two models: one for writes (aggregates) and one for reads (projections). Commands are handled by the write model and queries are handled by the read model.
In your example you have noticed that the projection (read model) and aggregate state (write model) are very similar, so it might save you some time and effort to use the aggregate state instead of creating a separate read model. This works until the needs of the two models starts to diverge, due to differing data access patterns. At which point you either migrate to a separate read model, or extend the aggregate state to store data which isn’t necessary for command handling. Investing the effort from the outset by implementing the two separate models should payoff as your model becomes richer in behaviour.
Last Post!
slashdotdash
Using a separate elements table with a jsonb column would be a hybrid approach that might work (basically using Postgres as a document DB!).
Popular in Questions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #elixirconf-us
- #advent-of-code
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #hex
- #security









