What’s new?
Testability has been kind of an issue for us with code that relies on the static Validator class. To improve the situation, I added IValidationEngine as an alternative. Validator is still fully backwards compatible
- Addition of the IValidationEngine interface
- The DefaultValidationEngine requires an instance of IValidatorProvider for its constructor
- Validator.SetValidatorProvider() has been marked obsolete. You can still use it, but it will have the side-effect of over-setting Validator.ValidationEngine to a new instance of DefaultValidationEngine.
- Added a NinjectModule to Simple.Validation.Ninject that configures the DefaultValidationEngine and DefaultValidatorProvider automatically.
When designing a batch API, it is important that the return results be relatable to the individual arguments that were asked to be processed.
Consider an API that takes a list of objects to perform an operation on. It might have a method signature that looks like this:
Response Process(Record[] records);
What is a good design for the Response object? I recently encountered a situation in which I was given a response object like this:
public class Response { public bool Success { get; set; } }
Okay, actually it was more like this:
bool ProcessTheRecords(int[] ids);
The problem with this approach is that when the operation fails there is nothing on the Response to indicate the nature of the failure. A better Response object might look like this:
public class Response { public bool Success { get; set; } public OperationResult[] ErrorsAndWarnings { get; set; } public ProcessRecordResult[] Results { get; set; } } public class ProcessRecordResult { public string RecordIdentifier { get; set; } public OperationResult[] ErrorsAndWarnings { get; set; } } public class OperationResult { public string Message { get; set; } public Severity Severity { get; set; } } public enum Severity { Error = 1, Warning =2, }
Why is this better? Response.ErrorsAndWarnings lets me see if anything went wrong with the operation outside of any particular record. There should be a ProcessRecordResult for each record passed into the method call. Each ProcessRecordResult has its own list of ErrorsAndWarnings that indicates what may or may not be wrong with a specific record.
The purpose of the response object is to give enough information to the caller so that they can adjust their failed request into a successful one. Minimize the size and shape of the data required to perform the operation. Maximize the size and shape of the data required to understand what went wrong during the operation. These principles apply to non-batch operations as well, but they are especially frustrating when there’s an error in one of hundreds of records.