Альтернатива использованию Thread.current в оболочке API для 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

У меня также есть модуль, который я могу включить в мои модели для доступа и вызова методов 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

Затем в контроллере приложения я создаю новый объект Apiconnection для каждого запроса с использованием фильтра before, который устанавливает Thread.current [: api_conn]. Это связано с тем, что у меня есть сотни клиентов, каждый из которых имеет свои собственные 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 будет nil на случайных запросах. Поэтому я хотел бы знать, как лучше настроить эту оболочку.

Ответ 1

Ответ 1

Я ожидаю, что проблема заключается в следующем запросе, который должен пройти до завершения соединения, а затем before_filter перезаписывает соединение для продолжающегося соединения. Я постараюсь держаться подальше от тем. Это проще для fork_off, но есть и некоторые предостережения, особенно в отношении производительности.

Я пытаюсь переместить логику, подобную этой, на какую-то фоновое задание. Общим решением является отложенная работа https://github.com/collectiveidea/delayed_job, так что вам не нужно возиться с потоками, и она более надежна и легко отлаживается. Затем вы можете запускать фоновые задания для асинхронной синхронизации службы, когда кто-то входит в систему.

@account.delay.optin_via_email(email,user)

Это приведет к сериализации учетной записи, сохранит ее в очереди заданий, где ее подхватит отложенная работа, неэтериализованная, и будет вызван метод после задержки. У вас может быть любое количество фоновых заданий и даже некоторые очереди заданий, предназначенные для определенных типов действий (с использованием приоритетов работы - скажем, два bj для высокоприоритетных заданий и один посвященный заданиям низкого 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) }