Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

There are certain parts of my UserEntity that I would like to be able to change and pass around, and there are certain parts that should remain constant.

For example, I NEVER want to change my UserEntity's id, but things like email or password may change often, and can be used by other objects outside of the UserEntity as well.

One instance of this would be when creating a UserEntity. Since a UserEntity cannot exist without an id, my controller could create a UserData object that would standardize the UserEntity properties. After the mapper creates an entity in the db, it would create a new UserEntity and pass in the id and UserData object in the constructor.

When UserEntity needs info like email or password, it can just look at its UserData.

Seems more portable, but is this overkill? Is there a better solution?

Note

  • The reason why I think this might be good: the values of the mutable fields need to be standardized...and sometimes these fields need to be passed around outside of the entity itself. For example, before the entity has been created. By created a value object that can be passed around we provide a standardized spot to assign these values from anywhere, as well as something that can be passed around outside of the entity.

  • By "standardize" I mean that my information needs to be uniform, wherever it exists. For example, email needs to always be n length and in a valid format, name always needs to be n length, etc. My goal here is that I'd like to be able to set those "rules" in a single spot...and since these properties of the UserEntity (the mutable ones) exist outside of the entity itself, sometimes, they could potentially live on their own, in their own value object.

like image 584
johnnietheblack Avatar asked Mar 17 '12 06:03

johnnietheblack


1 Answers

I don't think there is "one true way" to do it (no matter what you read about it)... if it makes sense in your model, then it sounds good to me. I'm not sure exactly what you mean when you say "many of those fields need to be standardized" and why that couldn't be done as part of the UserEntity, but whatever. That said, odds are you can accomplish exactly what you're trying to do without an entirely separate object class.

Comments/Critiques:

What you are suggesting doesn't really go along with a strict "object" model, i.e. the UserData is just made up of things that are really attributes of UserEntity, and there's no other underlying relationship to those attributes.

I'm not really sure why you would need a separate object to pass around outside the entity... if you need the data, why can't you just pass around the UserEntity and access it from there? What do you need to do with the data before passing it to the UserEntity constructor that couldn't just as easily be accomplished with gathering the data together in an instance of stdClass and then processing it in UserEntity?


If it were me, I'd do something more like the following (for, say, creating a new user):

<?
// 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());
?>

Edit to address more information provided:

You say these properties exist outside of the UserEntity, and they could potentially live on their own... Do you mean that these properties could be gathered, used, and discarded without even being intended for a UserEntity object? If that's the case, then a separate object would be entirely appropriate for that data. If not, if the data is always subordinate to either an existing or future UserEntity, then those properties will never "live on their own" from a... let's call it a "global data" point of view. When you consider the entire system as a whole, not just the code from moment to moment, it's likely that the data "belongs to" the UserEntity class.

As for static methods, I see no particular reason to avoid them (obviously), but to each his own. Many other architectures would be a little more complex, but here are some options:

  1. Validate the data in the constructor. The problem is that if it doesn't validate, you'll have to remove the database entry. Ugly.
  2. Move database interaction into the constructor, along with data validation. This could violate your preferred object model, and you'll just have to check the status of the object once it's created (i.e. set a public property $this->status = 'error'; or something like that to tell you that something bad happened that you'll have to handle).
  3. Create a standalone function to validate the data. Ugly, because this is a function that's specifically related to UserEntity and/or its data.
  4. Or, just create a separate UserData object like you've suggested and be done with it. Much like step # 2, you'll have to have some sort of $status property to indicate a failed validation.
like image 98
Jason Avatar answered Sep 27 '22 17:09

Jason