Chapter 4. Manipulating objects

This chapter covers

  • Making a distinction between mutable and immutable objects
  • Using modifier methods to change state or create modified copies
  • Comparing objects
  • Protecting against invalid state changes
  • Using events to track changes in mutable objects

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?

4.1. Entities: Identifiable objects that track changes and record events

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.

Listing 4.1. The SalesInvoice entity
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
    {
        // ...
    }
}

  • 1 You can create a sales invoice.
  • 2 You can manipulate its state, such as by adding lines to it.
  • 3 You can finalize it.
  • 4 It exposes some useful information about itself.

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.

Listing 4.2. SalesInvoice gets an identifier at construction time
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.

Listing 4.3. Using an identifier to modify an entity you created earlier
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);

  • 1 First, create SalesInvoice and save it.
  • 2 Later, retrieve it again to make further changes to it.

Given that the state of an entity changes over time, entities are mutable objects. They come with specific rules for their implementation:

  • The methods that change the entity’s state should have a void return type and their names should be in the imperative form (e.g., addLine(), finalize()).
  • These methods have to protect the entity against ending up in an invalid state (e.g., addLine() checks that the invoice hasn’t been finalized already).
  • The entity shouldn’t expose all its internals to test what’s going on inside. Instead, an entity should keep a change log and expose that, so other objects can find out what has changed about it, and why.

The following listing shows how SalesInvoice keeps a change log by recording internal domain events, which can be retrieved from outside by calling recordedEvents().

Listing 4.4. The SalesInvoice entity keeps an internal change log
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()
);

  • 1 In a test scenario …
  • 2 In a service, we can allow event listeners to respond to the internally recorded events.

4.2. Value objects: Replaceable, anonymous, and immutable values

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.

Listing 4.5. Value objects used by the SalesInvoice entity
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.

Listing 4.6. add() returns a new copy of Quantity
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

  • 1 A quantity of 1500 with a precision of 2 represents 15.00.
  • 2 The modified quantity represents 15.00 + 5.00 = 20.00.

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.

4.3. Data transfer objects: Simple objects with fewer design rules

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.

Listing 4.7. DTO classes with public fields
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.

Exercises

  1. What type of object is represented by the following class?
    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);
        }
    }

    1. Entity
    2. Value object
    3. Data transfer object
  2. What type of object is represented by the following class?
    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;
       }
    }

    1. Entity
    2. Value object
    3. Data transfer object
  3. What type of object is represented by the following class?
    final class CreateUser
    {
        public string username;
        public string password;
    }

    1. Entity
    2. Value object
    3. Data transfer object

4.4. Prefer immutable objects

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

  • 1 Pass along the object.
  • 2 Assign the object to a property.
  • 3 Maybe return the object.

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.

Listing 4.8. The Appointment class has mutability issues
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

  • 1 This actually modifies the object stored in the time property.
  • 2 First, get the time of the appointment. This returns ‘12:00’.
  • 3 Then get the time for sending a reminder. This returns ‘11:00’.
  • 4 Finally, get the time of the appointment again. This returns ‘11:00’ now.

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.

4.4.1. Replace values instead of modifying them

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.

Listing 4.9. The Year class
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);

  • 1 This has no effect, since next() doesn’t actually change year.
  • 2 Instead, we should capture the return value of next().

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.

Listing 4.10. Replace values instead of modifying them
final class Journal
{
    private Year currentYear;

    public function closeTheFinancialYear(): void
    {
        // ...

        this.currentYear = this.currentYear.next();
    }
}
How to decide if an object should be immutable

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.

Exercises

  1. The following ColorPalette class represents an immutable object, which should be created once and never modified. Unfortunately, the current implementation won’t result in an immutable object. Can you see what’s wrong with it?
    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;
       }
     }

    1. startWith() internally modifies the ColorPalette instance, making it a mutable object.
    2. colors() returns a mutable collection, making the ColorPalette instance indirectly mutable.
    3. withColorAdded() modifies the original ColorPalette instance.

4.5. A modifier on an immutable object should return a modified copy

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.

Listing 4.11. plus() returns a new copy using the existing constructor
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.

Listing 4.12. withX() uses the clone operator to create a copy
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);

  • 1 The next position will be 4 steps to the left (6, 20).

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

  • 1 Move 4 steps to the left.

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.

Listing 4.13. toTheLeft() is more useful than withX()
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

  • 1 The next position will be (6, 20).
  • 2 The original object should not have been modified.
Exercises

  1. Take a look at the following DiscountPercentage and Money value object classes.
    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
    );

    • 1 20.00 euros
    • 2 10% discount
    • 3 Calculate the discount and subtract the discount from the original price.
    Instead of doing this calculation outside a Money object, write a modifier method called withDiscountApplied() on the Money class that can perform the calculation on itself.

