0

Scenario

I have a system where a user can upload an Excel file which is validated and then used to provide data for the application.

There are two distinct operations I need to handle with this file:

  • Validation: On upload, I want to validate report any problems with the file, so that the user can fix it. Some of the validation criteria are dynamic, and depend on other values in the database.
  • Runtime integrity check: Later, during processing, I want to create value objects from the Excel file. While doing that I want to re-check the integrity of the Excel file, because the value objects cannot and should not be created otherwise. A failing integrity check will throw an exception.

Typical failures could be "Missing sheet names", "Unexpected sheet names", "A sheet does not have the expected column labels.", "Unsupported formula", etc.

Reporting and logging:

  • If validation fails on upload, I want to show detailed messages to the user. There is not really a need to log anything. The messages should be localized.
  • If the integrity check fails, I want to show a simple message to the user, telling them that the operation failed, and that the previously uploaded Excel file is no longer valid. I don't think this is the place to show details about the validation. In the logs I want to post a similar message, plus all the details I have available.

The failing integrity check should generally not happen, if the validation was successful. But I don't want to fully trust the validation process - perhaps the Excel file was uploaded earlier, when some parameters for the validation were different. Also if validation and integrity check are different code, I don't want to rely on them being fully equivalent.

Technology

I am using PHP + Drupal 9, which includes parts of symfony. But I am looking for a generic answer that could also apply to other languages / technologies.

Goal + Question

To have a single source of truth, I would like to reuse the code for user-facing validation and the integrity check when creating the value object.

Is there a pattern I can use to do this?

Or is it common practice to just have two fully separate parts of code?

My concern is that the two parts will easily get out of sync, and that there is at least a theoretical code path where the validation passes but the runtime integrity check still fails.

A side goal is to also have reporting information for positive cases. E.g. "3 of 10 sheets were imported" (compared to previous version).

What I considered so far

Exceptions with user messages

One option would be to have special exceptions that contain messages suitable for display to the user, as suggested here. One problem here is that this will only show the first fail, and not all the problems in the data. Also, all the arguments from here apply.

Injectable failure handler

Have an injectable object that deals with failures. One version would collect all failures, while another would immediately throw an exception on fail.

Complex return value.

There could be a return value that contains information about the failures, but that also provides the value object, if the data is valid. It could either produce a validation report, or it could provide methods like getMissingSheetNames() etc which would then be used by another piece of code to produce failure messages. Tbh I would prefer to keep it all in one place.

3
  • How do both of my scenarios rule out stopping on the first error? For an integrity check it would be ok to fail early and fail "cheap". Any additional validation steps would be unnecessary if we already know it is failing. For the validation case we usually want to run all the checks, but there could be some errors that make further validation impossible. E.g. if the file is not a valid Excel file, we cannot validate the sheet names. Or if we don't find the expected sheet names, we cannot validate the contents of a specific sheet. So there could be some early exit points.
    – donquixote
    Commented Nov 3, 2022 at 23:12
  • Your suggestion I would say falls in the category of "complex return value", which is maybe ok.
    – donquixote
    Commented Nov 3, 2022 at 23:14
  • (my comments refer to another comment which has been deleted since, it seems)
    – donquixote
    Commented Nov 7, 2022 at 10:34

1 Answer 1

1

I would typically start with the most simple and DRY solution first you can come up with. I guess this would be a reusable component which produces everything (error objects and value objects) and let callers decide what they need. Callers then can decide if they want to throw an exception in case of errors, or if they just want to log the results, or take other actions.

When it turns out this approach is not efficient enough for certain scenarios, (justified by real observations and measurements, not just hypothetically), then it is time to optimize, not beforehand. Optimization could include, for example, parameters for the component to control which values to return, like

  • localized error messages

  • value objects (or not)

or if certain validation checks shall be applied or not.

The decision if validation stops at the first error, or after the first 10 errors, or after collecting all errors can be solved most cleanly by using an iterator (a.k.a. generator function) approach, if your environment supports this (so the caller can simply stop iterating over the resultset, when they like to). But you could also introduce a parameter like "maximum number of errors to return".

IMHO the most important thing, however, is, that you don't try to make all these decisions upfront. Instead, start simple and small, and optimize by doing some refactoring cycles. No stranger from the internet can do this for you, hence we cannot tell you which of these alternatives will suit your case best.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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