Page 1 of 1
Value Objects, Models, and validation, oh my!
Posted: Mon Jul 09, 2007 11:36 am
by Begby
From reading the stuff here on my latest project I have elected to split up my model stuff into Value Objects and Models as it seems to make sense, here is how I have the responsibilities divided
Model
--------
Insert( valueObject )
Update( valueObject )
Delete( valueObject or ID )
fetchByID( ID )
etc.
ValueObject
---------
setField1( value )
getField1()
setSomeOtherField(value)
getSomeOtherField()
etc.
Does that sound about right?
Is there a standard for handling validation? For instance if I set the values in a value object then want to see if the data is valid (like if a required field is blank or an integer field is set to a string) is that something that is done with the model, in the value object itself, or in the controller? I am leaning towards putting it into the value object. What I want to do is in CRUD set the values, validate the object, then either display errors or do an insert. So I think something like this?
Code: Select all
$ident = new FCP_Auth_Identity() ;
$ident->setUsername( $this->params()->getParam('username') ) ;
$ident->setPassword( $this->params()->getParam('password') ) ;
if (!$ident->isValid())
{
$this->view->ident = $ident ;
$this->view->errors = $ident->getErrors() ;
}
else
{
$model = new FCP_Auth_Identity_Model($this->db()) ;
$model->insert($ident) ;
$this->redirect('some url') ;
}
Posted: Mon Jul 09, 2007 12:13 pm
by TheMoose
My only concern is the multiple Set/Get handlers for the Value object. Why not just create a single handler that takes an extra parameter of the key/name of the value you're setting, ie:
Code: Select all
$valObj->setValue($key, $value);
$valObj->getValue($key);
That way it's more flexible so you don't have to keep adding handlers each time you have a new key/value pair to deal with.
As far as validation, to me it would seem more of a separate validator object that would handle whether a string is a string, or an integer is an integer.
Code: Select all
$validation = new StringValidator($myString);
or
Code: Select all
$validation = new Validator($myString, type_String);
Posted: Mon Jul 09, 2007 12:16 pm
by Begby
TheMoose wrote:My only concern is the multiple Set/Get handlers for the Value object. Why not just create a single handler that takes an extra parameter of the key/name of the value you're setting, ie:
Code: Select all
$valObj->setValue($key, $value);
$valObj->getValue($key);
That way it's more flexible so you don't have to keep adding handlers each time you have a new key/value pair to deal with.
The problem is that we have several developers here, so doing it that way would require everyone knowing the available keys for every object. By manually coding handlers its easy to see what fields are settable and also shows up in autocomplete in our IDEs. So the time spent coding all those handlers is worth it for us.
Posted: Mon Jul 09, 2007 4:06 pm
by kyberfabrikken
A
Value Object is normally used to mean an immutable object, which represents complex primitives, such as money, time and nullobjects. See:
http://nida.se/patterns/poeaa/valueObject.html
The object, you're describing, looks like a
row data gateway. Addtionally, your Model could be more aptly named a
table data gateway, or just a
gateway. Model is a very vague term, since it describes a layer of your application.
Posted: Mon Jul 09, 2007 5:45 pm
by Begby
OK, that makes sense.
I think it might be a bit more along the lines of a data transfer object though as I won't be mapping a row object directly to a single table. For instance the order object will be made up of elements from a lot of different tables from one select statement. For that I think I will have an Order object, then an OrderGateway object.
With the validation, it now appears that it might be best if the validation goes into the gateway, reason being is that some validation will require a query, for instance to see if an order number already exists or if a username already exists.
So maybe like this?
Code: Select all
$gateway = new FCP_Auth_Identity_Gateway() ;
$ident = new FCP_Auth_Identity() ;
$ident->setPassword( $password ) ;
$ident->setUsername( $username ) ;
if ( !$gateway->isValid( $ident ) )
{
$this->view->errors = $ident->getErrors() ;
}
else
{
$gateway->insert( $ident ) ;
}
Posted: Mon Jul 09, 2007 5:47 pm
by Christopher
That's better. I thought maybe you had the names backward. To me it seems like the model is you FCP_Auth_Identity class -- which I would call FCP_Auth_Validator. That contains the data and the logic to make sure the data is valid. I agree with kyberfabrikken that your FCP_Auth_Identity_Model looks like a Table Data Gateway -- I would probably just call it User_Gateway. I would not have the validation there as it is just the data layer.
It is usually a good idea to unlink your authentication code from the actual data source. Inverting the dependency can help do this. So your idea of making your Auth Validator use a generic ValueObject interface makes sense. You can pass it to any Gateway class and the Gateway gets the values and sorts out what to do with them.
Posted: Tue Jul 10, 2007 1:28 am
by kyberfabrikken
Begby wrote:
I think it might be a bit more along the lines of a data transfer object though as I won't be mapping a row object directly to a single table.
While Table Data Gateway and Row Data Gateway suggests by their names, that they operate on a single table, this needn't be the case. The interface could be unchanged, even if you decided to split the data over multiple tables. Actually, that is one of the main benefits of separating data access into two entities. Whether the names are appropriate then, is another question, and I guess that's why most people will just name them Gateway and Record respectively -- to rid of the apparent single-table relationship. I'd say your naming is just fine.
Begby wrote:
With the validation, it now appears that it might be best if the validation goes into the gateway, reason being is that some validation will require a query, for instance to see if an order number already exists or if a username already exists.
I would suggest that you don't make validation en integral part of the data access layer. Put it in a separate entity instead. The reason is, that validation really depends a lot on the context. User privileges, and the state of other entities can be determining. If you want, you can put some basic sanity-checks in the model layer (I would say the gateway is fitting then), which just throws exceptions. This should only be used for basic stuff like out-of-range errors &c, and generally I'd leave this to the database it self, as far as possible.
Posted: Tue Jul 10, 2007 8:14 am
by Begby
The problem though is that I don't want to have my record class do queries and if I create a third entity that handles validation, then that entity will need to handle queries as well since I need to check if a username already exists, or an order number already exists, or whatever. I spose I could pass the gateway object to the validator.
I don't want to do validation, or even filtering, right at the controller level as these objects will be reused in a lot of different controllers. That should be setup into a set of rules in one spot. For instance I would like to have end users manually create orders, have an admin create an order a bit differently in another controller, take orders through a web service, and then take orders through a csv file import etc. All those methods would make use of the same set of error messages.
Another wrench to throw into this thing, some of the data storage will not be in a database, but rather web services. I would like this to be transparent as far as the code is concerned, so the db and web services would implement the same gateway interface.
Some of the web services do validation, some none whatsoever. For instance to create an order I will send a list of items and a ship to address to a web service that will then return a tracking # and other stuff, or it will return errors if the address is not legitimate. This error has nothing to do if the address is formatted correctly (although it does catch that) but rather if the address is a real live address that exists according to post office records. So in that case the validation will need to be handled by the data access layer and by passing in the entire order to the web service.
To handle this issue maybe I could do two stages of validation? One stage just takes a look at the data to make sure required fields are there, do filtering etc. Then the second stage would happen in the data access layer where it checks a second set of rules that might require data access, or checks a response from a web service. The second set of rules would apply to all contexts. The problem with this is that a user attempting to enter data on a form would also see these two sets of errors. The first time they might see that their form is fine except they are missing their last name. Then they resubmit and find out that this time the address is invalid (but properly formatted) as the 2nd layer of validation in the data access layer caught it.
Here is a summary of the options from what I have read
All validation in the data access layer
--------------
Validation is always the same regardless of context
Easy to do validation that requires database access
Validation in the record object
--------------
Requires data access so the data access object would need to get passed to the record object
Validation in another object
--------------
Could use different validators depending on context
Requires data access so the data access object along with the data object would need to get passed
Posted: Tue Jul 10, 2007 9:37 am
by TheMoose
Two stages of validation doesn't seem all that necessary. It can be done in a single stage, although behind the scenes it's checking data multiple times.
Say you have a super validator class to inherit from to make your Address_Validator. Inside this specific validator you can have it check not only format, but legitimacy of address information if the format turns out to be feasible. The same can be done with the username check, except as you said, you'll need to give it the gateway for queries.
But you can also do it in reverse, instead of passing the data object to the validator, you pass the validator to the object instead. This way there is no extra necessity to pass the gateway object to the validator instead since it already has a relationship to the data object already. Then your data objects each will have the isValid() function that is not dependent upon context in the global, but context dependent in each entity.
Code: Select all
class valObj {
function isValid() {
$this->validator->valid();
}
}
$valObj->setValidator(new Address_Validator());
$valObj->isValid();
Posted: Thu Jul 12, 2007 9:47 am
by Begby
TheMoose wrote:But you can also do it in reverse, instead of passing the data object to the validator, you pass the validator to the object instead. This way there is no extra necessity to pass the gateway object to the validator instead since it already has a relationship to the data object already. Then your data objects each will have the isValid() function that is not dependent upon context in the global, but context dependent in each entity.
I am going to try to make the data object have no knowledge of the data object, so the validator will need the data object. I got the Martin Fowler book on enterprise patterns and its pretty good reading. I think the patterns I was ending up with was a domain model and a data mapper since my domain objects are made up from data from a lot of tables, its not a 1 to 1 table to object mapping that I want to have. For instance the orders will be made up of data from like 8 tables including some data from an XML system, but I want to work with the an order object like it is a single entity. Using lazy loading I think I can pull it off so it is efficient as well.
Oooh, I said domain model in a sentence for the first time and know what it means. Do I get my OOP badge now?
With these patterns the domain model should have no direct knowledge of the data mapper. I think I am going to encapsulate validation into another class so that it can be swapped out, then pass that class the mapper through the constructor.
So here is my new code snippet
Code: Select all
$user = new FCP_User() ;
$user->setUsername($this->params()->requireParam('username')) ;
$user->setPassword($this->params()->requireParam('password')) ;
$user->setPasswordMatch($this->params()->requireParam('password_match')) ;
$mapper = new FCP_User_Mapper($this->db()) ;
$validator = new FCP_User_Validator($mapper) ;
$validator->requirePassword(true) ; //require password for inserts, but on updates its optional
if (!$validator->validate($user))
{
$errors = $validator->getErrors() ;
$this->drawAddForm($user, $errors);
}
else
{
$mapper->insert($user) ;
}