I'm having my User
entity value objects loosely coupled, and because of that I use a UserFactory
to build the object whenever it comes from the database or when creating a entirely new entity to the domain.
Would it be okay to tightly couple the value objects so I can get rid of its Factory and having my Application Services bloated with individual value object instantiation logic (before being able to inject them) when updating the entity its properties? Aren't value objects tightly related to their root anyways?
For example, when I update one of the properties with the loosely coupled version I would have to instantiate the value object first, and then inject it. But in the tightly coupled example I would be able to just enter the new values directly without having to explicitly go through the process of instantiating the VOs.
Example:
// Updating User's name (loosely coupled version)
$firstName = new FirstName('John');
$lastName = new LastName('Doe');
$fullName = new FullName($firstName, $lastName);
$user->setFullName($fullName);
// Updating User's name (tightly coupled version)
$user->setFullName('John', 'Doe');
Loosely coupled:
class User extends Entity
{
private $fullName;
private $email;
public function getFullName()
{
return $this->fullName;
}
public function setFullName(FullName $fullName)
{
$this->fullName = $fullName;
return $this;
}
public function getEmail()
{
return (string) $this->email;
}
public function setEmail(Email $email)
{
$this->email = $email;
return $this;
}
// etc.
}
Tightly coupled:
class User extends Entity
{
private $fullName;
private $email;
public function getFullName()
{
return $this->fullName;
}
public function setFullName($firstName, $lastName)
{
$firstName = new FirstName($firstName);
$lastName = new LastName($lastName);
$this->fullName = new FullName($firstName, $lastName);
return $this;
}
public function getEmail()
{
return (string) $this->email;
}
public function setEmail($email)
{
$this->email = new Email($email);
return $this;
}
// etc.
}
I think that the example is very simplistic and does not show the true extent of the problem/question. I try to add more scenarios which will better demonstrate the difference between "loosely" & "tightly" coupled solutions.
Using a sophisticated Value Object shows that it is not the "setter" responsibility to build a Value Object, because for setting of date you need locale (or let's imagine other values - just for demonstration sake) and not only the string value of date. So passing around date as Value Object makes more sense and clearer demonstrate the intent.
class User extends Entity
{
private $dateOfBirth;
public function setDateOfBirth(\Zend_Date $date)
{
$this->dateOfBirth= $date;
}
public function setDateOfBirth2($date = null, $part = null, $locale = null)
{
$date = new \Zend_Date($date, $part, $locale);
$this->dateOfBirth = $date;
}
}
As you can see the method User::setDateOfBirth2() does not look right - it has two responsibilities and thus breaks SRP. And in case you needed to set the date using Zend_Date object you would have to add another method. In next example you can see that the setter should accept the Value Object only and for creation of "complex" Value Objects you can either create a helper (factory) method or a Factory - depends how complicated it is:
class User extends Entity
{
private $dateOfBirth;
public function setDateOfBirth(\Zend_Date $date)
{
$this->date = $date;
}
public function createDate($date = null, $part = null, $locale = null)
{
return new \Zend_Date($date, $part, $locale);
}
}
$user = new User;
$user->setDateOfBirth($dateOfBirth);
// or
$user->setDateOfBirth($user->createDate($date, $part, $locale));