CQS is a well known principle in object oriented programming after Bertrand Meyer. It state that we should divide an object’s methods into two sharply separated categories:
- queries that return a result and do not change the observable state of the system (are free of side effects).
- commands that change the state of a system but do not return a value.
As you might know by experience, side effects are expensive. For example, for each new mutable flag in a class, the number of possible states will double. When you model a domain, you should remember that minimizing the side effects means minimizing complexity (thus maximizing the revenues). |
As for value objects, all their methods must be queries. They must be immutable and no method of theirs should modify either the arguments or the system.
Domain services could be designed to change the system (by sending an email or writing a report) and in such case you should make such change explicit.
Mutable entities could have both query methods and commands. You should cleanly diffirenciate them, so that all model’s users will grasp the rationale of each method.
We adopted an idiomatic convention with the following rules:
- queries return a value;
- commands must be void;
-
should a command returns a value, it will go through an
out
parameter that will have a meaningful name; - properties are readonly (thus they are always query);
- entity’s queries should never throw exceptions (argument validation apart);
- commands must be well documented and thrown exceptions should be listed and explained already in the interfaces.
This is probably the major concern in the choice of C# for modeling tasks. Checked exceptions would be valuable for us! We want that clients breaks as early as possible when we change the domain. |
We apply these rules without exception. Epic will provide a set of Gendarme’s rules to check the adherence to such rules, but it will not force them by default.
Factory methods are worth a dedicated section since it could be difficult to ponder whether they are queries or commands.
We all know the creational pattern that encapsulates the creation of objects in a method owned by another class (either static or not).
In the context of Epic, you should separate two kind of factory methods:
- those that build value objects (such as specifications, itineraries or moneys);
- those that build entities.
Providing a new instance of a value object does not change the system: you can
create all the copies of a PreferredStockQuantity
you like, but until you do
not Purchase(PreferredStockQuantity)
that quantity, your securities account
doesn’t change!
Since such operation do not change the observable state of the system, it is a
query and should return a value.
Creating a new securities account, instead, deeply changes the system: first
the bank smiles, but what matter is that the identifier assigned to your
account is taken forever. No other account can take that identifier.
This is definitely a command: it should be a void method and the new account
(or its identifier at least) should be returned in an out
parameter.
Idempotence is another well known principle in math and functional programming. It is the property of operations that can be applied multiple times without changing the result.
C# does not handle referential transparency and since we design mutable entities, idempotence can not be adopted as when we programm, say, in Haskell. Nevertheless it profitable to prefer idempotent APIs.
Methods of both value objects and domain services should be designed to be idempotent. However, when they get an entity as an argument the result could change among subsequent calls, as the entity recieves a command.
Each instance of an entity holds a mutable state that affects the result of depending queries. |
Entities' queries are a bit trickier: you should always get the same result when calling with the same arguments, until either the method’s owner or the arguments recieve a command.
Commands of entities are even more complex: you should design them to be
idempotent as far as possible. For example, you could prefer methods like
SetSomething()
instead of AddSomething()
and RemoveSomething()
, thus
allowing idempotence (and reducing the interface size) without
affecting comprehensibility.
Indeed, it’s often quite easy to adopt idempotent APIs. See for example the
IVoyage
interface in the DDDSample from the Epic source code: commands like
StopOver(ILocation)
and DepartFrom(ILocation)
are both idempotent and
expressive.
If a voyage either departs from a location or stop over a location twice,
it will always be in the same state.