Access to the state of the Entity in Repository

I feel that I lack the experience of good design and I might over complicate things, let me know if I do :)

Lets look at an example of a User entity and User repository. Ill start with the repository

class UserRepository {
  public function save(User $user) {
    if($user->getStatus() == User::STATUS_NEW)
      $this->getDataAccessObject()->insert($user->getState());
    else
      $this->getDataAccessObject()->update($user->getState());
    $user->setStatus(User::STATUS_MANAGED);
  }
}

And the User it self

class UserEntity {
  const STATUS_NEW = 1;
  const STATUS_MANAGED = 2;

  private $_status = self::STATUS_NEW; 
  private $_state = array();

  public static function create($username, $password) {
    return new UserEntity(array('Username' => $username, 'Password' => $password));
  }

  public function __construct(array $state) {
    $this->_state = $state;
  }

  public function getState() {
    return $this->_state;
  }

  public function getStatus() {
    return $this->_status;
  }

  public function setStatus($status) {
    $this->_status = $status;
  }
}

So few notes on the code before I ask the question:

  • Its php (tough should be easy to understand for everyone who knoes C++C#Java)

  • I omitted the base classes (like abstract Repository and abstract Entity from which UserRepository and UserEntity inherits) for the simplicity of the example.

  • I abandoned the factory object/class pattern and instead I prefer to use factory method pattern inside the Entity it self.

  • The factory method looks redundant in this example and can be replaced with

    $user = UserEntity(array('Username' => $username, 'Password' => $password));

  • But in reality its a bit more complex since the factory accepts "raw" data (from POST form for example) and create all the needed Entities or Value objects to then create a valid User entity (so password inside the entity might not be real password but a value object that contains the hash of the password and not the password).

    Now to the question:

    I'm not complete with my self in the fact that I expose the getState() and setStatus() methods to the world. Those method should be used only inside the repository, but since they are public, nothing forbid me of accessing the Entity's state and/or modifying its status in any place. Again this might be over reaction and over complication of things but I feel its not right.

    The only "solution" I find is to pass everything via the repository, something like

    class Entity {
      public static function create($identity, $param1, $param2, Repository $r) {
        $state = $r->getNewState($identity, $param1, $param2);
        return new Entity($state);
      }
    
      private $_state = null;
    
      public function __construct(State $state) {
        $this->_state = $state;
      }
    
      public function getIdentity() {
        $this->_state->getIdentity();
      }
    }
    
    class Repository {
      private $_states = array();
    
      public function getNewState($identity, ...) {
        $state = new State($identity, ...);
        $this->_states[$identity] = $state;
        return $state;
      }
    
      public function save(Entity $entity) {
        $id = $entity->getIdentity();
        //maybe check if such entity is managed by this repository..
        if($this->_states[$id]->getStatus() === State::STATUS_NEW)
          $this->getDataAccessObject()->insert($this->_states[$id]->toArray());
        else
          $this->getDataAccessObject()->update($this->_states[$id]->toArray());
        $this->_states[$id]->setStatus(State::STATUS_MANAGED);
      }
    }
    
    class State {
      const STATUS_NEW = 1;
      const STATUS_MANAGED = 2;
    
      private $_state = array()
      private $_identity = 'something';
    
      public function getIdentity() {
        return $this->_state[$this->_identity];
      }
    
      public function toArray() {
        return $this->_state;
      }
    }
    

    It looks "more correct" to me since here I don't expose the internal state of the Entity and only repository knows about it.

    So what do you think? Is exposing internal state of the Entity is ok? Or maybe the second example is a better way to design such system? Or maybe you know better ways?

    Thank you a lot!


    First of all: do not use a single base class for Entity and Repository; they aren't related, and you'd be breaking every single solid principle by doing it :)

    There are a number of different ways you could go about this. The first that comes to mind is DAO, Data Access Object pattern. I notice you use the term in your repository implementation, but I think you have it a bit backwards... Usually an entity would have a corresponding DAO which is reponsible for persisting that particular entity.

    UserEntity myEntity = new UserEntity("username","password");
    UserDAO myDAO = new UserDAO(myEntity)
    myDao.Create();
    

    or

    UserDAO myDAO=new UserDAO();
    UserEntity myEntity=myDAO.GetByUsername("username");
    

    In this case, both the UserDAO and UserEntity could extend a DTO base class, and you could keep your business logic in the entity and the persistance code in the DAO, thus eliminating all duplication of fields and (shiver) properties. The entity would have the single responsibility of business logic, and the DAO the single responsibility of persistance. The Repository pattern could be used to abstract the storage infrastructure, but when using DAOs it's usually not worth it.

    public class UserDTO
    {
        protected bool banned;
        protected string username;
        protected string password;
    }
    
    public class UserEntity : UserDTO
    {
        public void BlockUser();
        public void ChangePassword();
    }
    
    public class UserDAO : UserDTO
    {
        private int Id;
        public void Create();
        public void Update();
        public UserEntity GetByUsername(string userName);
        public void Delete();
    }
    

    Now this may seem like the same thing hakre said, but there's a big difference between the Repository pattern and the DAO pattern.

    If I knew anything about member visibility options in PHP (like, do you have other scopes than public/private/protected? internal?) I could probably give you some advice on how to implement it using Repository pattern, but DAO seems like it should do it here...

    Edit: It just occured to me that I gave some misleading info in the method signatures; a DAO shouldn't return an entity, an entity should have a constructor taking a DAO, creating a new one from it...

    Edit 2: A different approach would be to treat the UserEntity as a value object. Don't expose a setter for the state, but a public getter. The repository gets the data using that, and simply passes all attributes through the constructor when retrieving an entity. This carries some implications on how you want to work with the user objects however, which is the reason I didn't suggest it earlier.


    Why to reinvent the wheel? For what you are trying to make there is already a enterprise level library called Doctrine2 - has more features out of the box, easy to use and can handle the wildest aggregate roots you might encounter in your domain.

    I would recommend to refrain from 'home built' solutions because AR are very tedious to make and maintain, they cost a lot of time, because require a lot of manual work.


    Those method should be used only inside the repository

    If you want that, make both each entity and the repository itself having the same base class the entity getters / setters are protected members of. This done these are protected and not public any longer.

    This will bring repositories and entities more tightly together, which might not be bad in your case, as the repository gives birth to entities.

    链接地址: http://www.djcxy.com/p/61448.html

    上一篇: 可以将域实体的可变属性作为​​值对象存储吗?

    下一篇: 访问存储库中实体的状态