- Home
- LLBLGen Pro
- Architecture
Cross Entity Validation/Best Practice
Joined: 16-Jan-2009
v2.6, Adapter2008, Manager classes (self generated)
I'm wondering where is the best place to put "cross entity" validation. I am using the LLBL entity level validation and dependency injection to relate the validation entities with the business entities. Basic entity validation works fine there but there are times when the validation has to reach out to the database to validation other data.
For example: Odometer readings. We have a VehicleEntity which has multiple OdometerReading entities. When a reading is created there is basic validation like the reading value must be > 0, that makes sense in the OdometerReadingValidator. But I also have to check that this reading is greater than the last reading. My thoughts are as follows.
-
Put that validation in the reading validator class. Have the reading validator use the reading manager to get the prior reading and then validate that the values are ok. This ensures that this validation is always called prior to save but I'm not sure if I'm going to run into issues with the validation classes calling the manager classes down the line.
-
Put that validation in the Manager. However my question here is how to enforce that the developer using the business layer dll correctly calls the methods that do the validation. If I have a "persistance manager" that allows SaveEntity and the developer just creates a new reading entity on their own and calls the save (as opposed to going through a readingmanager.createreading(paramters) call) then the validation wouldn't get called. Or, if the reading is created on its parent vehicle object and the save is called on the vehicle I need to make sure the validation is called on the reading object without having redundant "if vehicle.odometerreadings.hasdirty then validate" code.
A high percentage of our entities are parent/child relations with a decent amount of cross validation so this is going to be a common occurance for us. At this point this is a web app but have future needs for WCF/Remoting/WebService support so am trying to "think ahead" to that as well.
Thanks in advance,
Heather
Joined: 28-Nov-2005
Hi Heather, some of my thoughts:
I personally would go on your option #2. This is the best place as you could fetch/save things as part of your business validation. The entity validation is for entities, the manager clases (business logic) is intended for cross-entity validation.
I know a programmer could take your entity and save it without using the Manager classes. But if you can't trust on your programmers, then you are in troubbles as there are a lot of other things they could do. You can refine this if, for instance, you only give the programmers the interface for your manager classes and enforce the guidelines to use them. I've work this way in many projects and I'm satisfied so far.
This way (#2) is also consistent with your future needs (WCF/Remoting/etc).
HTH
Joined: 04-Apr-2007
I have sought the solution to this problem for some time now. Here are my thoughts.
“Aggregate Roots” - (My preferred method)
If you consider the VehicleEntity to be an aggregate root, then doors, windows and odometer readings are part of that entities domain and they don’t have meaning without the Vehicle. Therefore if you want to perform CRUD operations on any one of those components you must retrieve the vehicle to do it. Then adding cross validations in the validation classes for odometer readings are easy, as you have assured that when ValidateEntity is called on a new OdometerReadingEntity it’s containing VehicleEntity object was already populated with all of its previous OdometerReadings or at least the latest one. The drawback to this method is that there are no Door, Window, or Odometer managers (repositories) in your object model. You would only have one Vehicle manager (repository) that would handle all of the queries for the entire aggregate (graph) and its methods should only return VehicleEntities. To make the point clearer here is an example:
Let’s say you want the DoorEntities from all 2 door Vehicles. You manager class would not have the method: public List<DoorEntity> GetDoorsFromCarsWithOnly2Doors();. It would however have: public List<VehicleEntity> Get2DoorCars();. Then you would traverse each entity to get the doors. That doesn’t mean that all method have to return just VehicleEntities. You just don’t want to return any entities without the full object graph. Now, let’s consider that the Odometer readings are actually your primary business objects and do have a meaning without the vehicle. Make them the aggregate root as well as a vehicle. Therefore you can go up either side of the object graph to get what you need. You will have to decide what makes sense for your application.
Using this method makes saving easy as there is only one save method for the entire aggregate : Save(VehicleEntity entity); . It assumes the entire graph that you returned is there so you can easily check for added, updated and deleted related entities. This is also the place where the final validation steps are handled.
If you really think about it, odometer readings are immutable value types. So I wouldn’t even allow use of the actual entities in your application other than in the manager class, nor would I return them in the object graph. I would have methods like GetLastOdometerReadingForVehicle(); GetAllOdometerReadingsForVehile(); and RecordNewOdometerReadingForVehile(). Those methods would return separate structures not entities. Then, in the entity designer I would remove the related fields between those two entities so there is no question where to work with the odometer readings. (On an aside, that is the one problem that I have had with LLBLGEN. It is an open canvas and all relations in the database including m:n are initially included in the generated code. I initially thought it was great as I could do anything anywhere. But I soon figured out having all that power in a multitier architecture was the bane of my existence. Developers consuming my manager classes would get a VehicleEntity and traverse all of the related entity collections assuming that everything was prefilled (when using adapter). I constantly had to remind them that this method only returns this partial graph and that method one returns a different one. So by hiding fields in the designer I made it very clear what was returned with what Entity. If you aren’t going to return doors with the vehicle entity then remove the related fields. Don’t worry all of the relations are still there which makes it easy to query across graphs.)
“Independent Entities”
If you really just want to stick with independent entities then I would really want the validation to belong to the entity itself even if the validator has to access remote resources. The purpose of validation is to keep your object in a valid state and if you allow a developer to circumvent your business rules your data store will quickly become invalid. I wouldn’t necessarily have the validator call another manager because you really want very specific data that corresponds with specific business rules. The dependency is unnecessary. I would just create a dataaccessadapter and issue the query that will get you what you need to validate the entity.