可以存储域实体吗';的可变属性作为值对象


Is it okay to store a domain entity's mutable properties as a value object?

我希望能够更改和传递UserEntity的某些部分,并且某些部分应该保持不变。

例如,我从不想更改UserEntity的id,但电子邮件或密码之类的东西可能会经常更改,并且也可以被UserEntity之外的其他对象使用。

其中一个实例是在创建UserEntity时。由于没有id,UserEntity就不可能存在,因此我的控制器可以创建一个UserData对象来标准化UserEntity属性。映射器在数据库中创建实体后,将创建一个新的UserEntity,并在构造函数中传递id和UserData对象。

当UserEntity需要电子邮件或密码等信息时,它只需查看其UserData即可。

看起来更便携,但这是不是太夸张了?有更好的解决方案吗?

注意

  • 我认为这可能是好的原因:可变字段的值需要标准化。。。有时这些字段需要在实体本身之外传递。例如,在创建实体之前。通过创建一个可以传递的值对象,我们提供了一个标准化的点来从任何地方分配这些值,以及可以在实体外部传递的东西。

  • 所谓"标准化",我的意思是我的信息需要统一,无论它在哪里。例如,email需要始终是n长度,在有效格式中,name始终需要是n长度,等等。我的目标是能够在一个位置设置这些"规则"。。。由于UserEntity的这些属性(可变属性)存在于实体本身之外,有时,它们可能会在自己的值对象中独立存在。

我认为没有"一种真正的方法"可以做到这一点(无论你读到什么)。。。如果它在你的模型中有意义,那么它对我来说听起来很好。我不确定你说"这些字段中的许多字段需要标准化"是什么意思,以及为什么不能作为UserEntity的一部分来实现,但不管怎样。也就是说,在没有一个完全独立的对象类的情况下,你很有可能完全完成你想要做的事情。

评论/批评:

你的建议并不符合严格的"对象"模型,即UserData只是由真正属于UserEntity属性的东西组成,与这些属性没有其他潜在关系。

我真的不知道为什么你需要一个单独的对象在实体之外传递。。。如果您需要数据,为什么不能绕过UserEntity并从那里访问它呢?在将数据传递给UserEntity构造函数之前,您需要对数据做些什么?在stdClass的实例中收集数据,然后在UserEntity中处理数据,这是不容易实现的?


如果是我,我会做一些更像以下的事情(比如,创建一个新用户):

<?
// assume an appropriately defined UserEntity class...
// I'm using stdClass just to keep the parameters together to pass all at once
// I'm assuming some basic user data passed from the browser
$user_data = (object) array(
    'email' => $_REQUEST['email'],
    'name' => $_REQUEST['name'],
    'password' => $_REQUEST['password'],
    'confirm_password' => $_REQUEST['confirm_password']
);
/*
validateData is static so it can be called before you create the new user
It takes the $user_data object to validate and, if necessary, modify fields.
It also takes a $create flag which indicates whether the data should be
checked to make sure all of the necessary fields are there to create the user
with.  This allows you to call it on update with the $create flag unset and it
will pass validation even if it's missing otherwise required fields.
It returns $result, which indicates pass or failure, and the potentially modified
$user_data object
*/
$create = TRUE;
list($result, $user_data) = UserEntity::validateData($user_data, $create);
// equivalence allows you to pass back descriptive error messages
if ($result === TRUE) {
    // create the user in the database, get back $user_id...
    $user = new UserEntity($user_id, $user_data);
}
else {
    // return error to user
}
// access user data either individually, or if you want just make a getter
// for the entire group of data, so you can use it just like you would a
// separate UserData object
send_double_opt_in($user->getUserData());
?>

编辑以解决提供的更多信息:

你说这些属性存在于UserEntity之外,它们可能独立存在。。。你的意思是说这些属性可以在不用于UserEntity对象的情况下被收集、使用和丢弃吗?如果是这种情况,那么一个单独的对象将完全适合该数据。如果不是,如果数据总是从属于现有或未来的UserEntity,那么从……我们称之为"全局数据"的角度来看,这些属性永远不会"独立存在"。当您将整个系统视为一个整体,而不仅仅是每时每刻的代码时,数据很可能"属于"UserEntity类。

至于静态方法,我看不出有什么特别的理由(显然)要避免它们,但每个方法都有自己的理由。许多其他体系结构会稍微复杂一点,但这里有一些选择:

  1. 验证构造函数中的数据。问题是,如果它没有验证,您将不得不删除数据库条目。丑陋
  2. 将数据库交互与数据验证一起移动到构造函数中。这可能会违反您的首选对象模型,并且您只需要在创建对象后检查对象的状态(即设置公共属性$this->status = 'error';或类似的属性来告诉您发生了一些您必须处理的糟糕事情)
  3. 创建一个独立的函数来验证数据。丑陋,因为这是一个专门与UserEntity和/或其数据相关的函数
  4. 或者,只需像您建议的那样创建一个单独的UserData对象并完成它。就像步骤#2一样,您必须具有某种$status属性来指示验证失败

对于一组可以独立于任何域对象使用的验证器来说,这似乎是一个很好的例子——也许这是我的函数编程方面,但我认为创建一堆像isValidEmail()这样的函数没有问题。

我知道你对一堆静态函数感到不舒服;你会考虑一个带有一堆方法的静态Validator类来保持整洁吗?

无论哪种方式,您都可以在UserEntity对象之外使用此验证(我想您谈论的是其他域对象、控制器中的输入验证等情况?)。但您也可以在"UserEntity"对象中使用它——就我自己而言,我还没有确定在设置属性时,还是在将其保存到持久存储时,我更喜欢消毒和验证。我目前更倾向于第一种,通过setter函数。