【问题标题】:Organizing API-Calls in callbacks在回调中组织 API 调用
【发布时间】:2014-11-05 06:45:17
【问题描述】:
我们正在维护几个 Rails 应用程序,它们都提出了类似的问题,我们没有真正好的解决方案:所有这些应用程序都包含需要在其生命周期中对外部服务进行 API 调用的模型。
可能的情况:
- 成功创建后,用户订阅了 Newsletter-subscriber-list
- 优惠价格在更新后与外部购物系统同步
- 产品更新后在搜索索引中更新
我们体验到的不是是一个好的解决方案:将这些调用添加到模型的after_*callbacks。由于这会快速破坏测试,因此所有工厂现在都必须处理 api 调用。
我正在寻找一种组织这些 API 调用的好方法。你们是怎么做到的?
我们提出的想法,我认为这并不理想:
- 将这些回调移动到控制器。现在他们在创建对象时很容易被遗忘
- 生成一个异步工作器来处理 api 调用。然后每个(甚至是小型应用程序)都需要延迟作业队列的开销,例如 sidekiq。
【问题讨论】:
标签:
ruby-on-rails
ruby
design-patterns
architecture
refactoring
【解决方案1】:
如果您关心测试,您可以put the callback methods into a separate class 并在测试期间模拟回调类。这是一个使用 RSpec 的示例,给定以下 Foo 和 FooCallbacks 类:
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
【解决方案2】:
在得到建议之后,我现在就是这样做的:
在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