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? – user3347715 Oct 03 '18 at 17:48

3 Answers3

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.)

Roger
  • 287
  • 1
  • 3
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.

Constantin Galbenu
  • 3,242
  • 12
  • 16
  • 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 – user3347715 Oct 03 '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 03 '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. – user3347715 Oct 03 '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 03 '18 at 18:25
  • If by "Command Validator" you mean "Command Handler", then fair enough, but the idea of adding business rules without modifying code (aggregates) is quite literally the antithesis of DDD. Your domain *is* your business rules! – user3347715 Oct 03 '18 at 18:25
  • Authorization and Authentication are best handled outside your domain. So sure, conceivably this could be done in a `UserMustBePrivileged` command validator (or put in any piece of modularized application service/configuration) – user3347715 Oct 03 '18 at 18:28
  • @king-side-slide my CommandValidators check business rules that are defined in other BCs. For example, checking for abuse in an ecommerce application – Constantin Galbenu Oct 03 '18 at 18:36
  • If a user's actions are limited by their `AbuseStatus`, then those actions should be grouped with that piece of data. Separating the the two into different bounded contexts only creates this problem and moves the focus of your application layer towards data instead of behavior (tell don't ask). Often problems like arise when modeling is treated as an exercise in "architecture" rather than "discovery". Alternatively, `AbuseStatus` can be treated as a part of Authorization (decoupled from the domain), and therefore not part of any bounded context. FWIW, this is how I would treat it. – user3347715 Oct 03 '18 at 18:47
  • @king-side-slide regarding your command handlers, there is a style that I use (see cqrs.nu) that makes them unnecessary. I have observed that all comand handlers athere the same in CQRS: identify aggregate, rehydrate it, send command to it, collect the events, persist them then publish them to in process readmodels or sagas. A pattern emerges that can be implemented by a miniframework. This miniframework then can also detect Aggregates, commands, event handlers and also command handlers, so useful for authorisation. – Constantin Galbenu Oct 03 '18 at 19:37
  • @king-side-slide Authorisation in CQRS is quite simple to do because you just have to authorize the Commands and the Queries, and this can be auto wired/discovered. – Constantin Galbenu Oct 03 '18 at 19:38
  • I would disagree with passing commands directly to aggregates. Not only does that create unnecessary coupling (in the wrong direction), it simply doesn't work when commands have complex behavior that spans more than one aggregate. Command handlers should be coordinating the domain. So in the example from cqrs.nu `OpenTab` should yield something like `Tab.Open()` in a handler. In any case, I don't see any utility in a mini-framework other than to provide the temptation to increase coupling and confuse where rules are enforced because it makes it possible. – user3347715 Oct 03 '18 at 20:35
  • Sure it could help with Auth, but there are other strategies as well (pass a context to every command handler, for example) that keep code decoupled. What you describe makes it impossible to know the path of any command by simply looking at the source, instead forcing a developer to understand all of the run-time bootstrapping that takes place. Command handlers should read like a script. And like a script, that makes them extremely easy to understand (people think like scripts). – user3347715 Oct 03 '18 at 20:39
  • @king-side-slide from my experience, when you have many commands (i.e. 100) there too much repeating code like I've wrote earlier. What you are saying regarding forcing the developer is not right. There is only one additional place to look at: to the command validators map. The Ubiquitous language in this case should suffice, just reading the class name of the command handler should tell you what it does. The first place to look is at the aggregate's handle method. – Constantin Galbenu Oct 03 '18 at 20:51
  • @king-side-slide That coordination that you are saying is using in any case stale data, from some projection because in CQRS you cannot query an Aggregate. It can(and should) be extracted in a command handler. Those complex command should not exist in the first place, **unless** they represent a process so a Saga is a better place to put such coordinating behavior, not in a command handler, which **should not** wrap multi-aggregate commands in a single transaction (that is anti DDD). – Constantin Galbenu Oct 03 '18 at 20:53
  • It's perfectly possible for a `WithdrawMoney` command to invoke `BankAccount.Debit(amount)` and `ATM.Dispense(amount)`. These two actions are bound to a single transaction (for obvious reasons). – user3347715 Oct 03 '18 at 21:14
  • Look, I can agree with using command validators if the scope is limited to application-level concerns like authorization etc (although "command validator" would cease to be a particularly good name). But coupling them to your domain is dubious. I can't envision a situation where you **wouldn't** be "asking" an aggregate for data from a validator. How else does it work? I've never run into issues with true code duplication in a well-designed system. That's a non-issue created by separating data and behavior. – user3347715 Oct 03 '18 at 21:34
  • @king-side-slide about your example, it is simply wrong. In reality the ATM gives you money even if the internet connection fails, not as much but it gives you. In a scalable system you should not have transactions that spans multiple Aggregates. Also, this is basic DDD, the first lesson that we learn. – Constantin Galbenu Oct 04 '18 at 04:23
  • @king-side-slide in CQRS you don't query the Aggregate, the write model, **by definition**. Again, this is basic CQRS. Don't use it if you cannot imagine, but then don't call it CQRS. – Constantin Galbenu Oct 04 '18 at 04:26
  • You are out of your depth son. Not only are ATMs *not* connected to the internet, an ATM will debit an account first. If that fails, it will not dispense money. While a Saga with compensating actions may be a more scalable approach, not every system will need sharding. The purpose of DDD is to simplify systems, not scale them. Everything is a trade-off. Anyway, I didn't suggest querying a domain model. Maybe you can enlighten me, but I cannot conceive of any other way a "Command Validator" could work. How can it use the domain model to verify a command can occur downstream without "asking" it? – user3347715 Oct 04 '18 at 14:42
  • @king-side-slide I cannot continue discussing with someone that is wrapping two Aggregate command calls to a single transaction and call it DDD or query a write model (the Aggregate) in the context of CQRS. Sorry. We have an understanding of terms that is just too different. – Constantin Galbenu Oct 04 '18 at 14:54
  • Again, I'm not advocating querying a write model. Is that not clear? I'm asking *you* how *your* system of "Command Validators" can work without doing so. Does that question make sense? Earlier you said, `my CommandValidators check business rules that are defined in other BCs`. How is this done without querying aggregates? Is nothing returned to CVs? What kinds of methods could possibly exist in the other BC? `User.IsAboutToPostComment() -> throws if abuser`? – user3347715 Oct 04 '18 at 15:32
  • @king-side-slide so, every command has zero or more validators; each CV can reject the command - in this case it cannot reach the Aggregate anymore. CV query most of the time some private state (maintained by events), a canonical readmodel or sends a Query (that gets routed to a read model). – Constantin Galbenu Oct 04 '18 at 15:39
  • @king-side-slide so, CVs are using always stale data and this is the reason you cannot use them to protect strong invariants. But knowing this one can use them in situations where the business can afford that 1 in 999999 cases the rule is broken. – Constantin Galbenu Oct 04 '18 at 15:42
  • @king-side-slide I understand that this term "command validator" can be weird to you but it is the best that I could find. The term is technically correct, these components reject or pass the command to the next validator or to the Aggregate, in case there are no validators left. So they "validate" a command. – Constantin Galbenu Oct 04 '18 at 15:46
  • Your approach is just a "workaround" for querying your write model. You understand that right? Using a read model to validate commands to a write model is *not* CQRS (what does CQRS stand for again?). It achieves the exact same thing a traditional CRUD approach would achieve: `Command -> Query -> (data) -> Validation -> Mutation -> Persist`. The entire purpose of DDD is to reorder the above to: `Command -> Query -> (domain) -> Mutation -> Validation -> Persist` so that invariants *must* be enforced for every mutation of data (business rules are encapsulated *with* data). – user3347715 Oct 04 '18 at 15:59
  • By telling your data (domain) to mutate itself, you can guarantee that the same mutation requested in different commands undergoes the exact same validation (without duplication). Feeling the need to add *another* layer of rules on top seems, to me, like a workaround for a poor design. – user3347715 Oct 04 '18 at 16:00
  • @king-side-slide I see your point. It is correct only in situations where it is used when it should be done inside an Aggregate. But there are cases when this pattern can be used. – Constantin Galbenu Oct 04 '18 at 16:02
  • @king-side-slide there are situations where the data used by the CV is orthogonal-to/independent-of the data from the Aggregate (the data that mutates). – Constantin Galbenu Oct 04 '18 at 16:05
  • The very fact that the CV is using the data as part of the same transaction (and to guide the transaction) means it is not orthogonal, rather, very much coupled. Like I said above, a CV can make sense for application-level concerns like Auth, but the moment it touches the domain, you have created coupling. The biggest offense (and I touched on this too) is that the coupling is "hidden" in run-time configuration rather than just hard-coded into handlers (functionally there is no difference). This can make a system much more difficult to understand. I understand that creating traditional... – user3347715 Oct 04 '18 at 16:12
  • @king-side-slide it is outside the transaction (before). – Constantin Galbenu Oct 04 '18 at 16:13
  • ... command handlers (under your current approach) would lead to lots of duplication. But your duplication would be a result of the design, not a limitation in DDD or architecture. – user3347715 Oct 04 '18 at 16:14
  • 1
    @king-side-slide please let's use other method of communication. Commenting is hard. – Constantin Galbenu Oct 04 '18 at 16:15
  • sorry, to clarify I meant transaction in the sense of the running script. Not a persistence mechanism. – user3347715 Oct 04 '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.

VoiceOfUnreason
  • 32,131
  • 2
  • 42
  • 79