2

I find it difficult allying CQRS/ES with the "Out of tar pit" paper architecture.

This architecture implies 4 layers:

  • State (state of the application)
  • Business Domain (purely functional)
  • I/O
  • Control ( all the dirty stuff for making the layers work together)

In my case, the state depends heavily on a database.

With the CQRS/ES in mind, I have a decision engine. The decision engine for producing an event from a command, is the Business Domain, which has to be purely functional.

When a command asks for an Item to be created, the decision engine chooses to accept or not to create an event CreateItem only if the Item is not already existing (simplified for the example).

In theory, I should pass the state of the application as a argument to the Business Domain, so as to decide accordingly.

But in the case of a database, I cannot pass the database as a side-effect free parameter. I feel like I have to perform the query checking the existence of the item beforehand inside the Control layer, and then pass the result of the query on the database to the Business Domain, which will then return an event or not.

The consequence is that I have some business code (the one related to the query and the business logic to perform the checkings) that will be inside the Control layer.

That feels wrong for me as far as I understood the "Out of the tar pit" paper and also CQRS/ES.

The main issue seems for me that the state is huge: it's the whole database.

What is wrong in my reasoning ?

  • Have you had a look at Monads? – Robert Harvey Jan 11 '17 at 14:38
  • I have not considered that, in the sense that I had understood the decide function should be pure. Maybe that's the problem in my reasoning. – Stephane Rolland Jan 11 '17 at 14:58
7

With the CQRS/ES in mind, I have a decision engine. The decision engine for producing an event from a command, is the Business Domain, which has to be purely functional.

Good.

When a command asks for an Item to be created, the decision engine chooses to accept or not to create an event CreateItem only if the Item is not already existing (simplified for the example).

Also good.

In theory, I should pass the state of the application as a argument to the Business Domain, so as to decide accordingly.

Yes, perfect. (Note: more "state of the domain" than "state of the application". Also, taught us that we don't need the state of the entire domain, just state local to the change we are considering.)

But in the case of a database, I cannot pass the database as a side-effect free parameter.

That's right -- all of the interaction with the database should happen outside of the business domain (which only cares about state).

I feel like I have to perform the query checking the existence of the item beforehand inside the Control layer, and then pass the result of the query on the database to the Business Domain, which will then return an event or not.

Ah. Not quite -- you don't need to check the existence of the item, you need to check its state, which is not quite the same thing.

Fortunately, you are using ES; which does make things simpler to explain: this history of an entity that doesn't exist is empty.

So in this case: you receive a command from IO. Control fetches the appropriate history from the state of the application. The model is invoked, passing in that history and the command as arguments. The model returns event(s) that are consequences of the command being run at this point in the history. Control updates the copy of the history in the state of the application.

So happy path: the commmand CreateItem(x) arrives. Command fetches History(x), which in this case is an empty sequence of events. The model invoked, it sees from the history that the item doesn't exist yet, and that the business invariant is otherwise satisfied, to it returns a representation of the ItemCreated(x) event.

Alternative path: the commmand CreateItem(x) arrives. Command fetches History(x), which in this case is a sequence of events including CreatedItem(x). The model invoked, it sees from the history that the item has previously been created, and rejects the command (throws an exception, returns an Error, returns an empty collection of events).

  • 2
    Of course, you have to use consistent aggregate identity to be able to do that, so that two concurrent commands to create the same aggregate reach the same event stream. You can also preemptively filter out in the application layer by doing a query on the view model database. The filtering describe above will the happen only in case of race condition. – thinkbeforecoding Jan 12 '17 at 8:20

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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