Separating Repositories into Commands & Queries
Christopher Davis has written this article. More details coming soon.
A typical repository interface might have a set of methods for finding entities as well as adding, updating, and removing them.
SomeObjectRepository.php
<?php
interface SomeObjectRepository { public function getByIdentifier($id); public function findAll(); public function add(SomeObject $object); public function remove($objectOrId); public function update(SomeObject $object); }
That’s okay. It’s easy enough to understand.
We’ve been doing things slightly different on a new project at PMG. Instead of a repository interface containing both the fetching and storage methods, it only contains fetching (query) methods. We keep the same name, SomeObjectRepository.
SomeObjectRepositoryReadOnly.php
The methods that change the state of the repository (commands) are moved into a separate interface that extends the repository.<?php
interface SomeObjectRepository { public function getByIdentifier($id); public function findAll(); }
SomeObjectStorage.php
This is just a form of command query separation or, perhaps more accurately, CQRS.<?php
interface SomeObjectStorage extends SomeObjectRepository { public function add(SomeObject $object); public function remove($objectOrId); public function update(SomeObject $object); }
Building a reasonably complex application means splitting things out into smaller modules. The project might have a PMG\AppName\Users namespace that contains all the stuff related to users, including User{Repository,Storage} interfaces and a User object.
Within the user module, there’s probably a lot of code that deals with modifying users and persisting them. Maybe there’s a command that promotes a user to an admin, for instance. Things that modify and persist users likely also need to fetch them first (hence XStorage extends XRepository).
Code external to the users module probaby doesn’t need to modify users as much as fetch them. Separating the interfaces, even if the same object implements both, means external code doesn’t get the entire user kingdom. They can only read things from the repository, not modify it.
Stay in touch
Subscribe to our newsletter
By clicking and subscribing, you agree to our Terms of Service and Privacy Policy
We split to keep things contained. If someone wants to read/write users, they have to explicitly ask for the correct interface. In a language with public/private objects, you could enforce external code using the repository only. Static anlysis tools, a compiler (depending on your language), or your tests will error when they see you using command methods on the read-only repository. Splitting the interface is a nice way to make sure clients of a module behave.