2

I have an aggregate root called SizeRangeAggregate which holds the range of size of clothing piece dimension and price for that range. I created an post rest API which create new SizeRangeAggregate. It follows the CQRS/ES which means

  1. it creates CreateSizeCommand
  2. publish it to CreateSizeCommand command handler
  3. generate SizeRangeCreated event
  4. persist the event to eventstore
  5. publish the event to SizeRangeCreated event handler where the read model is generated called SizeRangeReadModel

I want to add a simple domain logic where an API will not create a new SizeRangeAggregate if the range is already defined in other SizeRangeAggregate.

Currently I am using the read model range filter (something like getByRange) in API controller to get the SizeRangeReadModel if exist, before creating CreateSizeCommand . But I think I am doing it wrong as this is a domain logic and thats why should not be in controller.

Could you suggest me best option/ way to implement this.

  • Set validation is tricky in any system. Is there a reason Repository.Add( SizeRange) can't throw an exception if an aggregate already exists? Without loading every SizeRange into memory, the only other option is to pass this invariant to your Repository for validation. Conveniently, your Repository already knows the key piece infrastructure necessary to carryout validation. What is a Repository other than the simulation of an in-memory collection anyway? – king-side-slide Oct 3 '18 at 17:48
1

But I think I am doing it wrong

You've got good instincts. There's a lack of clarity in the thinking around this scenario and everything will be easier if we can clean that up.

I have an aggregate root called SizeRangeAggregate which holds the range of size of clothing piece dimension and price for that range.

Okay, so I'm imagining your class is something like:

class SizeRangeAggregate     {   
  size SmallestSize = size.Small;   
  size LargestSize = size.ExtraExtraLarge;   
  currency Price = $0.00; }

So, what's the problem here?

The problem is that this is not an aggregate root -- or, to truncate a little less -- this is not an aggregate root entity.

This is not an entity at all.

This is a value object.

If you accept this as now self-evident, it should raise further questions:

  • What does it mean to 'create' a 'new' value object by POST?

  • What does it mean for a value object, or a piece of a value object, to 'already exist'?

It might be easier to think of these questions in the context of a more-universal, less-Domain-bound value object -- like Dates, for example.

Better thinking around these questions and the role of this particular Value Object within this particular Domain should make everything much clearer.

(To hazard a guess at a Domain that I know nothing about, I might guess that the actual Entity you want to deal with is something like a set of non-overlapping SizeRanges at a particular point in time, and your operations will consist mostly of querying this set and adding or removing set items.)

0

But I think I am doing it wrong as this is a domain logic and thats why should not be in controller

Although it is domain logic it is a requirement that affects multiple Aggregates, so a service is the place to put the code. But, this business requirement is implemented mainly using technology/infrastructure, so the domain logic can be implemented with simple code that looks like this:

if(repository.theAggregateDoesNotYetExist(some, data))
    sendCommand(createAggregate)

The simplest solution is to put this code in the Application service that normally sends the command.

I do it another way. My CQRS framework has the concept of command validators. These are components that intercept the commands and, if the context does not permit them, they reject them. I don't use command validators to protect business rules that should be checked inside the Aggregates. Command validators are also eventually consistent so they don't protect from concurrent inserts. Together with a Saga they can eventually bring the entire system in a permitted state.

Command validators help following the Open-closed principle (you can add as many business rules you want without modifying existing code) and Single responsibility principle (they validate only one command for a single business rule).

The way this pattern is implemented is by decorating the CommandDispatcher.

  • The term "Command Validator" does not make sense in the context of DDD, where your domain is the command validator. Separating command validation from the data necessary to carryout such validation is fundamentally procedural in nature (simply using objects doesn't mean one is practicing OOP). Functionally passing data through a pipeline is a valid way of organizing a system.. just not a DDD system. The entire purpose of DDD is to model business rules (mutation) into your domain such that it is impossible to circumvent them. This can only be achieved if the rules are hard-coded to the data – king-side-slide Oct 3 '18 at 17:39
  • @king-side-slide one cannot all within Aggregates do. These CV are implemented as domain services in flat (not CQRS) architectural styles, where multiple Aggregates are involved. – Constantin Galbenu Oct 3 '18 at 18:11
  • If an invariant spans multiple aggregates your model must, by definition, be incorrect. The purpose of an aggregate, as you mention above, is to enforce a consistency boundary. This cannot be accomplished if the aggregate does not "own" it's data. If multiple aggregates are necessary to carryout a use-case, they may all be retrieved by a command handler and coordinated as necessary. Importantly, coordination is not the same as enforcing business rules. At any point during this coordination, an exception may be thrown. – king-side-slide Oct 3 '18 at 18:22
  • @king-side-slide not necesarily. For example, the Authorisation is a perfect use case. Also, the business can decide to have rules that are not so strongly consistent. – Constantin Galbenu Oct 3 '18 at 18:25
  • 1
    @king-side-slide please let's use other method of communication. Commenting is hard. – Constantin Galbenu Oct 4 '18 at 16:15
0

I want to add a simple domain logic where an API will not create a new SizeRangeAggregate if the range is already defined in other SizeRangeAggregate.

This sounds like a case of Set Validation; Greg Young reviews the details well.

We talk about event streams as though they are tied to an aggregate, but the truth is that they are much closer in practice to the write ahead log of a database. Deciding that each aggregate has its own stream is analogous to saying that each aggregate lives in its own (logical) database.

(If you consider an RDBMS; it's easy to constrain all of the rows of a table in one database, but it is much more challenging to apply that constraint across two different databases).

If each aggregate has its own unique stream, then verifying a constraint across the set of aggregates at the moment of write isn't going to happen. You can use views -- copies of information prepared by reading from multiple streams as a surrogate for an absolutely correct check, but there will be race conditions that lead to conflicting writes that you will need to resolve later.

Within the domain model, this view would normally be represented as a domain service -- it would provide an API to let you know if a given range is unique to a cached set of ranges, and the domain logic could use that information.

But the risk that the cache is out of data never really goes away.

This is why the critical question is

what's the cost to the business?

If this validation is something critical to the business, then you need a domain model that supports it -- and that's not a model where the set is distributed into multiple independent parts.

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.