
Matthias Noback
15 februari 2016
Matthias Noback
15 februari 2016
In the previous parts of this series we looked at how to get rid of complexity at the level of algorithms. After discussing the problem of s in your code, we looked at object lifecycles and how to encapsulate them properly. Now that we have objects that can be constructed and changed only in valid ways, we need to look at how they communicate with each other and how we can improve our code with regard to that aspect.
Whenever one object calls a method on another object, they communicate: there is a sender, a receiver, and a message being passed. The message is of a certain type and has certain values, which make the message unique. When we call :
$a->doSomething($b, $c, $d);
When we call the same method again, even if we supply the same arguments, the message will be processed again, which makes the message unique:
// same values, new message: $a->doSomething($b, $c, $d);
This becomes more clear when we apply the Introduce parameter object refactoring:
$message1 = new Message($b, $c, $d); $a->doSomething($message1); $message2 = new Message($b, $c, $d); $a->doSomething($message2);
Note that even if we don’t supply any arguments, calling a method is still about communicating a message:
$a->doSomethingElseWhichRequiresNoInformation();
When the receiver of a message wants to send some message back to the sender, it can do so by defining a return value:
function whereIs(Person $person) { … return new Location(…); }
The return value itself is again a message with a certain type and a particular value.
Besides having a type and a particular value, messages can also be categorized:
We saw examples of these message categories in the code samples above. When we called object, is a document message.
Each category comes with its own, mutually exclusive intentions:
Once we know and understand the different types of messages that are being communicated between objects, we can improve the design of these objects by being explicit about whether a method supports command or query methods, and - when they are a query - whether they return something. We have to be strict about this in the implementation of our methods:
This is known as the Command/query separation principle, abbreviated as CQS. When strictly applied this has several advantages:
Practical implications for implementing command functions/methods according to the CQS principle are:
A command function looks like this:
function doSomething(/* any number of parameters here */) { if (/* problematic situation */) { throw new Exception(…); } if (/* nothing to do */) { return; } // happy path, do things … // nothing here }
Some suggestions for implementing command functions:
When documenting these functions, you should mention the fact that it has no return value:
/** * @return void */
You may optionally mention exceptions that can be thrown by this function:
/** * @throws SomeSpecificException */
Usually only interfaces need such a detailed specification of the exceptions that can be thrown, offering some suggestions to the implementer.
The opposite of command methods are query methods. The implementation of a query methods has the following characteristics:
The general structure of a query method looks like this:
/** * @return SomeSpecificTypeOfValue */ function getSomething(/* any number of parameters */) { if (/* we can’t satisfy our client */) { throw new Exception(…); } return $somethingOfTheExpectedType; }
Instead of throwing an exception you could also choose to offer a fallback value, which is nevertheless of the expected type, like an empty array:
/** * @return SomeSpecificTypeOfValue[] */ function getThoseThings(/* any number of parameters */) { if (/* we can’t satisfy our client */) { return []; } return […]; }
You may recognize these patterns from a previous post on ways to get rid of null
Anything that is returned by a query function should be considered a document message. You can model it in any way: a primitive (in PHP: scalar) value, an array, or an object. As mentioned previously it should be considered a neutral message: it doesn’t come with any intention for the recipient. The receiver of a document message isn’t required to do anything with it.
Whenever you return document messages, you need to be careful about the mutability of the message itself. In general, you shouldn’t allow the receiver to modify the message. Maybe the sender still carries a reference to the original message object, or maybe the message is sent to other receivers as well. As mentioned in the previous article, only make objects mutable that are consciously designed to undergo changes.
We’ve discussed CQS (Command Query Separation) already: object methods should be either command or query methods. Another design principle, called CQRS (Command Query Segregation Principle), takes this one step further. It brings the separation of command and query methods to the object level: objects have either command methods or query methods. This means there will be objects that accept change requests and there are different objects which supply information:
class Entity { private $id; private $something; public function changeSomething($newValue) { $this->something = $something; } … } class SummarizedRepresentationOfTheEntity { private $id; private $something; public function getId() { return $this->id; } public function getSomething() { return $this->something; } … }
The ultimate application of CQRS is in the separation of write from read models, including their underlying storage facilities. The main question is of course: how will the correct data, provided when calling ?
This supposed dilemma can be solved by introducing events.
Events should be considered a fourth category of messages, separate from command, query and document messages. An event message is used to record whatever has changed when a command message was processed. That’s why they are much like document messages. Events are different though, since they are not the answer to a query message.
Events are used to record changes and then broadcast them. Event listeners can respond to new events and project the changes they represent onto read models.
For example, inside our causes an event to be recorded:
class Entity { private $id; private $events = []; public function changeSomething($newValue) { $this->events[] = new SomethingChanged($this->id, $newValue); } public function getRecordedEvents() { return $this->events; } }
The event class itself might look something like this:
class SomethingChanged { private $id; private $something; public function __construct($id, $something) { $this->id = $id; $this->something = $something; } public function id() { return $this->id; } public function something() { return $this->something; } }
An event listener could respond to the event and replace corresponding read model instances:
class UpdateSummarizedRepresentationOfTheEntity { public function whenSomethingChanged(SomethingChanged $event) { $listingObject = $this->readModelRepository ->getById($event->id()); // create a new SummarizedRepresentationOfTheEntity instance $replaceWith = …; $this->readModelRepository ->save($replaceWith); } }
There are some interesting remarks to be made on the above examples:
Recording changes in “command” objects allows for some very interesting opportunities. Instead of only having access to the current state of things, you could remember any change that happened to this object, ever. The only thing you need to do is store the event objects in something appropriately called an event store. Whenever you reconstruct an object, you just replay any previous event inside the object to bring it back to the state in which you left it. Then you can call a command method on the object, which will cause a new event to be created, which will be appended to the event stream of the object.
One of the biggest advantages of this approach is that you can get invaluable insights from all the historical information that is now available.
In this article we’ve equated calling methods with sending messages. Command messages are supposed to cause a change inside an object. Query messages aren’t supposed to change anything, only to return some information. That information should be considered a document message. We correctly apply the Command Query Separation principle when we strictly distinguish command functions from query functions.
When a change occurs inside an object, an event message may be sent to inform interested parties of that change. Separating objects that undergo changes from objects that give information is called Command Query Segregation principle.