2

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.

  • These dtos are tree structures like json/xml? For each error why not simply list the path, an action object, and a human readable error. The path points out the exact point in the object which is a problem. The action object represents one or more recovery actions like: delete, or use this format string, etc... The human readable string is there to try and communicate the problem to a human, just in case the application isn't up to the task. – Kain0_0 Jun 19 at 6:32
  • A better way would be not to get that far. Either don't show them or show as out of stock and not let them to be added to the cart. – Darren Young Jun 19 at 13:53
  • @DarrenYoung The item may have become out of stock after it was added to the cart. – Igby Largeman Jul 12 at 4:04
3

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.
1

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.

  • 4
    "...it can call to check the stock levels before placing the order...". And between the check stock level call and submit order, another order comes in and that second order fails. So the check stock level call was pointless. What you describe is exactly the way it should not be done. – David Arno Jun 19 at 11:10
  • This problem is solved with transactions – Ewan Jun 19 at 12:23
  • if indeed its a problem. obviously the call fails either way. and both ways allow you to see which item is out of stock after a failed call – Ewan Jun 19 at 12:25
  • 3
    Indeed. What's needed is a "TryPlaceOrder" that uses transactions behind the scene and responds with a success or an error-with-details response. No exceptions should come anywhere near this unless the API call itself is invalid or the server fails for some reason (not having stock is not one such failure) – David Arno Jun 19 at 12:26
  • 1
    nonsense! check before and throw if fails is the best pattern. If you need to guarantee the check is still good, then : begin trans, check, command, commit – Ewan Jun 19 at 12:51
1

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.

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.