Alternativa al uso de Thread.current en la envoltura API para Rails

He desarrollado una aplicación que permite a nuestros clientes crear sus propios sitios web protegidos por membresía. Mi aplicación luego se conecta a un servicio API externo (api_key / api_url específico del cliente) para sincronizar / actualizar / agregar datos a este otro servicio. Bueno, he tenido un contenedor de API escrito para este otro servicio que ha funcionado hasta este punto. Sin embargo, ahora estoy viendo caídas muy aleatorias donde la conexión es nula. Aquí es cómo estoy usando actualmente la conexión:

Tengo una clase de conexión 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 

También tengo un módulo que puedo incluir en mis modelos para acceder y llamar a los métodos 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 

Luego, en el controlador de la aplicación, creo un nuevo objeto ApIConnection en cada solicitud utilizando un filtro anterior que establece Thread.current [: api_conn]. Esto se debe a que tengo cientos de clientes, cada uno con sus propias api_key y api_url, que utilizan la aplicación al mismo tiempo.

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

Bueno, mi pregunta es que he leído que el uso de Thread.current no es la forma más ideal de manejar esto, y me pregunto si esta es la causa de que ApiConnection sea nula en solicitudes aleatorias. Así que me gustaría saber cómo podría configurar mejor esta envoltura.

respuesta 1

Espero que el problema sea la siguiente solicitud antes de que finalice la conexión, y luego el before_filter sobrescribe la conexión para la conexión en curso. Intentaría alejarme de los hilos. Es más fácil hacer un fork_off, pero también hay ciertas advertencias, especialmente con respecto al rendimiento.

Intento mover la lógica como esta a un trabajo de fondo de algún tipo. Una solución común es el trabajo retrasado https://github.com/collectiveidea/delayed_job de esa manera, no tiene que meterse con los hilos y es más robusto y fácil de depurar. Luego, puede iniciar trabajos en segundo plano para sincronizar de forma asíncrona el servicio cada vez que alguien inicie sesión.

 @account.delay.optin_via_email(email,user) 

Esto serializará la cuenta, la guardará en la cola de trabajos, donde será retenida por un trabajo retrasado sin ser serializado y se llamará al método posterior al retraso. Puede tener cualquier número de trabajos en segundo plano, e incluso algunas colas de trabajos dedicadas a ciertos tipos de acciones (a través del uso de prioridades de trabajo, por ejemplo, dos bj para trabajos de alta prio y uno dedicado a trabajos de baja prio)

Respuesta 2

Solo hazlo como un objeto en su lugar

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

entonces puedes usar esa conexión en tus métodos de control

 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 

Respuesta 3

La solución más sencilla podría ser crear la conexión api siempre que la necesite.

 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 

de esa manera, simplemente puede olvidarse de la creación de la conexión y tener una nueva a mano. Puede haber penalizaciones de rendimiento con esto, pero sospecho que son insignificantes a menos que haya una solicitud de inicio de sesión adicional

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