5

I have a website which uploads job postings to my API, there are multiple steps to doing this:

  1. Upload a logo image to file storage.

  2. Insert data about the job posting into a database.

  3. Process a payment with a third-party provider.

  4. Send an email through a third-party provider.

In general, you can imagine other steps being present here in different applications, e.g. getting some information from a 3rd party API, validating a ReCAPTCHA, updating Google Indexing API, sending an SMS, etc., etc.

Since all of these are using 3rd parties and are independent of the server handling the API call, any one of them can fail to leave some of the steps successfully completed and others not (e.g. logo updated but payment not collected).

My question is how are errors in these kinds of multi-step actions between clients and servers typically handled in production systems? Are there any accepted standards or best practices?


I have considered:

  1. Not handling errors and just hoping it all goes through without error.

  2. Defining an 'undo' function on the backend for each of the steps and if one of the steps fails, then calling that on the previous steps. With actions consisting of many steps, this could turn into spaghetti code pretty quickly, and some steps cannot be undone so easily - e.g. sending an email.

  3. Creating a separate endpoint on the backend for each of the steps and letting the client call each one in turn. This could also use an 'undo' API endpoint so if the client receives an error on one of the steps it can then undo all previous steps. This has the advantage of allowing the client to estimate the progress of the action being completed, i.e. it could display '1 of 5 steps completed' to the user.

  4. Creating a row in a DB (or in-memory database?) for each action and when each step is completed marking the corresponding column as completed. When every column in the row is completed then sending a response back to the user.

3
  • I would collect all this in the users session and once all "requirements" are aproved, i would add it to the database with a transaction to make sure "all or nothing" is saved.
    – Mr Zach
    Aug 18, 2020 at 14:21
  • 1
    Your option #4 involving a status field and additional messaging (including any errors) is what I would recommend. This is a multi-step workflow. Track the job posting as it moves through the workflow and report on its current status. If an error is encountered, then you know where it failed and why. See also sagas. You might add steps in the future, and 3rd party services can have delays.
    – Dan Wilson
    Aug 18, 2020 at 14:28
  • Thank you for your comments. There's plenty of reading I will do with this information.
    – AndyM
    Aug 18, 2020 at 14:54

3 Answers 3

1

This multi-step process is a transaction, moving from one consistent initial state ("nothing happened") to some final state ("job posting done"), with intermediate steps that leave your app in a (possibly) inconsistent state.

You should not delegate the transaction handling to your client, especially not split the process into individual calls (or he'll maybe skip the payment step - bad for the business).

If one step fails, it's surely nonsense to continue, you have to do some kind of rollback to an acceptable state, not necessarily the initial state. E.g. I don't see an absolute necessity to remove the uploaded image from file storage.

First, I'd try to arrange the steps in such a way that most of the intermediate steps are acceptable, so there's no need to rollback.

The tricky steps are surely payment and email (if I understand your business correctly).

  • Billing the customer without having the emails sent is bad (close to fraud).
  • Sending the emails without getting the payment means losing money (if this doesn't happen often, it might be tolerable). You definitely can't rollback emails, once they have been sent, but maybe you can rollback payments (unless you lose the connection to the provider just in that moment).

As you rely on external connections, I don't see a way to absolutely avoid partial completion of your transaction, so I'd design the process in such a way that intermediate failures

  • either highly unlikely
  • or leave a tolerable state.

So, I'd

  1. Ping all the expernal services to make sure they are currently up and reachable
  2. Upload a logo image to file storage.
  3. Insert data about the job posting into a database.
  4. Send an email through a third-party provider.
  5. Process a payment with a third-party provider.

The rollback procedure would be

  • remove the image from file storage,
  • remove the job posting from the database,
  • if email has been sent, but payment failed: schedule the payment request for a later retry.
0

"Best" obviously depends on the requirements. Number 1 is clearly simplest to implement but transactions will be lost/incomplete in case of errors. Maybe this is an acceptable trade-off from a business perspective?

The most robust solution is to split the process into a series of steps where each step is a transaction. A transaction is either completed or failed, and if it is failed it can safely be re-tried. (For example sending a mail or sms would be transaction.) A database row keeps track of which steps have been completed.

I don't think you should have the client call each step. That would create a tight coupling and just increase complexity. Just have the client call a single request with all necessary data, which initiates the workflow. The client can then send a separate, periodic request to poll the status if you want to show the progress.

Support for undo is more complicated and (as you observe) not always possible. If some step can fail in a way where the whole process should be rejected (like if a credit card could not valid) then I think it should be performed in a validation step before the multi-step process is started. This will allow you to give synchronous feedback to the client and have the client review and re-enter the data.

1
  • 1
    This is pretty close to how I'd answer. The one thing I would add is that it is often very important to validate all inputs at the beginning before you try any of the steps. Even with a retry mechanism, it is impossible to recover from a partial failure automatically if the failure is due to one the inputs being invalid.
    – John Wu
    Aug 18, 2020 at 23:30
0

As others mentioned, ideally you want to bundle all the pieces required for a successful job posting and have a client to send it to your backend in one request. As a mobile developer myself I always advocate for clients to the least amount of work possible to preserve data usage and battery life.

As for the backend, I would suggest trying to inserting the most important information into db first, and then try e.g inserting a supplementary data after such as an logo image

Your Answer

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

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