4.6. On a mutable object, modifier methods should be command methods

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.

Listing 4.14. Player is mutable, Position is immutable
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.

4.7. On an immutable object, modifier methods should have declarative names

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().

Listing 4.15. moveLeft(), instead of toTheLeft()
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.

Listing 4.16. toTheLeft() is a more suitable 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.

Exercises

  1. An object has the following method:
    public setPassword(string plainTextPassword): void
    Is this object expected to be mutable or immutable?

    1. Mutable
    2. Immutable
  2. An object has the following method:
    public withPassword(string plainTextPassword): User
    Is this object expected to be mutable or immutable?

    1. Mutable
    2. Immutable
  3. An object has the following method:
    withPassword(string plainTextPassword): void
    Is this object expected to be mutable or immutable?

    1. Mutable
    2. Immutable

4.8. Compare whole objects

With mutable objects, you can write tests like the following.

Listing 4.17. A unit test for moveLeft()
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.

Listing 4.18. A unit test for toTheLeft()
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.

4.9. When comparing immutable objects, assert equality, not sameness

The following example shows how the Position class from the previous example can be used in a (mutable) Player class.

Listing 4.19. The 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.

Listing 4.20. A unit test for moveLeft()
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
 }

  • 1 We can get away with using assertSame() here—the Position object is still the same object we injected.
  • 2 Here we have to use assertEquals().

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.

Listing 4.21. equals() helps with comparing two Position objects
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.

Exercises

  1. How should you compare two value objects in a unit test?

    1. By comparing the return values of their getters.
    2. By using a specialized object comparison function like assertEquals().
    3. By comparing the object reference (using ==).
    4. By calling the object’s equals() method.
  2. How should you compare two value objects in production code?

    1. By comparing the return values of their getters.
    2. By using a specialized object comparison function like assertEquals().
    3. By comparing the object reference (using ==).
    4. By calling the object’s equals() method.

4.10. Calling a modifier method should always result in a valid 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.

Listing 4.22. TotalDistanceTraveled doesn’t accept a negative distance
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.

Listing 4.23. withDenominator() reuses validation logic in the constructor
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);
    }
);

  • 1 Forwarding the call to the constructor will also trigger any of its assertions.
Exercises

  1. Point out what’s wrong with the following implementation of a Range object:
    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;
        }
    }

    1. withMinimum() and withMaximum() create incomplete copies of the Range object.
    2. The rule that “maximum should be greater than minimum” doesn’t get verified in every modifier method.

4.11. A modifier method should verify that the requested state change is valid

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.

Listing 4.24. SalesOrder doesn’t allow certain state changes
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.

Listing 4.25. A unit test for cancelling a delivered sales order
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.

Listing 4.26. If a SalesOrder was already cancelled, we ignore the request
public function cancel()
{
    if (this.status.equals(Status.cancelled())) {
        return;
    }

    // ...
}

4.12. Use internally recorded events to verify changes on mutable objects

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.

Listing 4.27. We could test that the current position is the expected one
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.

Listing 4.28. We could compare the whole Player object to the expected one
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.

Listing 4.29. moveLeft() returns a 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.

Listing 4.30. A broken implementation that would pass the test
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.

Listing 4.31. Upon changing its state, Player records an event
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()
);

  • 1 After moving to the left, we record an event that can later be used to find out what has happened inside the Player object.
  • 2 Create a new Player object and set an initial position for it.
  • 3 Move it 4 steps to the left.
  • 4 Verify that the player has moved by comparing its recorded events to an expected list of events.

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.

Listing 4.32. You may choose not to record an event
public function moveLeft(int steps): void
{
    if (steps == 0) {
        return;                           1
    }

    nextPosition = this.position.toTheLeft(steps);

    this.position = nextPosition;

    this.events[] = new PlayerMoved(nextPosition);
}

  • 1 Don’t throw an exception, but also don’t record an event.

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.

Listing 4.33. Player also records a PlayerTookInitialPosition event
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.

Listing 4.34. The existing test for moveLeft(), which will fail now
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()
    );
}

  • 1 This assertion will fail because the constructor now records a PlayerTookInitialPosition event, which will also be returned by 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.

Listing 4.35. assertContains () can compare recorded events
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.

Listing 4.36. moveLeft() only records an event, but the test would still pass
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.

Isn’t it a bit too pushy to introduce events in every mutable object?

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.

