Альтернатива использованию Thread.current в API-оболочке для Rails - PullRequest
3 голосов
/ 22 сентября 2011

Я разработал приложение, которое позволяет нашим клиентам создавать свои собственные сайты, защищенные членством. Затем мое приложение подключается к внешней службе 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 будет нулевым при случайных запросах. Поэтому я хотел бы знать, как мне лучше настроить эту оболочку.

1 Ответ

6 голосов
/ 22 сентября 2011

Ответ 1

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

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

@account.delay.optin_via_email(email,user)

Это позволит сериализовать учетную запись, сохранить ее в очереди заданий, где она будет забрана несериализованным отложенным заданием, иметод после задержки будет вызван.Вы можете иметь любое количество фоновых заданий и даже несколько очередей заданий, выделенных для определенных типов действий (используя приоритеты заданий - скажем, два bj для заданий с высоким уровнем первичной обработки и одно выделенное для заданий с низким уровнем первичной обработки)

Ответ 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) }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...