替代在Rails的API包装器中使用Thread.current

时间:2011-09-22 05:22:50

标签: ruby-on-rails ruby api

我开发了一个应用程序,允许我们的客户创建自己的会员保护网站。然后,我的应用程序连接到外部API服务(客户特定的api_key / api_url),以同步/更新/添加数据到此其他服务。好吧,我已经为这个其他服务编写了一个API包装器。但是,我现在看到连接为零的随机丢弃。以下是我目前正在使用连接的方式:

我有一个xml / rpc连接类

class ApiConnection
  attr_accessor :api_url, :api_key, :retry_count

  def initialize(url, key)
    @api_url = url
    @api_key = key
    @retry_count = 1
  end

  def api_perform(class_type, method, *args)
    server = XMLRPC::Client.new3({'host' => @api_url, 'path' => "/api/xmlrpc", 'port' => 443, 'use_ssl' => true})
    result = server.call("#{class_type}.#{method}", @api_key, *args)
    return result
  end
end

我还有一个模块可以包含在我的模型中以访问和调用api方法

module ApiService

  # Set account specific ApiConnection obj
  def self.set_account_api_conn(url, key)
    if ac = Thread.current[:api_conn]
      ac.api_url, ac.api_key = url, key
    else
      Thread.current[:api_conn] = ApiConnection.new(url, key)
    end
  end

  ########################
  ###  Email Service   ###
  ########################

  def api_email_optin(email, reason)
    # Enables you to opt contacts in
    Thread.current[:api_conn].api_perform('APIEmailService', 'optIn', email, reason)
  end

  ### more methods here ###

end

然后在应用程序控制器中,我使用设置Thread.current [:api_conn]的before过滤器在每个请求上创建一个新的ApIConnection对象。这是因为我有数百个客户,每个客户都有自己的api_key和api_url,同时使用该应用程序。

# In before_filter of application controller
def set_api_connection
  Thread.current[:api_conn] = ApiService.set_account_api_conn(url, key)
end

我的问题是,我已经读到使用Thread.current并不是处理此问题的最理想方式,我想知道这是否是ApiConnection在随机请求中为零的原因。所以我想知道如何更好地设置这个包装器。

1 个答案:

答案 0 :(得分:6)

回答1

我希望问题是连接完成之前的下一个请求,然后before_filter会覆盖正在进行的连接的连接。我试图远离线程。 fork_off更容易,但也有一些警告,特别是关于性能。

我尝试将这样的逻辑转移到某种后台作业。一个常见的解决方案是延迟作业https://github.com/collectiveidea/delayed_job,这样你就不必乱用线程,它更健壮,更容易调试。然后,只要有人登录,您就可以启动后台作业以异步同步服务。

@account.delay.optin_via_email(email,user)

这将序列化帐户,将其保存到作业队列,在那里它将被延迟的作业取消序列化,并且将调用延迟后的方法。您可以拥有任意数量的后台作业,甚至可以使用一些专门用于某些类型操作的作业队列(通过使用作业优先级 - 假设两个bj用于高prio作业,一个用于低prio作业)

回答2

只需将其作为对象

def before_filter
  @api_connection =  ApiConnection.new(url, key)
end

然后您可以在控制器方法中使用该连接

def show
   #just use it straight off
   @api_connection.api_perform('APIEmailService', 'optIn', email, reason)
   # or send the connection as a parameter to some other class
   ApiService.do_stuff(@api_connection)
end

回答3

最简单的解决方案可能就是在您需要时创建api连接

class User < ActiveRecord::Base
  def api_connection 
    # added caching of the the connection in object
    # doing this makes taking a block a little pointless but making methods take blocks
    # makes the scope of incoming variables more explicit and looks better imho
    # might be just as good to not keep @conn as an instance variable
    @conn = ApiConnection.new(url, key) unless @conn 
    if block_given?
      yield(@conn)
    else
      @conn
    end
  end
end

通过这种方式,您可以轻松地忘记连接的创建,并拥有一个新的方便。这可能会有性能损失,但我怀疑它们是微不足道的,除非有额外的登录请求

@user.api_connection do { |conn| conn.optin_via_email(email,user) }