10

I'm working on a system that allows Administrators to define Forms that contain Fields. The defined Forms are then used to enter data to the system. Sometimes the Forms are filled by a human via a GUI, sometimes the Form is filled based on values reported by another system.

For each Field, the Administrator can define a Validation Rule that limits the allowed values for the Field. The Validation Rules can be anything from "the value entered in the Field must be True or False" to "the value entered in the Field must exist in column A of table B in the database". The Administrator may at any time change the Validation Rule for the Field.

In this scenario, what in your opinion is the most suitable place for validating that each Field is filled correctly? I currently have two main approaches in mind:

Option #1: Validate in the Domain Model

Each Field object would contain the Validation Rule specified by the Administrator. The Field objects would also have a reference to an IValidator. When an attempt is made to set the value of the Field, the Field would pass the given value and the Validation Rule to the IValidator. If the given value is not valid, a ValidationException would be thrown and appropriately handled in the GUI/interface to the other system.

Pros:

  • Strong protection against Fields being accidentally assigned values that violate the Validation Rule

Cons:

  • The Data Access Layer needs to be able to bypass the validation and construct Fields that violate the current Validation Rule. Despite the Administrator changing the Validation Rule for a Field, we still need to be able to construct Field objects based on the old data e.g. when rendering a Form that was filled years ago. This could potentially be resolved by storing the current Validation Rule whenever we store the Field.

  • In this design, the Field model has an indirect link to the Data Access Layer/Repository via the IValidator. The injection of Services/Repositories to Domain Models seems to be generally frowned upon.

Option #2: Validate in a Service

Try to ensure that all attempts to set the value of a Field pass through a Service that ensures the Validation Rule holds. If the Validation Rule is violated, throw a ValidationException.

Of course, the Data Access Layer would not use the Service when creating Field objects that have previously been persisted in the DB.

Pros:

  • Does not violate the "don't inject Services/Repositories into your Domain Models"-thinking.

  • No need to persist the current Validation Rule when persisting the Field. The Service can simply look up the current Validation Rule for the Field; when looking at history data, the value of the Field will not be changed.

Cons:

  • No guarantee that all logic that should use the Service to set the Field value actually does so. I see this as a major drawback; all it seems to take is someone writing "thisField.setValue(thatField.getValue())" and the Validation Rule of thisField might be violated without anyone being the wiser. This could potentially be mitigated by ensuring that the value of the Field matches with the Validation Rule when the Data Access Layer is about to persist the Field.

I currently prefer Option #1 over Option #2, mainly because I see this as business logic and feel that Option #2 poses a greater risk of introducing bad data to the system. Which option do you prefer, or is there another design that fits this scenario better than the two options described?

Edit (Complexity of validations)

The validation cases that have come up for now are relatively simple; the Field value must be e.g. numeric, a date, a date with a time or be an existing value in a database column. However, I suspect complexity to gradually increase over time. For example, the validation solution needs to be built with internationalization in mind - things such as Dates may be input in a locale-specific syntax.

I've decided to proceed with Option #1 for now, attempting to take care not to assign too many responsibilities to the Domain Model. Those facing a similar situation may also want to check out the related questions Validation and authorization in layered architecture and Data input validation - Where? How much?.

  • Creating the model works great with all the validations. But when you want to edit or view the form, the validation kicks in too. This throws exception for fields that are empty or have invalid values - perhaps changed in the db or loaded from Excel. The expected solution is to allow the form to show on updating so the Admin can correct those fields. – tunmise fasipe Jul 10 '15 at 15:41
4
+100

How complex are the validations? Often validations require a combination of fields and or business rules that rely on fields to be accurately evaluated.

The more complex the validations, the harder and less performant option #2 is.

Of course the data tier could invoke the validation service at persistence time. This could help the odd situation where data is in an invalid state due to a change in rules.

The other item worth commenting on is the ability for a change in validation rules without a qa cycle of some sort. But that is a topic for a different thread.

Given the information above, option 1 seems the most flexible assuming you maintain discipline, separating validation and persistence.

0

Maybe you are missing a layer. Without knowing the details of your application (requisites, architecture, etc) I would do something like Client (whoever it be) -> Application Service -> Domain Model

The application service layer is allowed to interact with the repository and the domain model containing the business logic. So you can have something like:

FieldUpdateService >> updateField(fieldId, newValue)
  List<FieldRule> rules = this.rulesRepository.findRulesFor(fieldId)
  Field field = this.fieldRepository.find(fieldId)
  try 
    field.updateValue(rules,newValue)
    fieldRepository.save(field)
  catch 
    // manage error

If you have a relationship beteween a Field and his FieldRules and use an ORM like Hibernate in Java you will only do:

FieldUpdateService >> updateField(fieldId, newValue)
  Field field = this.fieldRepository.find(fieldId)
  try 
    field.updateValue(newValue)
    fieldRepository.save(field)
  catch 
    // manage error

Field >> updateValue(newValue)
  for rule in rules
     if !rule.isValid(newValue)
        throw ValueNotAllowedException
  this.value = newvalue

Because the ORM query and instantiates the graph of objects that you ask for.

Regarding your doubt about the fact that someone can do

thisField.setValue(thatField.getValue())" and the Validation Rule of thisField might be violated without anyone being the wiser

Someone also could write: FieldRule alwaysReturnTrueRule = new FieldRule{ isValid(newValue){ return true; } } field.updateValue("uncheckedValue",alwaysReturnTrueRule) It's an obscure example but what im traying to say is that nothing protects you from a bad use of the code. Perhaps good documentation and comunication face to face. I think nothing protects you from a bad use of the code.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.