Good morning,
I've read many time that both setters and getters should be avoided in AR. While I can understand the reason for the setters, I disagree a bit regarding the getters when those are clearly part of the business.
Let's imagine the following User AR:
namespace vendor\Domain\Model;
class User
{
private $userId;
private $username;
private $password;
private $email;
static public register(...): User { ... } // Ubiquitous Language
public function unregister(...) { ... } // Ubiquitous Language
public function changePassword(...) { ... } // Ubiquitous Language
public function changeEmail(...) { ... } // Ubiquitous Language
public function getAggregateId(): UserId { ... } // A getter
public function getUsername(): UserName { ... } // A getter
public function getEmail(): UserEmail { ... } // A getter
...
}
Now let's imagine the following UserRepository interface:
namespace vendor\Domain;
interface UserRepository
{
public function userOfId(UserId $userId):? User;
public function userOfname(UserName $userName):? User;
public function userOfEmail(UserEmail $userEmail):? User;
...
}
Basically here, I've some finders which make it possible to retrieve a User by its ID, its username, or its email address.
Now, let's imagine the following in-memory implementation of that UserRepository:
namespace vendor\Infrastructure\Persistence;
use vendor\Domain\UserRepository;
class InMemoryUserRepository implements UserRepository
{
/** User[] */
private $users;
public function userOfId(UserId $userId):? User
{
if(!isset($this->users[$userId->tostring()]) {
return null;
}
$this->users[$userId->tostring()];
}
public function userOfEmail(UserEmail $userEmail):? User
{
foreach($users as $user) {
if($user->getEmail()->sameValueAs($userEmail) {
return $user;
}
}
return null;
}
...
}
As you can see, in that in-memory implementation, I need filter users by email, accessing their email property. This necessarily means that the User AR needs to provide the getEmail() getter. It is really bad? It is allowed to have getters for properties which must be accessed? Should I simply considere that the getEmail() getter has a business meaning, in which case, it is perfectly valid?
Getter are fine. The case of "don't write getters" is to avoid exposing internal state outside the aggregate: when your services take the aggregate, fetch its state, and make some decision based on that. That's what you want to avoid.
You could follow the guideline of the Law of Demeter, and aim for that. Don't strictly avoid getter, but be mindful about them.
Having a getId
or getUsername
is perfectly fine.
Having a getCollection
-kind of method that returns a mutable collection is not. The aggregate lose control on what it's an internal details, an implementation details. When you expose something like getFriends()
, your services could call it, add or delete things outside the aggregate, and you basically lose the ability refactor the aggregate without breaking everything. an addFriend()
and deleteFriend()
in that case would solve it. And if getFriends()
returns an immutable collection, it's fine as well.
Don't write getters is not an hard rule, try not to expose too much state outside the aggregate, but don't unnecessarily complicate your life by not have any getters at all. Use them if it make sense.