Named Constructors for Domain Objects

Constructing objects is something we do all the time. But are we doing it correctly? Is there maybe a better way to create multiple valid constructors for a given domain object? And with this making sure it’s easy to reason about? Should we take a look at this thing called Named Constructors?

In some situations, there is a need for multiple constructors. But the problem is that PHP does not allow us to have multiple constructors. We can’t overload constructors.

But to be honest, even that won’t help us. It will still be called _construct and this might not even be a good solution. It definitely would not help us in terms of the so-called ubiquitous language. This tells us that our objects should be easy to reason about between business and developers. Overloading constructors might make it even more confusing. I’m actually glad we don’t have it. This allows us to come up with better solutions.

3 Ways to construct a domain object

Before we talk about Named Constructors. Let’s take some step back, and go over the different solutions we can use to create domain objects.

For me, taking a step back always helps me to solve problems, and to figure out the best solution for a given situation. We as developers need options.

As far as I could find, there are 3 different solutions to construct a domain object. And you will probably be familiar with at least 2 of them.

Anemic Constructor

The Anemic Constructor is how I created my Symfony Entities when I first started working with Symfony.

I always tell new Symfony developers, don’t follow documentations blindly. They are not made to explain to you how to create complex business application’s. They are made to learn Symfony to developers of any knowledge.  Always remember; make the framework do your bidding, do not follow the framework blindly!

For me, this solution is similar to the dreaded anti-pattern called Anemic Domain Objects. Why would you use this solution in a rich domain? This smells like a domain without any real logic in it.

$purchasedOrder = new PurchaseOrder();
$purchasedOrder->setName($name);
$purchasedOrder->setCustomer($customer);
$purchasedOrder->setDate($date);
$purchasedOrder->setSalesPerson($salesPerson);

You might argue; well this is easy to reason about right? Well, you are newing an empty purchase order and then set the name, the customer, the date, the salesperson, … Ooh wait, the salesperson is null in this case because…

The problem lies in the fact that you can’t enforce the required values. You can create a PurchaseOrder object without defining any property. You then have an object in an inconsistent state. As a business, you don’t want purchase orders without any name, customer or date. You assume those required data will be set later with setters.  And then maybe validate it in some controller.

You also don’t know about all the different combinations that are valid. Can we create purchase orders with or without salespersons? Can we create purchase orders without a customer? Well yes… But according to the business we cant…

In my opinion, this should not exist in any professional build application with a life expectancy of more than 1 year. So let’s move on.

The Constructor

This is a step up from the first solution.  We just use the default PHP constructor. This is a natural evolution from the Anemic Constructor when you realise it doesn’t work. Meaning; when you are moving to a more rich domain.

Well, we figure out which properties are required, and then use those as our constructor’s properties. Works well when you only have 1 way to construct an object. So this might be a good option in some situations.

but if you have multiple ways, then for some properties you are forced to use setters. The constructor then will only consist of the properties that are required in all cases but are might be incorrect in all.

$purchasedOrder = new PurchaseOrder($name, $customer, $date);
$purchasedOrder->setSalesPerson($salesPerson);

You also still have the problem that when you are talking in the ubiquitous language, no one talks about newing a purchase order.

Named Constructors

And here then comes the static factory pattern to the rescue. Or when used on Rich objects; named constructors.

Naming your constructors makes your objects easier to reason about. Instead of newing a purchase order, you now create a purchase order.

$purchasedOrder = PurchaseOrder::create($name, $customer, $date);

And what if we want to create a purchase order for a salesperson?

$purchasedOrder = PurchaseOrder::createForSalesperson($name, $customer, $salesperson, $date);

I don’t know but. Don’t you think this is easier to reason about? You can talk to the business and say; well, in situation x we create a purchase order, but when a salesperson is involved we create one for a salesperson. In that case, we will send some email to inform you about it.

Thus this also allows us to record different events for different constructors right inside the domain object.

How do we actually create named constructors?

So now we went over the 3 solutions to construct your domain objects. Let’s go a bit deeper with the named constructor and let us take a look how we should use named constructors in practice in an already well-constructed domain object.

We create a private constructor with the properties that should be required in any case. The reason for this being private is simple. We don’t need it, it will become an implementation detail. You should implement meaningful interfaces to construct our domain object. For this, we want to use named constructors. We don’t want anyone just to new this object.

private function __construct(PurchaseOrderId $purchaseOrderId, CustomerId $customerId, string $name)
{
    $this->id = $purchaseOrderId->toString();
    $this->customerId = $customerId->toString();

    Assertion::minLength($name, 5);
    $this->name = $name;
}

As you can see I’m using value objects for my ID’s and these are all UUID’s instead of a simple ID. It’s not a requirement to do this while using named constructors. You can auto increment your id’s and provide the complete customer entity instead of just it’s ID.

First, we create our default implementation. We want to create a purchase order. And also record the event that a new purchase order was created.

public static function create(PurchaseOrderId $purchaseOrderId, CustomerId $customerId, string $name): PurchaseOrder
{
    $self = new self($purchaseOrderId, $customerId, $name);

    $self->record(new PurchaseOrderWasCreated($purchaseOrderId, $customerId, $name));

    return $self;
}

But now the business tells you that when a purchase order will be created by a salesperson, that it’s also required that the comment field of the purchase order is filled in with at least a 25 characters description. And we also need to send some messages to a reporting server that will keep track of that salesperson sales. The default create case still stands for the other situation that exists in the domain.

Instead of doing some hacky code, we create a new static constructor for this business case and record an event that can be subscribed on.

public static function createForSalesperson(PurchaseOrderId $purchaseOrderId, CustomerId $customerId, SalespersonId $salespersonId, string $name, string $comment): PurchaseOrder
{
    $self = new self($purchaseOrderId, $customerId, $name);
    $self->userId = $salespersonId;

    Assertion::minLength($comment, 25);
    $self->comments = $comment;

    $self->record(new PurchaseOrderWasCreatedForSalesperson($purchaseOrderId, $customerId, $salespersonId, $name, $comment));
        
    return $self;
}

The use of named constructors like this not only opens up possibilities to make our domain objects easier to understand and more in line with the business. But also makes it easier to reason about between developers.

Conclusion

So we went over 3 different solutions to construct domain objects. I hope that if you aren’t still creating them the anemic way. Try to use named constructors when appropriate.

In some cases named constructors will make it a lot easier for you and your fellow developers to reason about. It might take some extra time to create these objects because some more upfront thinking is required. But sometimes it seems to be faster and easier to just create domain objects without thinking too much about it. But when codebases grow, new features get added. Your domain might become messy. And going through the mess, no easy way to tell how an object can be constructed. This will cost you time. You might take into account situations that never happen, or will be blind to some other ones.

Remember, think before you code! And is the current way of constructing my domain objects the best solution?