DDD: how to keep a complex value object immutable?
Asked Answered
A

3

8

I'd like to model an Address as a value object. As it is a good practice to make it immutable, I chose not to provide any setter, that might allow to modify it later.

A common approach is to pass the data to the constructor; however, when the value object is pretty big, that may become quite bloated:

class Address {
    public function __construct(
        Point $location,
        $houseNumber,
        $streetName,
        $postcode,
        $poBox,
        $city,
        $region,
        $country) {
        // ...
    }
}

Another approach whould be to provide the arguments as an array, resulting in a clean constructor, but that might mess up the implementation of the constructor:

class Address {
    public function __construct(array $parts) {
        if (! isset($parts['location']) || ! $location instanceof Point) {
            throw new Exception('The location is required');
        }
        $this->location = $location;
        // ...
        if (isset($parts['poBox'])) {
            $this->poBox = $parts['poBox'];
        }
        // ...
    }
}

That also looks a bit unnatural to me.

Any advice on how to correctly implement a pretty big value object?

Alevin answered 13/9, 2011 at 16:30 Comment(2)
Personally I think if your value object is big enough that this causes problems, it needs to be broken down into multiple value objects. The address example seems fine to my personal sensibilities, but if you find it too large it could perhaps become location + street address + city (where city includes region and country).Gunfire
@Domenic: that's an interesting approach as well!Alevin
M
15

The main issue with large list of parameters is readability and the danger that you will mix up parameters. You can tackle these issues with Builder pattern as described in Effective Java. It makes code more readable (especially languages that don't support named and optional parameters):

public class AddressBuilder {
    private Point _point;
    private String _houseNumber;

    // other parameters

    public AddressBuilder() {
    }

    public AddressBuilder WithPoint(Point point) {
        _point = point;
        return this;
    }

    public AddressBuilder WithHouseNumber(String houseNumber) {
        _houseNumber = houseNumber;
        return this;
    }

    public Address Build() {
        return new Address(_point, _houseNumber, ...);
    }
}

Address address = new AddressBuilder()
    .WithHouseNumber("123")
    .WithPoint(point)
    .Build();

The advantages:

  • parameters are named so it is more readable
  • harder to mix up house number with region
  • can use your own order of parameters
  • optional parameters can be omitted

One disadvantage I can think of is that forgetting to specify one of the arguments (not calling WithHouseNumber for example) will result in a run time error, instead of compile time error when using constructor. You should also consider using more Value Objects like PostalCode for example (as oppose to passing a string).

On a related note, sometimes business requirements call for changing part of the Value Object. For example, when address was originally entered, the street number might have been misspelled and needs to be corrected now. Since you modeled Address as an immutable object there is not setter. One possible solution to this problem is to introduce a 'Side-Effect-Free function' on the Address Value Object. The function would return a copy of the object itself with the exception of a new street name:

public class Address {
    private readonly String _streetName;
    private readonly String _houseNumber;

    ... 

    public Address WithNewStreetName(String newStreetName) {
        // enforce street name rules (not null, format etc)

        return new Address(
            newStreetName
            // copy other members from this instance
            _houseNumber);
    }

    ... 
}
Minute answered 13/9, 2011 at 17:15 Comment(3)
Very interesting answer, thank you. Is that what we call a Factory as well, or is the Factory pattern reserved to Entities?Alevin
DDD Factory can be used to construct complex Value Objects. This Builder can be considered a DDD Factory.Minute
As an improvement to this pattern, you can also avoid a lengthy constructor in Address, but pass the builder to the constructor instead: public Address Build() { return new Address(this); }, and the constructor will pull the data from the builder instead.Alevin
E
1

This is a common problem with Domain Driven Design examples. The Domain Expert is missing and that is the person that would tell you what an Address is and its requirements. I would suspect that the Domain Expert would tell you that an Address does not have a Point. You might be a able to produce a Point from an Address but it wouldn't require a Point. Also a P.O. Box wouldn't be separate value in an Address. You might need a Post Office Box address class (POBoxAddress) I'm stating this because this class looks like it was defined by a developer not Shipping or Billing Domain Expert. By talking to the Domain Expert you can reduce your constructor parameter count.

2nd
You may start to group the parameters as Value Objects. You could create a City value object. That could require the City, Region/State and Country. I would think a City name doesn't mean much unless I know the Region and Country. Saying Paris means nothing but Paris, Illinois, US or Paris, Île-de-France, FR gives you a complete picture. So this would also reduce the count parameter count to the Address object.

If you go down DDD road find a Domain Expert for the Domain you are coding for, you should not be the expert. Sometimes problems should not be fixed by code or a nifty design pattern.

Extrasensory answered 26/4, 2017 at 22:18 Comment(0)
C
-3

immutable is fit for concurrent compute, no Blocking and no Lock, immutable is for high performance and good scalability.

so Value Object can be running better in a concurrent system, include in distribute system, replace old VO with new VO, no need update, so no blocking.

Cup answered 18/10, 2011 at 9:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.