DDD - 访问存储库中实体的状态

发布于 2025-01-04 10:06:44 字数 3250 浏览 1 评论 0原文

我觉得我缺乏良好设计的经验,我可能会让事情变得过于复杂,如果我这样做,请告诉我:)

让我们看一个用户实体和用户存储库的示例。 我将从存储库

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);
  }
}

和用户

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;
  }
}

开始,在我提出问题之前,对代码做一些注释:

  1. 它的 php (对于每个了解 C++\C#\Java 的人来说,困难应该很容易理解)

  2. 为了简单起见,我省略了基类(例如 UserRepository 和 UserEntity 继承的抽象 Repository 和抽象 Entity) 。

  3. 我放弃了工厂对象/类模式,而是更喜欢在实体本身内部使用工厂方法模式。

  4. 此示例中的工厂方法看起来多余,可以替换为

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

但实际上它有点复杂,因为工厂接受“原始”数据(例如来自 POST 表单)并创建所有需要的实体或值对象,然后创建一个有效的用户实体(因此里面的密码)该实体可能不是真正的密码,而是包含密码哈希值的值对象,而不是密码)。

现在的问题是:

我对自己并不完整,因为我向世界公开了 getState() 和 setStatus() 方法。这些方法应该只在存储库内部使用,但由于它们是公共的,所以没有什么可以禁止我在任何地方访问实体的状态和/或修改其状态。这可能是过度反应和事情过于复杂化,但我觉得这是不对的。

我找到的唯一“解决方案”是通过存储库传递所有内容,就像它对

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;
  }
}

我来说看起来“更正确”,因为在这里我不公开实体的内部状态,并且只有存储库知道它。

那么你觉得怎么样?暴露实体的内部状态可以吗?或者也许第二个例子是设计此类系统的更好方法?或者也许您知道更好的方法?

非常感谢!

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:

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

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

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

  4. 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!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

执笏见 2025-01-11 10:06:45

首先:Entity和Repository不要使用单一的基类;它们不相关,而且你这样做就会打破每一个坚实的原则:)

你​​可以通过多种不同的方式来解决这个问题。首先想到的是 DAO,数据访问对象模式。我注意到你在存储库实现中使用了这个术语,但我认为你有点倒退了......通常一个实体会有一个相应的 DAO,它负责持久化该特定实体。

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

或者

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

在这种情况下,UserDAO 和 UserEntity 都可以扩展 DTO 基类,并且您可以将业务逻辑保留在实体中,将持久性代码保留在 DAO 中,从而消除所有重复的字段和(颤抖)属性。实体将承担业务逻辑的单一责任,而 DAO 则承担持久性的单一责任。存储库模式可用于抽象存储基础架构,但在使用 DAO 时通常不值得。

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();
}

现在这看起来可能和 hakre 说的一样,但是 Repository 模式和 DAO 模式之间有很大的区别。

如果我知道有关 PHP 中成员可见性选项的任何信息(例如,除了公共/私有/受保护之外,您还有其他范围吗?内部?)我可能会给您一些关于如何使用存储库模式实现它的建议,但 DAO 似乎应该在这里做...

编辑:我刚刚想到我在方法签名中提供了一些误导性信息; DAO 不应该返回实体,实体应该有一个构造函数接受 DAO,并从中创建一个新的 DAO...

编辑 2:另一种方法是将 UserEntity 视为值对象。不要公开国家的 setter,而是公开 getter。存储库使用它获取数据,并在检索实体时简单地通过构造函数传递所有属性。然而,这对您希望如何使用用户对象有一些影响,这就是我之前没有建议它的原因。

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.

(り薆情海 2025-01-11 10:06:45

为什么要重新发明轮子?对于您想要做的事情,已经有一个名为 Doctrine2 的企业级库 - 具有更多开箱即用的功能,易于使用,并且可以处理您在域中可能遇到的最疯狂的聚合根。

我建议不要使用“自制”解决方案,因为 AR 的制作和维护非常繁琐,需要大量的手动工作,因此需要花费大量时间。

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.

差↓一点笑了 2025-01-11 10:06:45

这些方法只能在存储库内使用

如果您愿意,请使每个实体和存储库本身都具有相同的基类,实体 getter / setter 是受保护成员。完成后,这些内容将受到保护,不再公开。

这将使存储库和实体更紧密地结合在一起,这对于您的情况可能并不坏,因为存储库会产生实体。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文