如果API调用失败,如何进行事务回滚,反之亦然?

时间:2022-08-16 00:02:59

Two things I want:

我想要的两件事:

a) I want to be able to save a record in the db only if the API call succeeds

a)我希望只有在API调用成功时才能在db中保存记录

b) I want to execute the API call only if the db record saves successfully

b)我只想在db记录成功保存的情况下执行API调用

The goal, is to keep data stored locally (in the DB) consistent with that of the data on Stripe.

目标是使本地(在数据库中)存储的数据与Stripe上的数据保持一致。

@payment = Payment.new(...)

begin
  Payment.transaction do
    @payment.save!

    stripe_customer = Stripe::Customer.retrieve(manager.customer_id)

    charge = Stripe::Charge.create(
      amount:   @plan.amount_in_cents,
      currency: 'usd',
      customer: stripe_customer.id
    )
  end
# https://stripe.com/docs/api#errors
rescue Stripe::CardError, Stripe::InvalidRequestError, Stripe::APIError => error
  @payment.errors.add :base, 'There was a problem processing your credit card. Please try again.'
  render :new
rescue => error
  render :new
else
  redirect_to dashboard_root_path, notice: 'Thank you. Your payment is being processed.'
end

The above following will work, because if the record (on line 5) doesn't save, the rest of the code doesn't execute.

以上内容将起作用,因为如果记录(第5行)没有保存,则其余代码不会执行。

But what if I needed the @payment object saved after the API call, because I need to assign the @payment object with values from the API results. Take for example:

但是如果我需要在API调用之后保存@payment对象,那该怎么办呢,因为我需要为@payment对象分配API结果中的值。举个例子:

@payment = Payment.new(...)

begin
  Payment.transaction do
    stripe_customer = Stripe::Customer.retrieve(manager.customer_id)

    charge = Stripe::Charge.create(
      amount:   @plan.amount_in_cents,
      currency: 'usd',
      customer: stripe_customer.id
    )

    @payment.payment_id = charge[:id]

    @payment.activated_at = Time.now.utc
    @payment.save!
  end
# https://stripe.com/docs/api#errors
rescue Stripe::CardError, Stripe::InvalidRequestError, Stripe::APIError => error
  @payment.errors.add :base, 'There was a problem processing your credit card. Please try again.'
  render :new
rescue => error
  render :new
else
  redirect_to dashboard_root_path, notice: 'Thank you. Your payment is being processed.'
end

You notice @payment.save! happens after the API call. This could be a problem, because the API call ran, before the DB tried to save the record. Which could mean, a successful API call, but a failed DB commit.

你注意到@ payment.save!在API调用之后发生。这可能是一个问题,因为在DB尝试保存记录之前,API调用已运行。这可能意味着,一个成功的API调用,但失败的数据库提交。

Any ideas / suggestions for this scenario?

有关此方案的任何想法/建议?

1 个解决方案

#1


7  

You can't execute API => DB and DB => API at the same time (sounds like an infinite execution conditions), at least I can't image how you can achieve this workflow. I understand your data consistency needs, so I propose:

你不能同时执行API => DB和DB => API(听起来像无限的执行条件),至少我无法想象如何实现这个工作流程。我了解您的数据一致性需求,因此我建议:

  • Check if record is valid @payment.valid? (probably with a custom method like valid_without_payment?)
  • 检查记录是否有效@ payment.valid? (可能使用像valid_without_payment这样的自定义方法?)
  • Run api call
  • 运行api调用
  • Save record (with payment_id) only if api call succeds
  • 仅在api调用成功时保存记录(使用payment_id)

Alternatively:

或者:

  • Save record without payment_id
  • 没有payment_id保存记录
  • Run api call
  • 运行api调用
  • Update record with payment_id (api response) if call succeds
  • 如果调用已成功,则使用payment_id(api响应)更新记录
  • Run a task (script) periodically (cron) to check inconsistent instances (where(payment_id: nil)) and delete it
  • 定期运行任务(脚本)(cron)以检查不一致的实例(其中(payment_id:nil))并删除它

I think both options are acceptable and your data will remain consistent.

我认为这两个选项都是可以接受的,您的数据将保持一致。

#1


7  

You can't execute API => DB and DB => API at the same time (sounds like an infinite execution conditions), at least I can't image how you can achieve this workflow. I understand your data consistency needs, so I propose:

你不能同时执行API => DB和DB => API(听起来像无限的执行条件),至少我无法想象如何实现这个工作流程。我了解您的数据一致性需求,因此我建议:

  • Check if record is valid @payment.valid? (probably with a custom method like valid_without_payment?)
  • 检查记录是否有效@ payment.valid? (可能使用像valid_without_payment这样的自定义方法?)
  • Run api call
  • 运行api调用
  • Save record (with payment_id) only if api call succeds
  • 仅在api调用成功时保存记录(使用payment_id)

Alternatively:

或者:

  • Save record without payment_id
  • 没有payment_id保存记录
  • Run api call
  • 运行api调用
  • Update record with payment_id (api response) if call succeds
  • 如果调用已成功,则使用payment_id(api响应)更新记录
  • Run a task (script) periodically (cron) to check inconsistent instances (where(payment_id: nil)) and delete it
  • 定期运行任务(脚本)(cron)以检查不一致的实例(其中(payment_id:nil))并删除它

I think both options are acceptable and your data will remain consistent.

我认为这两个选项都是可以接受的,您的数据将保持一致。