سؤال

I'm building a shop system which works in three parts: a mobile app (Android/iOS) which is used by customers to make orders, view products and so on, a desktop application, used by the managers to manage orders and shop things and a web server with an API to which both mobile and desktop applications communicate.

The server API is restful, but I'm starting to encounter a few problems: if the user tries to buy items that are no longer available, I can't just send them a BadRequest back and say "Couldn't complete the order, please try again", I have to tell them why and tell them which product in the cart is unavailable, so the mobile app knows which product to remove from the cart. That have been fixed by using Dtos everywhere, but other problems began to arise: if the user tries to add a delivery address and some field isn't valid, I can't send them a BadRequest with some string message, I have to tell them exactly which field isn't valid.

So I thought about creating enums with every possible error for every endpoint, but I can't think of an organized way of doing it. I already have all the possible Dtos for every endpoint, so should I put the enums for each endpoint inside the Dtos? Or another folder/namespace only for it and place every enum inside a corresponding class for the endpoint? I don't think a single static class for it would be a good approach.

هل كانت مفيدة؟

المحلول

The kind of validation you're describing is part of your business domain and should be treated differently from API errors (such as a request that doesn't have the right parameters). If you think about it this way, then it makes sense to put the enums inside the DTOs since they represent the result of your business operation rather than the result of the API call (for which there is already a HTTP status code).

There are two levels of validation you can do here:

  • Mandatory validation when you actually place an order. Either this is successful and you return something like an order ID in the response, or it fails and you return the failure reason and additional details (such as which items are no longer available).
  • Optional validation that is performed via a separate API call. This can improve user experience by providing early feedback before they try to place the order. Of course you still have to validate when the order is actually placed to make sure the request is still valid.

نصائح أخرى

Don't try to send validation responses for errored api calls.

Treat them as exceptions. You can send a message and a type but thats your lot.

Instead, give your client the apis it needs to do validation itself. eg instead of submitting an order and getting 'item x is out of stock' give it a stock check api it can call to check the stock levels before placing the order.

With things like the address, give the client the rules it needs to construct a valid request every time. Don't fire off any old collection of strings and have the server run those same rules.

--

As David points out in his comments there is another (wrong) view that you should not check prior to the command as your check can be out of date.

However, this just doesn't work for complex checks, as in practice it requires database level structured error responses. ie the stock level failure would be caused by a FK constraint or similar when you tried to insert the order.

This would then bubble right back to the user and somehow tell them that item 5 in their basket is out of stock.

But there just isn't enough info exposed from the FK constraint failure to populate that level of detail. You need a seperate check.

The answer is, well I wrap the check and the command in a transaction, locking the db while I place the order so that the check can't go out of date, or if the check fails I can roll back so that no partial orders are placed.

However, it's perfectly possible to expand that transaction scope to the client. In this case with the concept of reserving stock for orders.

In most cases this is undesirable, it's easier to simply deal with the the edge case of the check being out of date and rerunning the check after a command fails with a simple ItemOutOfStockException

Plus, the client usually wants to run the check first anyway, ie when I am viewing the item im about to put in my basket, I want to see if its in stock.

For the server-response I would use

  • textual-error-code, (i.e. "err0815")
  • a technical error-messge not displayed to the enduser but added to the log-file (i.e. "no space left on database harddrive")
  • a localized errormessage that is displayed to the enduser. (i.e. "the order cannot be processed due to a technical problem)
  • a localized hint what the enduser can do (i.e. please try again later)

if you are using error-enums instead of textual-error-code then client-software and server-software must be compiled to the same version.

If you invent a new error-enum on the server side which the client does not know yet (i.e. old android app version) then the client may crash.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى softwareengineering.stackexchange
scroll top