An object can be instantiated and sometimes modified. An object may also offer methods for performing tasks or retrieving information. This chapter describes how to implement methods that return information. In chapter 7 we’ll look at methods that perform a task.
Earlier, we briefly discussed command methods. These methods have a void return type and can be used to produce a side effect: change state, send an email, store a file, etc. Such a method shouldn’t be used for retrieving information. If you want to retrieve information from an object, you should use a query method. Such a method does have a specific return type, and it’s not allowed to produce any side effects.
Take a look at the Counter class.
final class Counter { private int count = 0; public function increment(): void { this.count++; } public function currentCount(): int { return this.count; } } counter = new Counter(); counter.increment(); assertEquals(1, counter.currentCount());
According to the rules for command and query methods, it’s clear that increment() is a command method, because it changes the state of a Counter object. currentCount() is a query method, because it doesn’t change anything; it just returns the current value of count. The good thing about this separation is that given the current state of a Counter object, calling currentCount() will always return the same answer.
Consider the following alternative implementation of increment().
public function increment(): int { this.count++; return this.count; }
This method makes a change and returns information. This is confusing from a client perspective; the object changes even if you just want to look at it.
It’s better to have safe methods that can be called any time (and, in fact, can be called any number of times), and other methods that are “unsafe” to call. There are two ways to achieve this:
Martin Fowler, “CommandQuerySeparation” (2005), https://martinfowler.com/bliki/CommandQuerySeparation.html.
If Counter were implemented as an immutable object, increment() would become a modifier method, and a better, more declarative name for it would be incremented().
final class Counter { private int count = 0; public function incremented(): Counter { copy = clone this; copy.count++; return copy; } public function currentCount(): int { return this.count; } } assertEquals( 1, (new Counter()).incremented().currentCount() ); assertEquals( 2, (new Counter()).incremented().incremented().currentCount() );
A modifier method doesn’t really return the information you’re after. In fact, it returns a copy of the whole object, and once you have that copy, you can ask it questions. So modifiers don’t seem to be query methods, but they aren’t traditional command methods either. A command method on an immutable object would imply that it changes the object’s state after all, which isn’t the case. It produces a new object, which isn’t far from just answering a query.
Although it’s stretching the concept a bit, the incremented() method in listing 6.3 could answer the query “give me the current count, but incremented by 1.“
When a method returns a piece of information, it should return a predictable thing. No mixed types are allowed. Most languages don’t even support mixed types, but PHP, being a dynamically typed language does. Take, for example, the following isValid() method, which omits a return type, allowing several types of things to be returned. This will be very confusing for its users.
/** * @return string|bool */ public function isValid(string emailAddress) { if (/* ... */) { return 'Invalid email address'; } return true; }
If the provided email address is valid, isValid() will return true; otherwise it will return a string. This makes it hard to use the method. Instead, make sure always to return values of a single type.
There’s another situation to discuss here. Take a look at the following method that doesn’t have multiple return types (its single return type is a Page object), but it may alternatively return null:
public function findOneBy(type): Page? { }
This puts a burden on the caller: they will always have to check whether the returned value is a Page object or if it’s null, and they will need to deal with that.
if (page instanceof Page) { // ... 1 } else { // ... 2 }
Returning null from a method isn’t always a problem. But you have to make sure that the clients of the method will deal with this situation. For PHP, static analysis tools like PHPStan (https://github.com/phpstan/phpstan) and Psalm (https://github.com/vimeo/psalm) can verify this, and your IDE may also help you with it, telling you when you are risking a possible null pointer exception. For Java, there’s the Checker Framework (https://checkerframework.org/), which will provide you with compile-time warnings that clients don’t deal with a possible null value.
In most cases, though, it pays to consider alternatives to returning null. For example, the following getById() method, which is supposed to retrieve a User entity by its ID, shouldn’t return null if it can’t find the user. It should throw an exception. After all, a client expects the User to exist; it’s even providing the user’s ID. It won’t take null for an answer.
public function getById(id): User { user = /* ... */; if (!user instanceof User) { throw UserNotFound.withId(id); } return user; }
Another alternative would be to return an object that can represent the null case. Such an object is called a null object. Clients won’t have to check for null, since the object has the correct type, as defined in the method signature.
public function findOneByType(PageType type): Page { page = /* ... */; 1 if (!page instanceof Page) { return new EmptyPage(); } return page; }
You can let a method name indicate the uncertainty about whether or not the method will return a value of the expected type. In the previous examples, we used getById() instead of findById(), to communicate to the client that the method will “get” the User, instead of trying to find it and possibly returning empty-handed.
One last alternative for returning null is to return a result of the same type, but representing the empty case. If the method is expected to find and return a number of things in an array, return an empty array if you couldn’t find anything.
public function eventListenersForEvent(string eventName): array { if (!isset(this.listeners[eventName])) { return []; } return this.listeners[eventName]; }
Other return types will have different “empty” cases. For instance, if the return type is int, the empty return value might be 0 (or maybe 1), and for strings it might be '' (or maybe 'N/A').
If you find yourself using an existing method that mixes return types, or that returns null when it should return something more reliable, it might be a good idea to write a new method that sets things straight. In the following example, the existing findOneByType() method returns a Page object or null. If we want to make sure that clients don’t have to deal with the null case and will actually get a Page object, we could wrap a call to findOneByType() in a new method called getOneByType.
public function getOneByType(PageType type): Page { page = this.findOneByType(type); if (!page instanceof Page) { throw PageNotFound.withType(type); 1 } return page; }
The simplest implementation for query methods is usually to return a property of the object. These methods are known as getters, and they allow clients to “get” the object’s internal data.
Indeed, the JavaBean conventions prescribe objects to have a zero-argument constructor and to define both a getter and a setter for every property (http://mng.bz/1woy). As you might guess, after reading the previous chapters, every style guide or rule proposed in this book is incompatible with this convention. A zero-argument constructor leads to objects that can be created with an invalid starting state. Allowing every setter to be called separately leads to equally invalid intermediate states. And allowing all of an object’s internal data to get out makes it hard to change anything about that data without breaking its clients. The only type of object that could be designed as a JavaBean is a data transfer object (we discussed those in sections 3.13 and 4.3).
For clients, usually the reason to get data is to use it for further calculations, or to make a decision based on it. Since objects are better off keeping their internals to themselves, you should keep an eye on these simple getters and how their return values are used by clients. The things a client does with the information that an object provides can often be done by the object itself.
A first example is the following getItems() method, which returns the items in a shopping basket just so the client can count them. Instead of directly exposing the items, the basket could provide a method that counts the items for the client.
// Before: final class ShoppingBasket { // ... public function getItems(): array { return this.items; } } count(basket.getItems()); // After final class ShoppingBasket { // ... public function itemCount(): int { return count(this.items); } } basket.itemCount();
The naming of query methods is important too. We didn’t use getItemCount() or countItems() because these method names sound like commands, telling the object to do something. Instead, we named the method itemCount(), which makes it look like the item count is an aspect of a shopping basket you can find out about.
What if your object has a name property? The getter for this property would be called name(), but “name” can also be a verb. In fact, we already encountered the same potential confusion when we used the word “count,” which can be a verb too.
Although the intended meaning of words will always be up for debate, most of the ambiguity can be resolved by setting the right context. Once you establish a clear difference between query and command methods, it’ll be easy to notice when a method is meant to return a piece of information (e.g., return the value of the name property), or is meant to change the state of the object (e.g., change the value of the name property and return nothing). This will provide the reader with an important clue as to whether the word should be interpreted as a verb (which is most often the case with a command method) or as a noun (which is often the sign of a query method).
With small rewrites like this, you can make an object absorb more and more logic, thereby keeping the knowledge about the concept it represents inside it, instead of scattering little bits of this knowledge all over the code base.
Here’s another example: clients call a query method on an object, and then call another method based on the return value of that first call.
final class Product { public function shouldDiscountPercentageBeApplied(): bool 1 { // ... } public function discountPercentage(): Percentage { // ... } public function fixedDiscountAmount(): Money { } } amount = new Money(/* ... */); if (product.shouldDiscountPercentageBeApplied()) { 2 netAmount = product.discountPercentage().applyTo(amount); } else { netAmount = amount.subtract(product.fixedDiscountAmount()); }
One way to keep the information about how a discount should be calculated for a given product is to introduce a method with a name that matches this intention: calculateNetAmount().
final class Product { public function calculateNetAmount(Money amount): Money { if (this.shouldDiscountPercentageBeApplied()) { return this.discountPercentage().applyTo(amount); } return amount.subtract(this.fixedDiscountAmount()); } private function shouldDiscountPercentageBeApplied(): bool 1 { // ... } private function discountPercentage(): Percentage 1 { // ... } private function fixedDiscountAmount(): Money 2 { } } amount = new Money(/* ... */); netAmount = product.calculateNetAmount(amount);
Besides no longer needing to repeat this logic at different call sites, this alternative has two more advantages. First, we can stop exposing internal data like the discount percentage and the fixed discount. Second, when the calculation changes, it can be changed and tested in one place.
In short, always look for ways to prevent the need for query methods that expose the object’s internal data:
These approaches will let the object keep its internals to itself, and force clients to use its explicitly defined public interface (see figure 6.1).
You may have noticed that I don’t use the traditional “get” prefix for getters, as in discountPercentage() (listing 6.11). This convention shows that the method isn’t a command method, but that it simply provides a piece of information. The method name is a description of the piece of information we’re looking for, not an instruction for the object to “go get it” for us.
final class Line { private int quantity; private Money tariff; // ... public function quantity(): int { return this.quantity; } public function tariff(): Money { return this.tariff; } } final class Order { /** * @var Line[] */ private array lines = []; // ... /** * @return Line[] */ public function lines(): array { return this.lines; } } totalAmount = new Money(0); foreach (order.lines() as line) { totalAmount = totalAmount.add( new Money( line.quantity() * line.tariff() ) ); }Rewrite Order and Line in such a way that Order is no longer forced to expose its internal lines array, any of the Line objects that are in this array, or the Line’s tariff and quantity.
When you need a specific bit of information, make sure you have a specific question, and that you know what the answer should look like. As an example, if you’re working on a piece of code and you need today’s exchange rate for USD to EUR, you may discover that there are web services you can call to figure that out, like https://fixer.io/. So you might jump in and write a bit of code that makes the call.
final class CurrencyConverter { public function convert(Money money, Currency to): Money { httpClient = new CurlHttpClient(); response = httpClient.get( 'http://data.fixer.io/api/latest?access_key=...' . '&base=' . money.currency().asString() . '&symbols=' . to.asString() ); decoded = json_decode(response.getBody()); rate = (float)decoded.rates[to.asString()]; return money.convert(to, rate); } }
There are many issues with this tiny bit of code (we won’t deal with the possibility of a network failure, an error response, invalid JSON, a modified response structure, and the fact that a float isn’t the most reliable data type to use when dealing with amounts of money). At a conceptual level, we’re making much too large jumps as well. All we needed at this point in the code is an answer to a question: “What’s the current exchange rate for USD to EUR currency conversion?”
Rewriting this question in code results in two new classes: FixerApi and Exchange-Rate. The first has a single method, exchangeRateFor(), which represents the question that the CurrencyConverter wants to ask. The second class, ExchangeRate, represents the answer.
final class FixerApi { public function exchangeRateFor( 1 Currency from, Currency to ): ExchangeRate { httpClient = new CurlHttpClient(); response = httpClient.get(/* ... */); decoded = json_decode(response.getBody()); rate = (float)decoded.rates[to.asString()]; return ExchangeRate.from(from, to, rate); } } final class ExchangeRate 2 { public static function from( Currency from, Currency to, float rate ): ExchangeRate { // ... } } final class CurrencyConverter 3 { private FixerApi fixerApi; public function __construct(FixerApi fixerApi) { this.fixerApi = fixerApi; } public function convert(Money money, Currency to): Money { exchangeRate = this.fixerApi .exchangeRateFor( money.currency(), to ); return money.convert(exchangeRate); } }
The “answer” class, ExchangeRate, should be designed to be as useful as possible for the client that needs it. Potentially, this class can be reused at other call sites, but it doesn’t have to be.
The important part is that the introduction of the exchangeRateFor() method with a specific return type improves the conversation that’s going on in the code. When reading the code of convert(), we can clearly see that there’s a need for information, a question being asked, and an answer being returned, which is then used to do some more work. Note that so far we’ve only refactored the code; its structure has been improved, but it still has the same behavior.
The question “What’s the current exchange rate?” from the previous section is a question that the application itself can’t answer, based on what it has in memory. It needs to cross a system boundary to find the answer. In this case, it has to connect to a remote service, reachable through the network. Another example of crossing a system boundary would be when an application reaches out to the filesystem to load or store a file. Or when it uses the system clock to find out the current time.
As soon as an application crosses a system boundary, you should introduce an abstraction, allowing you to hide the low-level communication details of the calls that are going on behind the scenes.
Abstraction in this case means two things, and it can only be successful when you have both ingredients:
Introducing a proper abstraction will make it possible to run your code in a test scenario, without making the actual network or filesystem calls. It will also make it possible to swap out implementations without having to modify the client code; you only need to write a new implementation of the service interface.
First we’ll discuss an example that fails to introduce a proper abstraction. Let’s take another look at the FixerApi class. It makes a network call directly, using the CurlHttpClient class.
final class FixerApi { public function exchangeRateFor( Currency from, Currency to ): ExchangeRate { httpClient = new CurlHttpClient(); response = httpClient.get(/* ... */); decoded = json_decode(response.getBody()); rate = (float)decoded.rates[to.asString()]; return ExchangeRate.from(from, to, rate); } }
Instead of instantiating and using this specific class, we could define an interface for it and inject an instance of it into the FixerApi class, as follows.
interface HttpClient 1 { public function get(url): Response; } final class CurlHttpClient implements HttpClient 2 { // ... } final class FixerApi { public function __construct(HttpClient httpClient) 3 { this.httpClient = httpClient; } public function exchangeRateFor( Currency from, Currency to ): ExchangeRate { response = this.httpClient.get(/* ... */); 4 decoded = json_decode(response.getBody()); rate = (float)decoded.rates[to.asString()]; return ExchangeRate.from(from, to, rate); } }
We can now swap out HttpClient implementations because we rely on the interface, not the concrete implementation. This could be useful if you may want to switch to a different HTTP client implementation some day. But we haven’t abstracted the most important part yet. What happens if we want to switch to a different API? It’s not likely that a different API will send the same JSON response. Or maybe we will want to start maintaining our own local database table with exchange rates. In that case, we wouldn’t need an HTTP client anymore.
To remove the low-level implementation details, we need to pick a more abstract name that stands for what we’re doing. We’re looking for a way to retrieve exchange rates. Where would we get them from? From something that can “provide” them. Or from something that manages them, like a “collection.” A good name for this abstraction could be ExchangeRateProvider, or simply ExchangeRates if we look at this service like a collection of known exchange rates. The following listing shows what this would look like.
/** * We extract the "question" method and make it a public method on * an abstract `ExchangeRates` service: */ interface ExchangeRates { public function exchangeRateFor( Currency from, Currency to ): ExchangeRate; } final class FixerApi implements ExchangeRates 1 { private HttpClient httpClient; public function __construct(HttpClient httpClient) { this.httpClient = httpClient; } public function exchangeRateFor( Currency from, Currency to ): ExchangeRate { response = this.httpClient.get(/* ... */); decoded = json_decode(response.getBody()); rate = (float)decoded.data.rate; return ExchangeRate.from(from, to, rate); } } final class CurrencyConverter { private ExchangeRates exchangeRates; public function __construct(ExchangeRates exchangeRates) 2 { this.exchangeRates = exchangeRates; } // ... private function exchangeRateFor( Currency from, Currency to ): ExchangeRate { return this.exchangeRates.exchangeRateFor(from, to); 3 } }
As a final improvement, we should inline any existing calls to the private exchange-RateFor() method because it’s just a proxy to the ExchangeRates service now.
By defining an interface for the existing class, we performed the first step of a successful abstraction. By hiding all the implementation details behind the interface, we also performed the second step, meaning we now have a proper abstraction for retrieving exchange rates. This comes with two advantages:
By the way, if you know about the SOLID principles, you’ve already encountered a similar rule for abstraction of service dependencies, known as the dependency inversion principle. You can read more about it in books and articles by Robert C. Martin.[2]
For instance, Robert C. Martin, “The Dependency Inversion Principle,” http://mng.bz/9woa. Other articles about the SOLID principles can be found on http://mng.bz/j50y.
In the previous examples, it was clear that the question, “What’s the exchange rate?” deserved its own service. It was a question the application itself couldn’t answer. In most situations, though, asking a question shouldn’t immediately cause a new object to be introduced. Consider these alternatives too:
Only if the method becomes too large, needs to be tested separately, or crosses a system boundary should you create a separate class for it. This should keep the number of objects involved limited, and will help keep the code readable; you won’t have to click through lots of classes to find out what’s going on.
The moment you introduce an abstraction for your queries, you create a useful extension point. You can easily change the implementation details of how the answer will be found. Testing this logic will be easier too. Instead of only being able to test the CurrencyConverter service when an internet connection (and the remote service) is available, you can now test the logic by replacing the injected ExchangeRates service with one that already has the answers and will supply them in a predictable manner.
final class ExchangeRatesFake implements ExchangeRates 1 { private array rates = []; public function __construct( Currency from, Currency to, float rate ) { this.rates[from.asString()][to.asString()] = ExchangeRate.from(from, to, rate); } public function exchangeRateFor( Currency from, Currency to ): ExchangeRate { if (!isset(this.rates[from.asString()][to.asString()])) { throw new RuntimeException( 'Could not determine exchange rate from [...] to [...] ); } return this.rates[from.asString()][to.asString()]; } } /** * @test */ public function it_converts_an_amount_using_the_exchange_rate(): void 2 { exchangeRates = new ExchangeRatesFake(); 3 exchangeRates.setExchangeRate( new Currency('USD'), new Currency('EUR'), 0.8 ); currencyConverter = new CurrencyConverter(exchangeRates); 4 converted = currencyConverter .convert(new Money(1000, new Currency('USD'))); assertEquals(new Money(800, new Currency('EUR')), converted); }
By setting up the test like this, we focus only on the logic of the convert() method, instead of all the logic involved in making the network connection, parsing the JSON response, etc. This makes the test deterministic and therefore stable.
In listing 6.17, the test method has a name in so-called snake case: lower case with underscores as word separators. If we followed the standard for naming methods, it would have been itConvertsAnAmountUsingTheExchangeRate(). Most standards would also suggest using relatively short names, but it_converts_an_amount_using_the_exchange_rate() is anything but short. Because the purpose of test methods is different from that of regular methods, the solution is not to submit test method names to the same standard, but to set a different standard for them:
If you’re not used to these rules, a good way to ease into them is to start a test method name with it_. This should get you in the right mood for describing a particular object behavior. Although it’s a good starting point, you’ll notice that not every test method makes sense starting with it_. For instance, when_ or if_ could work too.
A fake is one kind of test double, which can be characterized as showing “somewhat complicated” behavior, just like the real implementation that will be used in production. When testing, you could also use a stub to replace a real service. A stub is a test double that just returns hardcoded values. So whenever we’d call the exchangeRateFor() method, it would return the same value, as follows.
final class ExchangeRatesStub 1 { public function exchangeRateFor( Currency from, Currency to ): ExchangeRate { return ExchangeRate.from(from, to, 1.2); 2 } }
An important characteristic of stubs and fakes is that in a test scenario, you can’t and shouldn’t make any assertions about the number of calls made to them, or the order in which those calls are made. Given the nature of query methods, they should be without side effects, so it should be possible to call them any number of times, even zero times. Making assertions about calls made to query methods leads to tests that don’t keep sufficient distance from the implementation of the classes they’re testing.
The opposite is the case for command methods, where you do want to verify that calls have been made, how many have been made, and potentially in what order. We’ll get back to this in the next chapter.
Mocking frameworks are often used to build test doubles on the fly. I recommend against using these frameworks for creating fakes and stubs. They may save you a few lines of boilerplate, but at the cost of code that is hard to read and maintain.
Even if you still prefer to use these mocking tools, I recommend using them only for creating dummies (that is, test doubles that don’t return anything meaningful and are only there to be passed as unused arguments). For stubs and fakes, mocking tools usually get in the way of good design. They will verify if and how many times a query method has been called, but they often make refactoring harder, because method names often have to be provided as strings, and your refactoring tool may not recognize them as actual method names.
We shouldn’t forget to also test the real implementation that uses an HTTP connection to retrieve exchange rates. We have to test that it works correctly. But at that point we’re no longer worried about testing the conversion logic itself, but only that the implementation knows how to communicate well with the external service.
/** * @test */ public function it_retrieves_the_current_exchange_rate(): void { exchangeRates = new FixerApi(new CurlHttpClient()); exchangeRate = exchangeRates.exchangeRateFor( new Currency('USD'), new Currency('EUR') ); // Verify the result here... }
You will find that this kind of test still needs some effort to make it more stable. You may have to set up your own exchange rate server that replicates the real one. Or you may be able to use a sandbox environment provided by the maintainers of the real service.
Note that this test doesn’t count as a unit test anymore: it doesn’t test the behavior of an object in memory. You could call this an integration test instead, since it tests the integration of an object with the thing in the world outside that it relies on.
As we discussed, command methods can have side effects. Command methods change something, save something, send an email, etc. Query methods, on the other hand, won’t do anything like that. They will just return a piece of information. Usually, a query method needs some collaborating objects to build up the requested answer. If we get the division between command and query methods right in our code, a chain of calls that starts with a query won’t contain a call to a command method. This has to be true, because queries are supposed to have no side effects, and calling a command method somewhere in the chain will violate that rule.
There are some exceptions to this rule. Consider a controller method for a web application, which can be called to register a new user. This method will have a side effect: somewhere down the chain of commands it will store a new user record in the database. This would normally force us to use a void return type for the controller itself, but a web application should always return an HTTP response. So the controller will have to return at least something.
final class RegisterUserController { private RegisterUser registerUser; public function __construct( RegisterUser registerUser ) { this.registerUser = registerUser; } public function execute(Request request): Response { newUser = this.registerUser .register(request.get('username')); return new Response(200, json_encode(newUser)); } }
Technically speaking, the controller violates the command/query separation principle, but there’s no way around that. At the very least, we should return an empty 200 OK response or something like that. But that won’t be very useful for the frontend, which makes the “register user” POST request, and would like to be given a response with a JSON structure representing the newly created user.
To solve this case, you should divide the controller’s action into two parts: registering the new user and returning it. Preferably you’d also determine the new ID of the user before calling the RegisterUser service, so the service doesn’t have to return anything at all and can be a true command method. This is demonstrated in the following listing.
final class RegisterUserController { private UserRepository userRepository; private RegisterUser registerUser; private UserReadModelRepository userReadModelRepository; public function __construct( UserRepository userRepository, RegisterUser registerUser, UserReadModelRepository userReadModelRepository ) { this.userRepository = userRepository; this.registerUser = registerUser; this.userReadModelRepository = userReadModelRepository; } public function execute(Request request): Response { userId = this.userRepository.nextIdentifier(); this.registerUser .register(userId, request.get('username')); 1 newUser = this.userReadModelRepository.getById(userId); 2 return new Response(200, json_encode(newUser)); } }
In almost all cases I find that it’s best to follow the command/query separation principle, but it should not become a rule that you can’t deviate from. In fact, no programming rule should ever be like that.
A common situation where CQS stops being a good rule is in the realm of concurrency. An example would be the following nextIdentity() method. It generates a unique ID for an entity that you’re going to save. The ID is the next available number in the sequence 1, 2, 3, etc.
final class EntityRepository { public function nextIdentity(): int { // ... } }
Two clients that call this method shouldn’t receive the same ID, since that might result in them overwriting each other’s entity data. Calling nextIdentity() should return an integer and at the same time mark the returned integer as “used.” However, that would make the method violate CQS: it returns information and performs a task, thereby influencing the state of the system in an observable way. Calling the method again will give you a different answer.
You could figure out a way to still follow CQS, but I think that would complicate your code a lot.[a] In a case like this, feel free to let go of CQS and just implement the method in a way that makes sense.
To find out more about this particular situation and possible solutions, take a look at Mark Seemann’s article “CQS versus server generated IDs” (2014), http://mng.bz/Q0nQ.
final class Line { // ... 1 public function amount(): Money { return new Money( line.quantity() * line.tariff() ); } } final class Order { // ... 2 public function totalAmount(): Money { totalAmount = new Money(0); foreach (this.lines() as line) { totalAmount = totalAmount.add( line.amount() ); } return totalAmount; } }
18.119.162.204