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

nat*_*itt 3 ruby api ruby-on-rails

我开发了一个应用程序,允许我们的客户创建自己的会员保护网站.然后,我的应用程序连接到外部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
Run Code Online (Sandbox Code Playgroud)

我还有一个模块可以包含在我的模型中以访问和调用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
Run Code Online (Sandbox Code Playgroud)

然后在应用程序控制器中,我使用设置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
Run Code Online (Sandbox Code Playgroud)

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

sun*_*ity 6

答案1

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

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

@account.delay.optin_via_email(email,user)
Run Code Online (Sandbox Code Playgroud)

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

答案2

只需将其作为对象

def before_filter
  @api_connection =  ApiConnection.new(url, key)
end
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

答案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
Run Code Online (Sandbox Code Playgroud)

这样你就可以轻松忘记连接的创建,并有一个新的方便.可能会有性能损失,但我怀疑它们是微不足道的,除非有额外的登录请求

@user.api_connection do { |conn| conn.optin_via_email(email,user) }
Run Code Online (Sandbox Code Playgroud)

  • @sunkencity非常好的答案,格式精美,易于阅读.做得好! (2认同)