Hi All
For example
I have two Aggregate roots
1AR
class Person : AggregateRoot
{
int Age {get;set;}
}
2 AR
class Department : AggregateRoot
{
List<Guid> persons;
RegisterPerson(Guid personId);
}
There is a business rule that says that a person may be registered in
the department, if its the age is than 25 years
My Question
How to implement this rule, if I have no reference to the person, but
only an ID
or implement a rule in the command handler
void Handle(RegisterPersonInDepartment cmd)
{
var person = domainRepository.Get<Person>(cmd.PersonId);
var department =
domainRepository.Get<Department>(cmd.DepartmentId)
if(person.Age > 25) department.RegisterPerson(person);
}
> I would think that this should be implemented as a Saga. You should be ableWhy we should use a read model if we have a Person AR with the age in
> to look up the age from the read model and pass anything required to
> Department, i.e. the Person ID and age.
it?
Why we need a saga if we have only one operation with the one AR?
Who or what is deciding how to send that command in the first place? Could this simple validation rule be someone else's responsibility? Do departments have variable age requirements?
I think Nils primary reasoning behind suggesting a saga (or even a simpler event handler) is that these objects are typically allowed to query read-stores, as they are making decisions/taking actions based on the facts already established, as opposed to decisions based on commands, or requests. Another way of saying that, is it is ok to give sagas (or event handlers) access to the read stores.
- Nick
I understand that a Saga or CommandHandler can get access to the read
side, but I don't like to use it, if all necessary information exists
in the write side, e.g. in the Person and Department ARs. I think that
write side should never get data from the read side if it possible in
other way.
Sagas querying read stores? They coordinate, remember, react and
timeout, but they dont go out to fetch state to take a decision.
That's a sign of a missed message.
Yuk. Read models equal last resort in my book.
Most of the time they
are not even optimised for these type of queries anyways.
There's not
even A read model.
Objects should collaborate to get things done, otherwise you'd end up
with an anemic, primitive type obsessed model all over again.
void RegisterPerson(Person person) {
If(!person.IsOlderThan(25.OfAge())) {
throw Up();
}
//...
}
- I meant reusing a UI read model for answering write model queries.
If you have a dedicated read model for these, than fine. Although one
should not expect full consistency
- as for state exposure, dont do it, use TDA (but since we both
dislike state exposure I presume you're already doing that)
- no, reading and writing are not done from the same model, but I've
done that in the past ;)
TDA: http://pragprog.com/articles/tell-dont-ask
ya well that make sense.Just to summarize the decisions1) We will have a Validation rules in UI depends on the Usability requirements2) We may also have the same rule in the Aggregate but this condition comes only when the Rule is changing often.
Well... it depends. If those validations are important part of your domain and not just only for this particular application, you want your design to reflect that in your domain model as well. Especially if you are using DDD.You might also have several applications as sources of commands. All of these applications might not have the same usability requirements. Thus you need that validation in your domain.
This assumes noone will bypass your client. I'm not saying you can't
trust the client, but you better build in security if you're going to.
Which comes back to my statement, why do it with a read model, when
you could express it using readable, encapsulated domain behavior. I'm
not saying the read model query won't be readable, but you'll be
putting "You can't do this behavior under the age of 25" in the
command handler (the application service).
So, I'm to conclude you rather go out to a readmodel to get the
birthdate, calculate the age, pass it into RegisterPerson (what AR is
that method on?),
and have the compare in there? All, that
to avoid
having the Person AR track its own birthdate(ergo derived age) and
have it collaborate with the Department to produce a Registration.
Where do you get the idea that ARs can't collaborate?
It's not to avoid having the Person AR track anything, but to avoid it slowly becoming a read-model.Where do you get the idea that ARs can't collaborate?I'm not ruling out collaboration, just that in this example, the Person AR is used strictly for reading, which in light of CQRS, I consider a code smell.
This set of problems naturally arises whenever one develops a
non-trivial domain model. It is obvious, that there is no immediate
"easy" solution.
Here is my take on this: the problem simply stems from the fact that we
are considering a collaboration. There are three entities involved:
Person, Department and Registration. So the possible points of contact are:
a) have Person expose its Age (to be used by Department or Registration)
b) have Person expose an IsOlderThan guard method/function (to be used
by Department or Registration)
c) move the rule into Person
d) use the readmodel to inject the age via command to the Department or
Registration
e) have person provide the age
If we are within one context, I personally rule out option d. The reason
is that coupling via readmodel is even worse than exposing a property.
It still couples but the coupling is hidden. This is a bit different if
we are talking about separate contexts, there the age information would
simple become part of the external interface. But within one context,
using the readmodel kindof defeats the idea of having a domain model.
So I am left with three options within the domain model. All of them
couple Person and Department or Registration. Which I consider perfectly
ok, what would be the point of having a domain model if the objects may
not interact?
Hence, I will consider different principles in deciding what to choose.
First, I like to look up Nicola et al's Streamlined object modeling for
such questions. The relevant passage is:
"A validation rule verifies a property value against a standard that is
not dependent on the properties of the other potential collaborators.
The object with the clearest access to the standard is the owner of the
collaboration rule." [SOM p55]
This seems to be either the Department or the Registration, depending on
formulation of requirements. At any rate this immediately rules out
option c. Person must not know about rules of the department.
Now SOM make heavy use of getters and setters. They would go for option
a. Since I instead intend to follow TDA, I cannot use a, which leaves me
with b or e.
But, actually IsOlderThan is a getter, too. And here I differ from
Ernst's implementation. So I play the TDA card again and dismiss option
b as just a copy of a.
Which leaves me with my desired strategy "have person provide the age".
But to whom? Now the registration is the entity mediating the
collaboration, therefore I suggest that Person checks its own rules,
department checks its own rules and registration checks the rules
governing the collaboration. Since they are potentially complex, I
actually move them into a factory.
In (pseudo)code:
Department{
RegisterPerson(Person person){
GuardIfICanRegisterAtAll();
var registration = new RegistrationFactory();
registration.SetDepartment(this);
person.RequestRegistration(registration);
registration.Do();
}
}
Person{
RequestRegistration(RegistrationFactory registration){
GuardIfICanRegisterAtAll();
registration.SetPerson(this, this._age);
}
}
RegistrationFactory{
SetDepartment(Department department){
_department = department.Id;
}
SetPerson(Person person, Age age){
_person = person.Id;
_age = age;
}
Do(){
EnsurePreconditions();
AgeRule();
new Registration(RegistrationHandle.Create(), _department,
_person); // which in turn emits the appropriate event which is
collected by a UoW.
}
EnsurePreconditions(){
if (_department==null) throw new
RegistrationFailedDueToInternalError("Department not provided");
if (_person==null) throw new
RegistrationFailedDueToInternalError("Person not provided");
if (_age==null) throw new
RegistrationFailedDueToInternalError("Age not provided");
}
AgeRule(){
if (_age < Age.FromYears(25)) throw new
RegistrationFailedPersonTooYoung(); // <-- here is the thingy
}
}
and the command handler
Handle(RegisterPersonInDepartment cmd)
{
var person = repository.Get<Person>(cmd.PersonId);
var department = repository.Get<Department>(cmd.DepartmentId);
department.RegisterPerson(person);
}
(using cqrs/event sourcing with if-it-doesn't-throw-it-succeeds
semantics and auto-registration of the dirty aggregate)
This way, encapsulation is preserved, TDA is observed and the rules are
with their respective owners. Requiring person to provide the age to the
registration factory is fine - this simply is part of the factory's
contract. Person is of course free to deny the collaboration.
My 2 cts. What do you think?
Cheers
Phil
Remembering the birthdate of the person seems easier than going to the
read model, that's all.
As for using another AR for reading purposes, this is always going to
be the case when multiple ARs are collaborating.
Afterall, we should
only be affecting one AR at a time.
It's safe to say I disagree with the both of you, because to me this
is breaking encapsulation
I'm interested in how you guys perceive that this is a smell (and it
should be obvious that I'm not exposing Age as a property on Person,
I'm merely asking a question in UL terms (IsOlderThan which could be
internal)).
a) have Person expose its Age (to be used by Department or Registration)
b) have Person expose an IsOlderThan guard method/function (to be used by Department or Registration)
c) move the rule into Person
d) use the readmodel to inject the age via command to the Department or Registration
e) have person provide the age
If we are within one context, I personally rule out option d. The reason is that coupling via readmodel is even worse than exposing a property.
It still couples but the coupling is hidden.
This is a bit different if we are talking about separate contexts, there the age information would simple become part of the external interface. But within one context, using the readmodel kindof defeats the idea of having a domain model.
if (_department==null) throw new RegistrationFailedDueToInternalError("Department not provided");
if (_person==null) throw new RegistrationFailedDueToInternalError("Person not provided");
if (_age==null) throw new RegistrationFailedDueToInternalError("Age not provided");
}
My 2 cts. What do you think?
Cheers
Phil
If we are within one context, I personally rule out option d. The reason is that coupling via readmodel is even worse than exposing a property.
Why?
A domain model's purpose is to encapsulate business logic. The choice between using a read-model or another AR doesn't change that.
Seems fine. I would have chosen a class design that would avoid having invalid states, to avoid EnsurePreconditions, but beyond that the approach seems reasonable, but perhaps overkill? I'm surprised Yves doesn't call it "all that" :-)
That's a real OO/DDD approach.
It doesn't break encapsulation. It's clean, easy testable (without
involving any infrastructure), and pragmatic (in all senses).
Basically it's collaboration between objects due to application of Tell
Don't Ask and Information Owner.
Also it has the same degree of consistency as asking read store, thus
using read store in this case have no benefits, it will only make domain
objects more dumb and command handlers more fat, and will likely end up
as just a reminiscence of Transaction Script (Controller-like style).
All data required to fulfill business behavior (rule) should be
available on write side, inside of domain objects which represent
business entities \ processes. I'll second Yves on that as well.
Shifting all information retrieval logic on a client, is a shift of
system (domain) intelligence, IMHO. As a knowledge of where to get
particular information (or whom to ask as in Yves' sample) is intrinsic
part of it.
The model should be cohesive. It is built with that purpose (in DDD).
And it usually highly cohesive within single Bounded Context.
Yevhen
17.02.2012 1:06, @yreynhout пишет:
> void RegisterPerson(Person person) {
> If(!person.IsOlderThan(25.OfAge())) {
> throw Up();
> }
> //...
> }
>
> On 16 feb, 18:31, Nils Kilden-Pedersen<nil...@gmail.com> wrote:
>> On Thu, Feb 16, 2012 at 10:20 AM, ShuriK<alex.ziz...@gmail.com> wrote:
>>> I understand that a Saga or CommandHandler can get access to the read
>>> side, but I don't like to use it, if all necessary information exists
>>> in the write side, e.g. in the Person and Department ARs. I think that
>>> write side should never get data from the read side if it possible in
>>> other way.
>> It means that you have to break encapsulation and expose all data outside
>> the class, making an anemic domain model more likely.
>>
>> But if that's an acceptable trade-off, then who am I to argue?
We were discussing different case: checking that person is older than 25
years is a business rule and should be expressed on a domain level.
But checking that person id, passed in a command, is indeed refer to an
existing entity, is an application level validation. You can't do it on
a domain level, cause domain is not aware of persistence (and should not
be). Domain operates on already provided references (objects), so you
usually don't have something like department.RegisterEmployee(Guid
employeeId) but rather department.Register(Employee employee).
BTW, in Yves' example, that validation of id existence is done in a
right place - in a command handler, which is an application level mediator.
var department = repository.GetById<Department>(cmd.DepartmentId);
var employee = repository.GetById<Employee>(cmd.EmployeeId);
The natural approach here is to add existence checking and blow up if
you can't obtain aggregate by a given id.
if (employee == null)
throw EmployeNotFoundException("Can't find employee with..");
What we did is just added a simple Guard Clause on a Boundary. We just
asserted whether we can further proceed with the operation at all.
Yevhen
19 О©╫О©╫О©╫О©╫. 2012, О©╫ 18:04, belitre <bel...@gmail.com> О©╫О©╫О©╫О©╫О©╫О©╫О©╫(О©╫):
>> 17.02.2012 1:06, @yreynhout О©╫О©╫О©╫О©╫О©╫:
> Which brings about one of my long-standing questions which is how to
> deal with validation which requires many aggregates of the same type
> without the readmodel. I have been using a combination of both
> readmodel and domain validations, as primarily expressed above,
> because of the situations where I want validation like "no more than
> 10 persons over the age of 25 can be registered at one time".
No, it doesn't require read model. You either missing an aggregate or
need to use Sagas.
In you example if creation and registration of person are two
independent actions, then
you can encapsulate this business logic in an aggregate:
class Department : Aggregate
{
const int MaxNumberOfEmployessOverAge25 = 10;
List<Employee> employees;
int numberOfEmployessOverAge25;
void Register(Employee emp)
{
if (emp.IsOlderThan(25)
{
if (IsLimitReached())
throw new RegistrationLimitReached(...);
numberOfEmployessOverAge25++;
}
employees.Add(emp);
}
bool IsLimitReached()
{
return numberOfEmployessOverAge25 = MaxNumberOfEmployessOverAge25;
}
}
If both creation and registration should be done atomically, then you
need to use a Saga,
which will make a multi-step process, look like an atomic one.
So under the hood you will still have 2 discrete actions: create and
register.
Te saga will compensate creation (by issuing delete) if registration fails.
Yevhen
20.02.2012 2:08, Aaron Navarro пишет:
No, it doesn't ;)
Checking that passed 3-letter ISO code resolves to a valid currency
is still an application level validation.
class InvoiceHandler
{
void Handle(SubmitInvoice cmd)
{
...
var currency = Currency.From(cmd.Currency);
if (currency == null)
throw new BadInputException("Bad currency ISO code");
if (cmd.Amount <= 0)
throw new BadInputException("Amount should be > 0");
var amount = new Money(cmd.Amount, currency);
supplier.SubmitInvoice(.., amount);
}
}
When domain is about to be invoked, everything should be already determined
and references are retrieved and checked.
I never do checks like IsValid or IsExists in domain.
Domain is always in a valid and deterministic state.
The only checks that Domain objects might have are argument checks,
like if arg is null or empty string. And people don't even bother to
have this
because an input is already pre-validated on a boundary.
If you have such problems it could be a sign of Primitive Obsession smell.
CQRS doesn't force you to have only primitive entities with primitive
fields.
On opposite, by introducing another layer of indirection like Commands,
it allows you to evolve your domain without being concerned about contract
breaking issues. So while you command feature primitive data, when it comes
to application, data is converted into a set of more sophisticated objects,
collaborating to complete a specific use-case. Command handlers are in fact
classic use-case controllers.
IMHO, the power of DDD lies in all those small and useful objects like
Money (where you can encapsulate your rounding and allocation rules),
Currency, DateRange etc. You can find many of those in EAP, Analysis
Patterns,
and Archetypes. And Eric's example of share pie math is what completely
blown me away and inspired to learn, grasp and apply DDD for many years.
To return to our discussion about relative cohesion of model elements:
If you open Analysis Patterns, you will see that in any of given models, all
elements are highly cohesive - they tightly collaborate to complete a
specific
computation or enforce a concrete business rule.
Hope it helps,
Yevhen
20.02.2012 9:27, belitre пишет:
void Handle(RegisterPersonInDepartment cmd)
{
var person = domainRepository.Get<Person>(cmd.PersonId);
var department =
domainRepository.Get<Department>(cmd.DepartmentId)
if(person.Age > 25) department.RegisterPerson(person);
}
I have five issues with using the readmodel.
#1 is that I believe the business logic of one bounded context using a domain model should be fully encapsulated in that domain model. That includes the interactions and collaborations. Having a path - even it is only for data access - outside of the domain model for the sole purpose of not explicitly accessing that data within the model doesn't make a lot of sense to me.
#2 is testing. I want to be able to test any domain by itself. And that includes the collaborations which are usually an important part of the model's behavior. But I do *not* want to have my read model projections be a part of that.
#3: I once learned that all dependencies in which a domain model participates should point inward to the domain model. This includes both afferent and efferent ones. Having to depend on the readmodel violates that.
#4: Let me ask, what is the reason to invoke the readmodel? Usually I hear "aggregates should not depend on each other". Frankly, that is nonsense. A domain model consists of classes that of course have interdependencies. While good model design tries to reduce and qualify these dependencies, having *none* is not of value by itself. An aggregate's fence doesn't say 'no access' but rather 'take me as a unit or not at all' to other aggregates.
#5 is that I did it. That part of the system became a black hole pulling more and more of first data and then logic away from the domain model. Over the course of two years it gradually shifted from "nice shortcut" to "nightmare".
Of course, one can do it, and it will work.
But successfully only for some time or for non-complex systems or for systems that do not change. I.o.w. for systems that do not really call for a domain model :)
My 2 cents,
Julian