Exercises

  1. In a unit test, what’s the preferred way of finding out if a SalesInvoice object instantiated from the following class has been finalized?
    final class SalesInvoice
    {
        private string isFinalized = false;
    
        // ...
    
        public function finalize()
        {
            this.isFinalized = true;
        }
    }

    1. Add a isFinalized(): bool method to the SalesInvoice class, and call it before and after a call to finalize(), to find out if that method did its job.
    2. Don’t add a getter, but use reflection to take a peek into the private property.
    3. Collect domain events inside the entity, which can later be analyzed to find out if the invoice has indeed been finalized.
    4. Dispatch domain events and set up an event listener in the unit test, which keeps track of whether or not the invoice has been finalized.

4.13. Don’t implement fluent interfaces on mutable objects

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.

Listing 4.37. QueryBuilder offers a fluent interface
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.

Listing 4.38. QueryBuilder method signatures
final class QueryBuilder
{
    public function select(/* ... */): QueryBuilder        1
    {
        // ...
    }

    public function from(/* ... */): QueryBuilder
    {
        // ...
    }

    // ...
}

  • 1 Do these methods update the state of the object they’re called on, or do they return a modified copy? Or . . . both?

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.

Listing 4.39. Reusing intermediate stages of a QueryBuilder instance
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().

Listing 4.40. The implementation of QueryBuilder.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.

Listing 4.41. A where() implementation that supports immutability
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).

Listing 4.42. Modifier methods on an immutable object form a fluent interface
position = Position.startAt(10, 5)
    .toTheLeft(4)
    .toTheRight(2);
Exercises

  1. Take a look at the following sample from a Product entity class. Why is its set-Price() method so confusing?
    final class Product
    {
        // ...
    
        public function setPrice(Money price): Product
        {
            // ...
        }
    }

    1. The client doesn’t know if the return value is the original object or a copy.
    2. Being an entity, Product is supposed to be an immutable object, but set-Price() suggests that you can modify it.
    3. The method looks like a modifier method on an immutable object, but set-Price() is not a declarative, but an imperative name.
“A third-party library has some object design issues. What do I do?”

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.

Summary

  • Always prefer immutable objects, which can’t be modified after they have been created. If you want to allow something to be changed about them, first make a copy and then make the change. Give the methods that do this declarative names, and take the opportunity to implement some useful behavior instead of simply allowing properties to be changed to new values. Make sure that after a modifier method has been called, the object is in a valid state. To do this, accept only correct data, and make sure the object doesn’t make an invalid state transition.
  • On mutable objects like entities, modifier methods should have a void return type. The changes that occur in such objects can be exposed by analyzing internally recorded events. As opposed to immutable objects, mutable objects shouldn’t have a fluent interface.

Answers to the exercises

  1. Correct answer: b.
  2. Correct answer: a.
  3. Correct answer: c.
  4. Correct answer: b. startWith() is a constructor, and it’s perfectly fine for a constructor to modify the instance it’s constructing. withColorAdded() doesn’t modify the original ColorPalette instance, but its copy.
  5. Suggested answer:
     final class Money
     {
         // ...
    
         public function withDiscountApplied(
             DiscountPercentage discountPercentage
         ): Money {
             discount = (int)round(
                 (discountPercentage.percentage() / 100)
                 * this.amountInCents()
             );
    
             return Money.fromInt(
                 this.amountInCents() - discount
             );
         }
     }
  6. Correct answer: a. If it were an immutable object, it would have a modifier method returning a modified instance of the object.
  7. Correct answer: b. If it were a mutable object, it would have a modifier method with a void return type.
  8. Correct answer: a. Admittedly, it’s a confusing method because it mixes a declarative naming style with a command method return type (void).
  9. Correct answer: b. Comparing the results of getters would make the test too tightly coupled to the value object. We also can’t compare references, because value objects aren’t supposed to share them. We should also not rely on any standard or built-in equals() method to compare objects, since we wouldn’t even need to compare value objects in production code—we shouldn’t add this method only for testing purposes.
  10. Correct answer: d. See the answer to exercise 9, but in this case there seems to be an explicit need for comparing value objects in production code. Adding a custom equals() method would be recommended in this case.
  11. Correct answer: b. It may not look like it, but withMinimum() and withMaximum() create complete copies of the Range object. Each method only overwrites the value for one property (minimum or maximum). The real problem is that withMinimum() doesn’t have the assertion that withMaximum() has, leaving room for minimum to be larger than maximum.
  12. Correct answer: c. You shouldn’t add a getter just for testing; you should also not start looking around in the object’s internals. Instead, use domain events to record what’s going on inside the entity, and analyze it afterwards. There’s no need to start dispatching events immediately either.
  13. Correct answers: a and c. An entity isn’t supposed to be an immutable object.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.116.10.201