jdumont

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

benwilson512

Author of Craft GraphQL APIs in Elixir with Absinthe

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 https://github.com/cargosense/fable 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 :frowning: 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

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! :joy:

slashdotdash

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.

jdumont

jdumont

Thanks for the reply, I’ll certainly take a look at Fable. I do agree that there’s an opportunity for an ES-lite option that doesn’t require a lot of the additional complexity that CQRS brings.

In my case I have tried to navigate that middle ground as there’s certainly bits of my app that don’t benefit from CQRS and even those that do wouldn’t be impossible without it. However, I’ve relented and gone “full ES/CQRS” just because it’s been enough of a stretch to understand the concepts without trying to work out which parts I need and which I don’t. I know that’s a terrible reason to adopt an architecture, but in the interest of flattening out my learning curve I thought it was a good idea.

My interest in the whole topic stemmed from the functional nature of event sourcing and how it suited the later parts of my app which are very easy to think about as a series of events (athletes completing workouts and movements). I found that without ES the app started taking a distinctly OO turn, starting to rely on lots of relations in the DB and even things that looked like class inheritance.

Event sourcing is a very simple pattern to understand and use, but I found that it was the persistence of the resulting state that complicated things. I like the idea of Fable and it certainly looks like it would be a good middle ground where the two models don’t need to be drastically different.

Where Next?

Popular in Questions Top

shahryarjb
Hello, I get Persian date from my client and convert it to normal calendar like this: def jalali_string_to_miladi_english_number(persi...
New
albydarned
Hello all! I am typing this post from my new MacBook Pro with the M1 chip. I’m loving it so far, and will probably use it as my daily dr...
New
aalberti333
As the title describes, I’m trying to run Enum.map() over a list of key/value pairs, where the value is a map. My data looks like this: ...
New
pgiesin
This should be a simple problem but I just can’t seem to figure it out. I have a standalone Elixir app that won’t find the database. Dep...
New
tduccuong
Hi, is there any work on GUI with Elixir, that is similar to Electron/Javascript? My idea is to bundle Phoenix and BEAM into a single se...
New
polypush135
As many of you may have realized by now (sorry for all the posts here) I’ve been working on a db problem where I’m trying to aggregate a ...
New
mgjohns61585
Could someone help me? I'm making my first elixir program, number guessing game. I can't figure out how to convert the user's guess from ...
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
Mooodi
Given a string, how can I get access to its character by index? Enum.at("my_string", 2) doesn't work. Or rather, not char, but a substr...
New
joeerl
Hello again - after a longish gap I’ve decided I really must dig into Elixir and see what’s been happening here - so I have a few questio...
New

Other popular topics Top

JDanielMartinez
Hi! May someone helps me, please! I have two apps into an umbrella project: the first one is Database, which manages queries, and the se...
New
bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
New
aalberti333
As the title describes, I’m trying to run Enum.map() over a list of key/value pairs, where the value is a map. My data looks like this: ...
New
Harrisonl
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service. Currently when I de...
New
danschultzer
None of the current solutions worked well for me, so I went ahead and built a user management system from scratch. This project took far...
548 27727 240
New
fayddelight
I tried installing elixir 1.11.2 erlang 23.3.4 via asdf in my zsh shell. Enabled the versions locally and globally. When I list them ...
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID<0.412.0> terminating ** (Postgrex.Error) FATAL...
New
romenigld
I am trying to run a deploy with docker and I successfully runned with this command: docker build -t romenigld/blog-prod . but when I t...
New
jay1
Why is it that the mnesia database isn’t the most preferred database for use in Elixir/Phoenix?
New
magnetic
Hey :wave:t3: Elixir community, I’ve been learning Elixir, and working on some side projects. My editor of choice is VSCode, and althoug...
New

We're in Beta

About us Mission Statement