2

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?

asked Jul 26, 2015 at 15:47
2
  • @Andy, and make User use the BanCommand? Commented Jul 27, 2015 at 5:11
  • I've deleted my comments and added an answer. Commented Jul 27, 2015 at 15:16

3 Answers 3

2

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)
 }
}
answered Jul 26, 2015 at 17:01
9
  • 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. Commented 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. Commented Jul 26, 2015 at 18:02
  • It seems like you shouldn't need to create an instance of user to remove it' Commented 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. Commented 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. Commented Apr 27, 2016 at 13:09
1

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.

answered Jul 27, 2015 at 15:14
5
  • 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. Commented 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. Commented Jul 27, 2015 at 17:55
  • So in the end we still have duplication of domain logic. What did we solve here? Commented 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 there Commented 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. Commented Jul 27, 2015 at 21:06
0

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.

answered Jul 26, 2015 at 16:16

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.