I am trying to avoid anemic domain models as it's said to be an anti-pattern. But doing so leaves my services to duplicate some of the logic.
class User
{
public $status;
public function ban()
{
$this->status = 'banned';
}
}
Now, a ban service allows to ban users by only having user's id, so there's no need to fetch the whole model.
class BanService()
{
public function __construct(IUserRepo $userRepo)
{
$this->userRepo = $userRepo;
}
public function banUser($userId)
{
$this->userRepo->update($userId, ['status' => 'banned'])
}
}
What is the commonly used pattern to deal with this?
3 Answers 3
I would call one object's method from the other, to avoid code replication.
I would find a way to do this like that :
class BanService()
{
public function __construct(IUserRepo $userRepo)
{
$this->userRepo = $userRepo;
}
public function banUser($userId)
{
$this->userRepo->getUser($userId)->ban()
}
}
or the other way (maybe better, if there is a banService need) :
class User
{
public function ban()
{
// get your banService instance, which could be a singleton and put it in $ban_service
$ban_service->banUser($this->userId)
}
}
-
The first is probably the most "correct" way to do it, however I was trying to avoid having two queries, one for getting object and the other for saving it. Your second way still has duplication of logic.Denis Pshenov– Denis Pshenov2015年07月26日 17:05:39 +00:00Commented Jul 26, 2015 at 17:05
-
I forgot to remove the second line taken from the question. There is no need for it as it is made in banUser(). I updated my answer.BriceP– BriceP2015年07月26日 18:02:51 +00:00Commented Jul 26, 2015 at 18:02
-
It seems like you shouldn't need to create an instance of user to remove it'Andy– Andy2015年07月26日 20:34:34 +00:00Commented Jul 26, 2015 at 20:34
-
@Andy I don't get what you're saying. There is no removal at all. Users are just banned.BriceP– BriceP2015年07月27日 07:18:46 +00:00Commented Jul 27, 2015 at 7:18
-
The first way is the correct one. The class has it's own logic. The logic that belongs to a class must reside there. @Andy: If it was a "removal" then it makes sense to call userRepository->removeUserById( $userId ); because you don't make the user delete himself, you tell the collection to do that, but this is not the case, banning is a clear operation on a user, so it's a user's method.Xavi Montero– Xavi Montero2016年04月27日 13:09:34 +00:00Commented Apr 27, 2016 at 13:09
I'd implement a BanCommand class, which takes care of this. Domain models don't necessarily have to have state; sometimes they represent actions that take data and do something. That's pretty normal OO too; objects are behavior + state, but sometimes the objects don't really need any state to do something useful. That's why I always list behavior before state when I describe OO; the real thing is the behavior you're trying to model. The state is just there to enable the behavior the user wants.
I don't see any issue just using this class directly either, just as you would any other domain model. You could have a Ban method on User which delegates to the command, but this would just be for convenience (say you're already editing a user and you want to have a Ban button). Whatever you end up doing will be driven by your exact use cases.
-
I'm not sure how you will make use of command from User class. The thing is, assuming your command takes user id, the command will modify user's status in repository but it won't modify the one in memory.Denis Pshenov– Denis Pshenov2015年07月27日 17:44:35 +00:00Commented Jul 27, 2015 at 17:44
-
@DenisPshenov If your user has a Banned flag, the Ban method on user could just change it to match the new state after it calls the command.Andy– Andy2015年07月27日 17:55:00 +00:00Commented Jul 27, 2015 at 17:55
-
So in the end we still have duplication of domain logic. What did we solve here?Denis Pshenov– Denis Pshenov2015年07月27日 18:18:08 +00:00Commented Jul 27, 2015 at 18:18
-
@DenisPshenov what logic are you duplicating? Setting a property from false to true is hardly something to be concerned about. All the work of updating the database and any other actions would be handled by the command; for example, if you disable notifications that a user can setup and email them to say they've been banned, if all those are the things that must happen on ban, only the command does those. The flag on user would only matter if you have a use case to go to your edit user screen and ban from thereAndy– Andy2015年07月27日 21:04:58 +00:00Commented Jul 27, 2015 at 21:04
-
And even then, it might be better not to have ban on user, just have the UI use the ban command to do the ban, then reload the user from the repo. It just depends on how you want to do it and your specific requirements.Andy– Andy2015年07月27日 21:06:17 +00:00Commented Jul 27, 2015 at 21:06
The approach I've used a few times (I'm not sure if it's recommended or not) is as follows:
- Code business logic into dedicated business logic classes
- Have your entities use these business logic classes so that they aren't anemic
- Whether they're injected at construction, or simply directly instantiated is up to you.
- Any time I need a certain entity's business logic without an instance of the entity itself I use the corresponding business logic classes.
I've never used this in a large scale application, so I don't know if it has any major limitations. In other words, YMMV.
Explore related questions
See similar questions with these tags.
User
use theBanCommand
?