This chapter covers
As you’ve learned in the previous chapters, services should be designed to be immutable. This means that once a service object has been created, it can never be modified. The biggest advantage is that its behavior will be predictable, and it can be reused to perform the same task using different input.
So we know that services should be immutable objects, but what about the other types of objects: entities, value objects, and data transfer objects?
Entities are the application’s core objects. They represent important concepts from the business domain, like a reservation, an order, an invoice, a product, a customer, etc. They model knowledge that developers have gained about that business domain. An entity holds the relevant data, it may offer ways to manipulate that data, and it may expose some useful information based on that data. An example of an entity is the following SalesInvoice class.
final class SalesInvoice { /** * @var Line[] */ private array lines = []; private bool finalized = false; public static function create(/* ... */): SalesInvoice 1 { // ... } public function addLine(/* ... */): void 2 { if (this.finalized) { throw new RuntimeException(/* ... */); } this.lines[] = Line.create(/* ... */); } public function finalize(): void 3 { this.finalized = true; // ... } public function totalNetAmount(): Money 4 { // ... } public function totalAmountIncludingTaxes(): Money { // ... } }
An entity may change over time, but it should always be the same object that undergoes the changes. That’s why an entity needs to be identifiable. When creating it, we give it an identifier.
final class SalesInvoice { private SalesInvoiceId salesInvoiceId; public static function create( SalesInvoiceId salesInvoiceId ): SalesInvoice { object = new SalesInvoice(); object.salesInvoiceId = salesInvoiceId; return object; } }
This identifier can be used by the entity’s repository to save the object. Later on, we can use that same identifier to retrieve it from the repository, after which it can be modified again.
salesInvoiceId = this.salesInvoiceRepository.nextIdentity(); 1 salesInvoice = SalesInvoice.create(salesInvoiceId); this.salesInvoiceRepository.save(salesInvoice); salesInvoice = this.salesInvoiceRepository.getBy(salesInvoiceId); 2 salesInvoice.addLine(/* ... */); this.salesInvoiceRepository.save(salesInvoice);
Given that the state of an entity changes over time, entities are mutable objects. They come with specific rules for their implementation:
The following listing shows how SalesInvoice keeps a change log by recording internal domain events, which can be retrieved from outside by calling recordedEvents().
final class SalesInvoice { /** * @var object[] */ private array events = []; private bool finalized = false; public function finalize(): void { this.finalized = true; this.events[] = new SalesInvoiceFinalized(/* ... */); } /** * @return object[] */ public function recordedEvents(): array { return this.events; } } salesInvoice = SalesInvoice.create(/* ... */); 1 salesInvoice.finalize(); assertEquals( [ new SalesInvoiceFinalized(/* ... */) ], salesInvoice.recordedEvents() ); salesInvoice = this.salesInvoiceRepository.getBy(salesInvoiceId); 2 salesInvoice.finalize(/* ... */); this.salesInvoiceRepository.save(salesInvoice); this.eventDispatcher.dispatchAll( salesInvoice.recordedEvents() );
Value objects are completely different. They are often much smaller, with just one or two properties. They can represent a domain concept too, in which case they represent part of an entity, or an aspect of it. For example, in the SalesInvoice entity, we need value objects for the ID of the sales invoice, the date on which the invoice was created, and the ID and quantity of the product on each line. The following listing shows an outline of the involved value object classes.
final class SalesInvoiceId { // ... } final class Date { // ... } final class Quantity { // ... } final class ProductId { // ... } final class SalesInvoice { public static function create( SalesInvoiceId salesInvoiceId, Date invoiceDate ): SalesInvoice { // ... } public function addLine( ProductId productId, Quantity quantity ): void { this.lines[] = Line.create( productId, quantity ); } }
As you saw in the previous chapter, value objects wrap one or more primitive-type values, and they can be created by providing these values to their constructors:
final class Quantity { public static function fromInt( int quantity, int precision ): Quantity { // ... } } final class ProductId { public static function fromInt(int productId): ProductId { // ... } }
We don’t need value objects to be identifiable. We don’t care about the exact instance we’re working with, since we don’t need to track the changes that happen to a value object. In fact, we shouldn’t change a value object at all. If we want to transform it to some other value, we should just instantiate a new copy, which represents the modified value. As an example, when adding two quantities, instead of changing the internal value of the original Quantity, we return a new Quantity object to represent the sum.
final class Quantity { private int quantity; private int precision; private function __construct( int quantity, int precision ) { this.quantity = quantity; this.precision = precision; } public static function fromInt( int quantity, int precision ): Quantity { return new Quantity(quantity, precision); } public function add(Quantity other): Quantity { Assertion.same(this.precision, other.precision); return new Quantity( this.quantity + other.quantity, this.precision ); } } originalQuantity = Quantity.fromInt(1500, 2); 1 newQuantity = originalQuantity.add(Quantity.fromInt(500, 2)); 2
By returning a new copy instead of manipulating the existing object, we effectively make the Quantity value object immutable. Once created, it won’t change.
Value objects don’t only represent domain concepts. They can occur anywhere in the application. A value object is any immutable object that wraps primitive-type values.
Another type of object that wraps primitive-type values is a data transfer object; we discussed them in the previous chapter. Although some people prefer to implement DTOs as immutable objects, this level of protection often gets in the way of other characteristics you may be after. For instance, you may want to fill the properties one by one, based on data submitted by the user. You also won’t want to maintain or unit test a DTO, as it has no significant behavior (it just holds data), so you won’t want it to have too many methods (such as getters and setters). In the end, you may settle on using public properties. If your programming language has a way to mark them as read-only/write-once (e.g., Java has a final keyword to accomplish this), it would be smart to use it on your DTOs.
The following listing shows an example of the DTO class CreateSalesInvoice, which also keeps instances of the DTO class Line.
final class CreateSalesInvoice { /** * @final */ public string date; /** * @var Line[] * @final */ public array lines = []; } final class Line { /** * @final */ public int productId; /** * @final */ public int quantity; }
We don’t have design rules for data transfer objects that are as strong as the rules for entities and value objects. For the latter, design quality and data integrity are more important than they are for data transfer objects. This is why the design rules in this chapter apply to entities and value objects.
final class UserId { private int userId; private function __construct(int userId) { this.userId = userId; } public static function fromInt(int userId): UserId { return new UserId(userId); } }
final class User { private UserId userId; private Username username; private bool isActive; private function __construct() { } public static function create( UserId userId, Username username ): User { user = new User(); user.userId = userId; user.username = username; return user; } public function deactivate(): void { this.active = false; } }
final class CreateUser { public string username; public string password; }
Since an entity is designed to track changes, it is useful for it to be manipulable after construction. In general, however, you should prefer objects to be immutable. In fact, most objects that are not entities should be implemented as immutable value objects. Let’s take a closer look at why you should prefer an object to be immutable.
By definition, an object can be created and then reused in different places. We can pass an object on as a method argument or a constructor argument, or we can assign an object to a property:
object = new Foo(); this.someMethod(object); 1 this.someProperty = object; 2 return object; 3
If one call site has a reference to an object, and another call site then changes some aspect of the object, it will be quite a surprise for the first call site. How can it know that the object is still useful? Maybe the initial call site doesn’t know how to deal with the new state of the object.
But even within the same call site, problems related to mutability can occur. Take a look at the following listing.
final class Appointment { private DateTime time; public function __construct(DateTime time) { this.time = time; } public function time(): string { return this.time.format('h:s'); } public function reminderTime(): string { oneHourBefore = '-1 hour'; reminderTime = this.time.modify(oneHourBefore); 1 return reminderTime.format('h:s'); } } appointment = new Appointment(new DateTime('12:00')); time = appointment.time(); 2 reminderTime = appointment.reminderTime(); 3 time = appointment.time(); 4
Reading code like this, it may take a lot of time to figure out why, after requesting the time for sending a reminder, the time of the appointment itself has changed. To prevent this kind of situation, the general rule is to design every object that is not an entity to be immutable. It’ll always be safe to keep a reference to an immutable object.
If you design objects to be immutable, they show a nice similarity with primitive-type values. Consider this example:
i = 1; i++;
Would you consider 1 to have been changed into 2? No, we should say that the variable i previously contained 1, and now it contains 2. Integers are in fact immutable themselves. We use them, and then we discard them, but we can always use them again. Also, passing them around as method arguments or copying them into object properties isn’t considered dangerous. Every time we need an integer, we create a new one from the endless supply of integers. There’s no shared place in the computer’s memory where we keep one instance of every integer.
The same goes for objects that are implemented as immutable values. It doesn’t feel like we share them anymore. And if we need the object to be different, we don’t modify the object—we create a new one. This means that if an immutable object is inside a variable or property, and we want to change something about it, we create a new object and store it in our variable or property.
To illustrate, let’s say we implemented Year as an immutable object, wrapping an integer and offering a convenient method for returning a new Year instance representing the next year.
final class Year { private int year; public function __construct(int year) { this.year = year; } public function next(): Year { return new Year(this.year + 1); } } year = new Year(2019); year.next(); 1 assertEquals(new Year(2019), year); year = year.next(); 2 assertEquals(new Year(2020), year);
If we keep a Year instance in a property of a mutable object, and we want it to proceed to the next year, we should not only call next(), but we should also store its return value in the property that holds the current Year instance, as follows.
final class Journal { private Year currentYear; public function closeTheFinancialYear(): void { // ... this.currentYear = this.currentYear.next(); } }
If an object is a service, it’s clear: it should be immutable. If it’s an entity, it’s expected to change, so it should be mutable. All other types of objects should be immutable, for all the reasons mentioned in the previous section.
In practice, depending on the type of application you work on, you may still need to implement some objects as mutable, such as if your application has an interactive GUI, or if you’re a game developer. If a framework forces you to let go of the rules, sometimes you have to (and sometimes you have to let go of the framework). Just make sure that your default choice is to make objects immutable.
final class ColorPalette { private Collection colors; private function __construct() { this.colors = new Collection(); } public static function startWith(sRGB color): ColorPalette { palette = new ColorPalette(); palette.colors.add(color); return palette; } public function withColorAdded(sRGB color): ColorPalette { copy = clone this; copy.colors = clone this.colors; copy.colors.add(color); return copy; } public function colors(): Collection { return this.colors; } }
Based on our findings regarding immutability, immutable objects can have methods that could be considered modifiers, but they don’t modify the state of the object on which we call the method. Instead, such a method returns a copy of the object, but with data that matches the method’s intention. The return type of the method should be the class of that object itself, just like the return type of the next() method from the previous example was Year.
There are two basic templates for these methods. The first uses the (potentially private) constructor of the object, to create the desired copy, like the plus() method in the next listing.
final class Integer { private int integer; public function __construct(int integer) { this.integer = integer; } public function plus(Integer other): Integer { return new Integer(this.integer + other.integer); } }
Since Integer already has a constructor that accepts an int value, we can add the existing integers and pass the resulting int to the constructor of Integer.
The other option, which can sometimes be useful for immutable objects with multiple properties, is to create an actual copy of the object using the clone operator, and then make the desired change to it. The withX() method does this in the following listing.
final class Position { private int x; private int y; public function __construct(int x, int y) { this.x = x; this.y = y; } public function withX(int x): Position { copy = clone this; copy.x = x; return copy; } } position = new Position(10, 20); nextPosition = position.withX(6); 1 assertEquals(new Position(6, 20), nextPosition);
In the previous example, withX() resembles a traditional setter method, which simply allows a client to replace the value of a single property. This forces the client to make the necessary calculations to find out what that new value should be. There are usually better options. Make sure you look for ways to make modifier methods a bit smarter, or at least give them a name that’s domain-oriented rather than technical. You may find useful clues about how to accomplish that by looking at how clients use these methods.
For example, here’s a client of the withX() method:
nextPosition = position.withX(position.x() - 4); 1
Because Position only has a modifier method for setting a new value for x, this client has to make its own calculations to determine which value it has to provide. But the client isn’t really looking for a way to modify x; it’s looking for a way to find out what the next position will be if it takes four steps to the left.
Instead of making the client do the calculations, you can let the Position object do it. You only need to offer a more convenient modifier method, such as toTheLeft() in the following listing.
final class Position { // ... public function toTheLeft(int steps): Position { copy = clone this; copy.x = copy.x - steps; return copy; } } position = new Position(10, 20); nextPosition = position.toTheLeft(4); 1 assertEquals(new Position(6, 20), nextPosition); assertEquals(new Position(10, 20), position); 2
final class DiscountPercentage { private int percentage; public static function fromInt(int percentage) { discount = new DiscountPercentage(); discount.percentage = percentage; return discount; } public function percentage(): int { return this.percentage; } } final class Money { private int amountInCents; public static function fromInt(int amountInCents) { money = new Money(); money.amountInCents = amountInCents; return money; } public function amountInCents(): int { return this.amountInCents; } }This is how you can use Money and DiscountPercentage to calculate a discounted price:
originalPrice = Money.fromInt(2000); 1 discountPercentage = DiscountPercentage.fromInt(10); 2 discount = (int)round( discountPercentage.percentage() / 100) 3 * originalPrice.amountInCents() ); discountedPrice = Money.fromInt( originalPrice.amountInCents() - discount );
Even though almost all of your objects should be immutable, there are usually some objects that are not, namely entities. As we saw at the beginning of this chapter, an entity has methods that allow it to be manipulated.
Let’s look at another example, the Player class, which has a current position, encoded as values for X and Y. It’s a mutable object: it has a moveLeft() method, which updates (replaces, actually) the player’s position. The Position object is immutable, but the Player object itself is mutable.
final class Player { private Position position; public function __construct(Position initialPosition) { this.position = initialPosition; } public function moveLeft(int steps): void { this.position = this.position.toTheLeft(steps); } public function currentPosition(): Position { return this.position; } }
We can recognize mutability by the assignment operator in moveLeft(): the position property gets a new value if you call this method. Another sign is the void return type. These two characteristics are the trademarks of a so-called command method.
Methods that change the state of an object should always be command methods like this. They have a name in the imperative form, they’re allowed to make a change to the object’s internal data structures, and they don’t return anything.
Modifier methods on mutable objects are expected to change the state of the object, which nicely matches the traditional characteristics of a command method. For modifier methods of immutable objects, we need another convention.
Imagine having the same implementation of Position that we saw earlier, but this time toTheLeft() was called moveLeft().
final class Position { // ... public function moveLeft(int steps): Position { // ... } }
Given the rule that modifier methods on mutable objects are command methods, this moveLeft() is confusing: it has an imperative name (moveLeft()), but it doesn’t have a void return type. Unless they look at the implementation, readers will be unsure whether or not calling this method will change the state of the object.
To create a good name for modifier methods on immutable objects, you can fill in the following template: “I want this . . ., but . . .”. In the case of Position, this becomes “I want this position, but n steps to the left,” so toTheLeft() seems to be a suitable method name.
final class Position { // ... public function toTheLeft(int steps): Position { // ... } }
Following this template, you may often end up using the word “with,” or using so-called participle adjectives in the past tense. For instance, “I want this quantity, but multiplied n times.” Or “I want this response, but with a Content-Type: text/html header.” These are declarative names: they don’t tell you what to do, but they “declare” the result of the manipulation.
When looking for good names, also aim for domain-specific, higher-level names instead of generic names from the underlying technical domain. For example, we chose toTheLeft() instead of withXDecreasedBy(), which has a different level of abstraction.
public setPassword(string plainTextPassword): voidIs this object expected to be mutable or immutable?
public withPassword(string plainTextPassword): UserIs this object expected to be mutable or immutable?
withPassword(string plainTextPassword): voidIs this object expected to be mutable or immutable?
With mutable objects, you can write tests like the following.
public function it_can_move_to_the_left(): void { position = new Position(10, 20); position.moveLeft(4); assertSame(6, position.x()); }
As mentioned earlier, this kind of testing usually forces additional getters to be added to the class. These getters are only needed for writing the tests; no other client might be interested in them.
With immutable objects, you can often resort to a different kind of assertion—one that allows the object to keep its internal data and implementation details on the inside, as follows.
public function it_can_move_to_the_left(): void { position = new Position(10, 20); nextPosition = position.toTheLeft(4); assertEquals(new Position(6, 20), nextPosition); }
assertEquals() will use a recursive method that tests for the equality of the properties of both objects, and of the objects it keeps inside those properties, and so on. Using assertEquals() therefore prevents value objects from having some hidden aspect that would make two objects incomparable.
The following example shows how the Position class from the previous example can be used in a (mutable) Player class.
final class Player { private Position position; public function __construct(Position initialPosition) { this.position = initialPosition; } public function moveLeft(int steps): void { this.position = this.position.toTheLeft(steps); } public function currentPosition(): Position { return this.position; } }
A test for moveLeft() might look like the following.
function the_player_starts_at_a_position_and_can_move_left(): void { initialPosition = new Position(10, 20); player = new Player(initialPosition); assertSame(initialPosition, player.currentPosition()); 1 player.moveLeft(4); assertEquals(new Position(6, 20), player.currentPosition()); 2 }
When comparing immutable objects, tests shouldn’t make a point of objects having the same reference in memory. All that matters is the thing they represent. When comparing integers, we don’t compare their memory locations. We just say, “Are their values equal?” So you should always use assertEquals() when comparing objects.
Sometimes you’ll want to compare two objects in production code rather than in a test. In that case, you can’t use assertEquals(). What you should do will depend on your programming language. Some languages, like Java and C#, have a built-in mechanism for object comparison. Objects in those languages will inherit an equals() method from a generic Object class, which you can override to implement your own comparison logic. If you’re using PHP, you should mimic this approach. Add an equals() method to the object that compares the data contained in both objects, as shown in the next listing.
final class Position { // ... public function equals(Position other): bool { return this.x == other.x && this.y == other.y; } }
However, most value objects really don’t need a custom equals() method, and you definitely shouldn’t implement one on every immutable object without thinking about it. The rule for getters applies for the equals() method too: only add this method if some other client than a test uses it. Also, if you can avoid typing other as object, you should do so. In general, clients shouldn’t try to compare a Position object to anything other than a Position object.
When we talked about creating objects earlier, we discussed concepts like meaningful data and domain invariants. The same concepts can be applied to modifier methods, and not just for modifier methods on immutable objects. The rules also apply to mutable objects.
A modifier method has to make sure that the client provides meaningful data, and it has to protect domain invariants. It does so in the same way constructors do: by making assertions about the arguments that have been provided. It can thereby prevent the object from ending up in an invalid state. For an example, look at the following add() method.
final class TotalDistanceTraveled { private int totalDistance = 0; public function add(int distance): TotalDistanceTraveled { Assertion.greaterOrEqualThan( distance, 0, 'You cannot add a negative distance' ); copy = clone this; copy.totalDistance += distance; return copy; } } totalDistanceTravelled = new TotalDistanceTraveled(); expectException( InvalidArgumentException.className, 'distance', function () use (totalDistanceTravelled) { totalDistanceTravelled.add(-10); } );
If the modifier method doesn’t clone, but reuses the original constructor of the class, you can often reuse the validation logic that’s already available. In fact, this can be a good reason not to use clone, but always to go through the constructor.
As an example, consider a Fraction class, which represents a fraction (e.g., 1/3, 2/5). The structure of a fraction is [numerator]/[denominator]. Both can be any whole number, but the denominator can never be 0. The constructor enforces this rule already, so the modifier method withDenominator() only needs to forward the call to the constructor, and the rule will be verified for the input of withDenominator() too.
final class Fraction { private int numerator; private int denominator; public function __construct(int numerator, int denominator) { Assertion.notEq( denominator, 0, 'The denominator of a fraction cannot be 0' ); this.numerator = numerator; this.denominator = denominator; } public function withDenominator(newDenominator): Fraction { return new Fraction(this.numerator, newDenominator); 1 } } fraction = new Fraction(1, 2); expectException( InvalidArgumentException.className, 'denominator', function () use (fraction) { fraction.withDenominator(0); } );
final class Range { private int minimum; private int maximum; private function __construct(int minimum, int maximum) { Assertion.greaterThan(maximum, minimum); this.minimum = minimum; this.maximum = maximum; } public static function fromIntegers( int minimum, int maximum ): Range { return new Range(minimum, maximum); } public function withMinimum(int minimum): Range { copy = clone this; copy.minimum = minimum; return copy; } public function withMaximum(int maximum): Range { Assertion.greaterThan(maximum, this.minimum); copy = clone this; copy.maximum = maximum; return copy; } }
Calling a modifier method on an object often means that the object’s properties will be modified. For mutable objects like entities, such a change in the state of the object can also represent an actual state transition. The transition may unlock new possibilities or block options that were previously available.
As an example, consider the following SalesOrder class. Once it has been marked as “delivered,” it will be impossible to cancel it, since that state transition wouldn’t make sense from a business perspective. The inverse is true for an order that has been cancelled; it shouldn’t be possible to deliver it after all.
final class SalesOrder { // ... public function markAsDelivered(Timestamp deliveredAt): void { /* * You shouldn't be able to deliver the order if it has been * cancelled. */ } public function cancel(Timestamp cancelledAt): void { /* * You shouldn't be able to cancel an order if it has already * been delivered. */ } // and so on... }
Make sure that every one of your methods prevents against making invalid state transitions. You should verify this with unit tests, like the one in the following listing.
public function a_delivered_sales_order_can_not_be_cancelled(): void { deliveredSalesOrder = /* ... */; deliveredSalesOrder.markAsDelivered(/* ... */); expectException( LogicException.className, 'delivered', function () use (deliveredSalesOrder) { deliveredSalesOrder.cancel(); } ); }
An appropriate exception to throw here would be a LogicException, but you can also introduce your own exception type, like CanNotCancelOrder.
If a client calls the same method twice, it requires a bit of contemplation. You could throw an exception, but in most cases it’s not a big deal, and you can just ignore the call.
public function cancel() { if (this.status.equals(Status.cancelled())) { return; } // ... }
We’ve already seen how testing constructors leads to adding more getters to an object than needed, only to test that what comes in can also go out again. This isn’t at all the idea of an object, which is to hide information and implementation details. The same goes for testing modifier methods.
When testing the moveLeft() method of the mutable Player object we discussed earlier, there are a few options. The first option is to use a getter to verify that the current position after moving left is the position we expect it to be.
public function it_can_move_left(): void { player = new Player(new Position(10, 20)); player.moveLeft(4); assertEquals(new Position(6, 20), player.currentPosition()); }
The other, more blunt, option is to verify that the whole object is now what we expect it to be.
public function it_can_move_left(): void { player = new Player(new Position(10, 20)); player.moveLeft(4); assertEquals(new Player(new Position(6, 20)), player); }
This second option isn’t a bad solution, because at least we don’t need the getter to retrieve the current position. The main issue with this test is that it covers too much ground, and we can’t easily add new behavior to the Player object without modifying this test too (in particular, if extra constructor arguments are added over time).
Another option could be to change moveLeft() a bit and make it return the new position.
final class Player { public function moveLeft(): Position { this.position = this.position.toTheLeft(steps); return this.position; } } player = new Player(new Position(10, 20)); currentPosition = player.moveLeft(4); assertEquals(new Position(6, 20), currentPosition);
This looks clever, but it’s a violation of the rule that a modifier method on a mutable object should be a command method, and thus should have a void return type. But on top of that, this test doesn’t really prove that the Player has moved to the expected position. Consider, for example, the following implementation of moveLeft(), for which the test in listing 4.29 would also pass. It returns the correct Position, but it doesn’t modify the position property of Player.
public function moveLeft(): Position { return this.position.toTheLeft(steps); }
A better way to test for changes in a mutable object is to record events inside the object that can later be inspected. These events will act like a log of the changes that happened to the object. Events are simple value objects, and you can create as many of them as needed. In the following listing, the Player class is rewritten to record PlayerMoved events and expose them through its recordedEvents() method.
final class Player { private Position position; private array events = []; public function __construct(Position initialPosition) { this.position = initialPosition; } public function moveLeft(int steps): void { nextPosition = this.position.toTheLeft(steps); this.position = nextPosition; this.events[] = new PlayerMoved(nextPosition); 1 } public function recordedEvents(): array { return this.events; } } player = new Player(new Position(10, 20)); 2 player.moveLeft(4); 3 assertEquals( 4 [ new PlayerMoved(new Position(6, 20)) ], player.recordedEvents() );
You can do interesting things, like only recording events if something has actually changed. For instance, maybe you allow the player to take 0 steps. If that happens, the player hasn’t really moved, and the call to moveLeft() wouldn’t really deserve an event to be created for it.
public function moveLeft(int steps): void { if (steps == 0) { return; 1 } nextPosition = this.position.toTheLeft(steps); this.position = nextPosition; this.events[] = new PlayerMoved(nextPosition); }
After a while, assertEquals([/* ... */], player.recordedEvents()) may prove not to be flexible enough to allow the implementation of the Player object to be changed without making existing tests fail. For example, let’s see what happens if we record another event to represent the moment the player took its initial position.
final class PlayerTookInitialPosition { // ... } final class Player { private events; public function __construct(Position initialPosition) { this.position = initialPosition; this.events[] = new PlayerTookInitialPosition( initialPosition ); } }
This will break the existing test we had for moving to the left.
public function it_can_move_left(): void { player = new Player(new Position(10, 20)); player.moveLeft(4); assertEquals( 1 [ new PlayerMoved(new Position(6, 20)) ], player.recordedEvents() ); }
One thing we could do to make this test less brittle is to assert that the list of recorded events contains the expected event.
public function it_can_move_left(): void { player = new Player(new Position(10, 20)); player.moveLeft(4); assertContains( new PlayerMoved(new Position(6, 20)), player.recordedEvents() ); }
Take a look at the alternative implementation of moveLeft() in the following listing, which records an event, but doesn’t actually update the player’s position as stored in its position property. The test in listing 4.35 would also pass for this alternative, but obviously broken, implementation.
final class Player { //... public function moveLeft(int steps): void { this.events[] = new PlayerMoved(nextPosition); } }
Actually, the implementation shouldn’t be considered “broken” at all. If the test passes, but the production code isn’t correct, there must be something about the object’s behavior that we didn’t fully specify in a test. So, in a sense, the test is broken. To fix this issue, we would have to make sure that some other test forces us to update the Player’s position property. If we can’t think of a good reason for doing so after all, we shouldn’t worry about the position property at all, and simply remove it. The object’s behavior will change in no observable way if we do.
As mentioned at the beginning of this chapter, almost all objects will be immutable. Those few objects that are mutable will be entities. These are objects for which it’s already useful to have events (they are called “domain events” then). So, in practice, adding support for recording events isn’t too much to ask—it’s a very natural thing to happen.
This added support will likely prove useful anyway, because events are a way to respond to changes in domain objects. One type of response could be to make even more changes, or to use event data to populate search engines, build up read models, or collect useful business insights on the fly.
Since the only information the Player exposes to its clients is a list of internal domain events, there isn’t an easy way to find out the current position of the player. In practice, that’s probably not very useful; we need this information, if only to show the current position of the player on the screen. We’ll get back to the topic of retrieving information from objects in chapter 6.
final class SalesInvoice { private string isFinalized = false; // ... public function finalize() { this.isFinalized = true; } }
An object has a fluent interface when its modifier methods return this. If an object has a fluent interface, you can call method after method on it, without repeating the variable name of the object.
queryBuilder = QueryBuilder.create() .select(/* ... */) .from(/* ... */) .where(/* ... */) .orderBy(/* ... */);
However, a fluent interface can be very confusing regarding which object a method gets called on. If QueryBuilder is immutable, then it doesn’t really matter. But who knows if it’s mutable? If you look at the method signatures of QueryBuilder in the following listing, there’s no way to find that out.
final class QueryBuilder { public function select(/* ... */): QueryBuilder 1 { // ... } public function from(/* ... */): QueryBuilder { // ... } // ... }
Given that these method signatures look a lot like modifiers on immutable objects, we might assume that QueryBuilder is immutable. So we may also assume that we can safely reuse any intermediate stage of the QueryBuilder object, like in the next listing.
queryBuilder = QueryBuilder.create(); qb1 = queryBuilder .select(/* ... */) .from(/* ... */) .where(/* ... */) .orderBy(/* ... */); qb2 = queryBuilder .select(/* ... */) .from(/* ... */) .where(/* ... */) .orderBy(/* ... */);
But it turns out that QueryBuilder isn’t immutable after all, as you can see by looking at the following implementation of where().
public function where(string clause, string value): QueryBuilder { this.whereParts[] = clause; this.values[] = value; return this; }
This method looks like a modifier of an immutable object, but it is, in fact, a regular command method. And as a very confusing bonus, it returns the current object instance after modifying it.
To avoid this confusion, don’t give your mutable objects fluent interfaces. QueryBuilder would be better off as an immutable object anyway. This would not leave its clients with an object in an unknown state. The following listing shows an alternative implementation of where() that would make QueryBuilder immutable.
public function where(string clause, string value): QueryBuilder { copy = clone this; copy.whereParts[] = clause; copy.values[] = value; return copy; }
For immutable objects, having a fluent interface is not a problem. In fact, you could say that using modifier methods as they are described in this chapter gives you a fluent interface by definition, because every modifier will return a modified copy of itself. This allows for chained method calls in the same way as a regular fluent interface does (see the following listing).
position = Position.startAt(10, 5) .toTheLeft(4) .toTheRight(2);
final class Product { // ... public function setPrice(Money price): Product { // ... } }
The QueryBuilder example in this section was inspired by the actual QueryBuilder class from the Doctrine DBAL library (http://mng.bz/dx2v). It is just one example of a class that doesn’t follow all the rules in this book. You’re likely to encounter other classes that don’t (in third-party code, and in project code itself). What to do with them?
There are different trade-offs to be made depending on how its used. For example, do you use the “badly” designed class only inside your methods, or do instances of it get passed around between methods or even objects? In the case of Query-Builder, it will likely only be used inside repository methods. This means that it can’t escape and be used in other parts of your application, mitigating the design risk of using it in your project. So even if QueryBuilder has some design issues, there really is no need to rewrite it or work around it.
There may be other cases where an object is very confusing; for example, is it immutable, or mutable? A nice example is PHP’s built-in DateTime class, or Java’s now-deprecated java.util.Date class. Immutable alternatives have been introduced for them, but before those existed, it was a good idea to make copies of these objects before doing anything with them, or to introduce your own immutable wrapper objects. That would ensure that the mutable object never “escaped” and was modified by other clients, which could cause strange state-related issues in your application.
final class Money { // ... public function withDiscountApplied( DiscountPercentage discountPercentage ): Money { discount = (int)round( (discountPercentage.percentage() / 100) * this.amountInCents() ); return Money.fromInt( this.amountInCents() - discount ); } }
18.116.10.201