在回调中组织API调用

时间:2014-09-11 10:43:17

标签: ruby-on-rails ruby design-patterns architecture refactoring

我们正在维护多个Rails-Apps,这些应用程序都存在类似的问题,我们没有一个非常好的解决方案:所有这些应用程序都包含需要在其生命周期中对外部服务进行API调用的模型。

可能的情况:

  • 用户在成功创建时订阅了Newsletter-subscriber-list
  • 更新后,优惠价格与外部购物系统同步
  • 产品在更新后的搜索索引中更新

我们对 NOT 的体验是一个很好的解决方案:将这些调用添加到模型的after_*回调中。由于这会快速打破测试,因此现在所有工厂都必须处理api调用。

我正在寻找一种组织这些API调用的好方法。你们是怎么做到的?

我们提出的想法,我认为这不是真正的理想:

  • 将这些回调移动到控制器。现在,在创建对象时,它们很容易被遗忘
  • 产生一个异步工作程序来处理api-call。然后,每个 - 甚至是小应用程序 - 都需要有延迟的作业队列的开销,比如sidekiq。

2 个答案:

答案 0 :(得分:2)

如果您担心测试,可以put the callback methods into a separate class并在测试期间模拟回调类。以下FooFooCallbacks类给出了使用RSpec的示例:

class Foo < ActiveRecord::Base
  after_save FooCallbacks
end

class FooCallbacks
  def self.after_save
    fail "Call to external API"
  end
end

您可以编写并成功运行如下规范:

describe Foo do

  before do
    allow(FooCallbacks).to receive(:after_save)
  end

  it "should not invoke real APIs" do
    Foo.create
  end

end

答案 1 :(得分:1)

这就是我现在的意思,在建议之后:

Foo

class Foo < ActiveRecord::Base
  before_save Foo::DataSync
end

Foo:DataSync看起来像这样:

class Foo::DataSync
  def self.before_save(foo)
     ...do the API-Calls...
  end
end

现在在rspec中进行测试我添加了这个:

spec_helper.rb

config.before(:each) do
  Foo::DataSync.stub(:before_save)
end

请注意config.before(:suite)无效,因为当时未加载Foo:DataSync

现在foo_spec.rb只包含这个:

describe Foo do
  let(:foo) {create(:foo)}

  it "will sync its data before every save" do
    expect(Foo::DataSync).to receive(:before_save).with(foo)

    foo.save
  end
end

Foo::DataSync可以像这样进行测试:

describe Foo::DataSync do
  let!(:foo) {create(:foo)}

  before do
    Foo::DataSync.unstub(:before_save)
  end

  after do
    Foo::DataSync.stub(:before_save)
  end

  describe "#before_save" do
    ...my examples...
  end
end