开发者

DDD: how to keep a complex value object immutable?

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 th开发者_如何学运维e 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?


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);
    }

    ... 
}


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.


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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