I've got the following:

Clientside C# application. Contains forms for CRUD operations. It uses an API for all data operations. Input is validated on the client, and obviously also on the server (API).

I use the repository pattern for calling my API. That's because I want to communicate with data layers via interfaces, and want to be able to have multiple data sources for one repository interface. Like:

  - bool CreateCustomer(Customer customer);
  - Customer GetCustomer(int id);
  - bool UpdateCustomer(Customer customer);
  - bool DeleteCustomer(int id);

APICustomerRepository : ICustomerRepository 

DatabaseCustomerRepository : ICustomerRepository

So far the context of the question.

When the API is called by the client, and a validation error would occur (or maybe some server error that is put in the response data), the errors have to be handled on the client. API responses are JSON, and when it's an error response, it's a JSON structure like {name: "Name is required"}. The point is: what is the best approach to give those errors back to the code that uses my repository classes.

As you can see, I return boolean values from repository methods which indicate success/failure. I would like to not just return a success/fail bool, but the actual errors that came via the API response. So, I could change the design and modify the method signatures to return a collection of errors. But the problem is, that 'read' operations like GetCustomer(int id) already need their return value to return the actual data on success. The return value of the method cannot be used for error handling anymore.

Solutions I can think of:

  1. Throw exceptions for everything. But this seems ugly. Because in my understanding there's a distinction between exceptional errors (like no internet connection while calling API, or API server is down), and non exceptional errors (like just wrong input that somehow slipped through the clientside input validation). Although the non-exceptional errors I mention are rare, and would theoretically never occur on the serverside validation.

  2. Wrapping the data (return value) of repository methods in a wrapper object, that can also contain a collection of error messages.

  3. After API response that indicates failure (and thus contains the error messages), save the messages in a class property of the repository class, and the code thet uses the repository has to check explicitly if there are errors stored in that class. (Seems to be ugly too).

What's the best or most common approach?

  • ‘Although the [...] errors I mention are rare, and would theoretically never occur on the serverside validation.’ So they only occur in exceptional situations?
    – Rik D
    Apr 25, 2021 at 12:17
  • Well, unless the serverside validation also uses some logic that can only be checked on the server. Like checking if a username already exists. Or maybe even that a concurrent request of another client instance changed data/state on the server between clientside and serverside validation. Apr 25, 2021 at 14:48

2 Answers 2

  1. Throw exceptions.

You are correct in that you should throw exceptions for exceptional things. But in this case it is exceptional for the API to throw a validation error, as you are checking for them on the client before sending.

You should only see this exception if the client has a bug that lets incorrect data through.

  • As you want to minimize the dependency between back- and frontend, deciding the design of your backend based on what your frontend is (or isn't) doing is not a great approach. If tomorrow the frontend stops validating, or the backend gets added validation that the frontend doesn't, this will force you to have to update/redesign both the backend and frontend. That is not to say that throwing exceptions isn't the right approach here, but "because the frontend also validates" is simply not the right justification for that approach.
    – Flater
    Apr 26, 2021 at 10:13
  • the dependency is only between the backend and the client lib for the backend. not the client application. This is an unavoidable dependency if you think about it, as the data structures and serialisation are shared.
    – Ewan
    Apr 26, 2021 at 10:18
  • I'm not talking about dependency in the sense of library reference, but rather that your backend design decision's justification "depends" on the frontend already validating (i.e. "as you are checking for [validation errors] on the client"). If that ever changes in the frontend, it'll force you to have to reconsider the backend, because you made your backend design depends on the frontend already validating.
    – Flater
    Apr 26, 2021 at 10:44
  • Interesting points. I wonder how other applications are designed concerning this question, since it's very common that client applications communicate with a (REST) API. Maybe someone has experience? Apr 26, 2021 at 10:49
  • if the client chooses not to check they will simply get the first failed validation as an exception. Its just the same as any other requirement of the api, such as "specify the id in this parameter.
    – Ewan
    Apr 26, 2021 at 11:10

If the repository is responsible for validating Customer, that implies that other code outside of the repository could encounter a Customer with a missing name, etc. From that perspective it doesn't make sense that the repository would do that validation. Its responsibility should be to store and retrieve Customer.

One option is to validate Customer within its constructor. Make it impossible to create an instance of Customer that's missing its name. Now you'll never have a Customer that's missing its name reach the repository or any other class where it could cause a problem. Otherwise you'd need to repeat that validation wherever Customer is used.

Even if the data is validate elsewhere, if data that's used to create a customer comes from the API and it's missing a name, attempting to create a Customer will fail. Same if there's something wrong with the data in the database. You'll never have an invalid Customer.

Once that's done you don't need the API to return success or failure. It either succeeds or throws an exception.

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.