¿Por qué necesitamos procesamiento asíncrono?
En todo momento de una aplicación queremos que el usuario esté lo más a gusto posible cuando se encuentre utilizando la aplicación. Es por esto que utilizamos “client side frameworks”, Ajax, procesamiento asíncrono entre otras cosas… Todo para que la experiencia del usuario dentro de nuestra aplicación sea la mejor y más rápida posible.
¿En que partes de nuestra aplicación podríamos utilizar dicho procesamiento asíncrono?
El uso más común y frecuente es en el registro de usuario con funcionalidad de correo de bienvenida. Por lo general el proceso de envío de correos es bastante lento, desde que el usuario presiona el botón de “registrar” hasta que el servidor de correos devuelve una respuesta pueden pasar más 5 segundos, es por esta razón que la solución obvia es utilizar este tipo de tareas asíncronas. Otro posible ejemplo es un “contador” de visitas, sea a un post o página particular. Actualización de algún campo en base de datos, procesamiento de imágenes, entre otros.
El resúmen general el procesamiento asíncrono lo utilizamos cuando cierta funcionalidad dentro de una aplicación no requiera de la intervención del usuario, sea una tarea lenta o simplemente algo que queramos ejecutar en un horario específico.
En la versión 4.0 de Rails se pretendía que existiera la funcionalidad para manejo de procesamiento asíncrono, lamentablemente no pudo ser posible (no conozco el motivo) y se postergó hasta la versión 4.1 que está próxima a salir. En lo que sea oficial posiblemente tendremos un post para enseñar como funciona.
¿Por qué esta librería y no otra?
Elegí la librería sucker_punch para esta entrada por lo simple que es, funciona sobre el mismo proceso de la aplicación, no necesita configuración alguna ni tampoco una base de datos “key/value” como Redis, todo esto quiere decir que podemos utilizar esta librería en Heroku sin coste adicional al que recibimos de la aplicación actualmente.
La otra posible opción (sencilla) era utilizar delayed_job pero requiere algo más de configuración, otro proceso para funcionar y por esto no la elegí.
Cuando el procesamiento asíncrono es muy grande, ejemplo: encolar miles o millones de tareas diarías, monitoreo y control exahustivo de tareas realizadas, interfaz gráfica, recomiendo evaluar sidekiq o resque.
Cómo mencioné anteriormente en este capítulo utilizaremos la librería sucker_puch
para realizar dos simples tareas. Enviar un email de bienvenida y actualizar un contador que representa un campo en base de datos cada vez que alguien se meta en cierta página o post.
Instalación
Para instalar esta libreria basta con agregar a el Gemfile
de nuestra aplicación la siguiente línea:
1 gem'sucker_punch','~> 1.0'
Luego hacemos bundle install
. Cabe destacar que esta libreria no es dependiente de Rails, la podríamos utilizar en una aplicación Sinatra o Padrino.
Empleando la librería
Dentro de nuestra aplicación Rails, debemos generar un Mailer si es que no tenemos y configurarlo como aprendimos en la entrada Enviar Emails (Action Mailer) a su vez utilizaremos el mismo mensaje dentro del correo.
$ rails g mailer UserMailer1 $railsgmailerUserMailer
Luego debemos configurar todo como lo explica la entrada antes mencionada. Una vez que estemos seguros que es posible enviar correos agregamos sucker_punch
a la formula.
Para esto vamos a crear un directorio llamad jobs
dentro del directorio app
donde alojaremos el código para nuestro procesamiento asíncrono.
1 $mkdirapp/jobs
Luego crearemos un archivo llamado user_welcome_job.rb
dentro del directorio app/jobs
1 $touchapp/jobs/user_welcome_job.rb
Dentro de este archivo agregaremos lo siguiente:
class UserWelcomeJob include SuckerPunch::Job def perform(user) UserMailer.welcome_email(user).deliver end end1234567 classUserWelcomeJob includeSuckerPunch::Job defperform(user) UserMailerwelcome_email(user)deliver endend
Podemos apreciar lo siguiente: – include SuckerPunch::Job
esta llamada incluye las funcionalidades de la librería y hace posible el procesamiento síncrono y asíncrono. – método perform
se utiliza obligatoriamente y es el encargado del procesamiento.
Luego para poder llamar a este proceso y poder enviar el email debemos hacer la llamada desde el controlador o modelo en la acción create
de la siguiente manera.
1234567891011 defcreate @user=Usernew(params[:user]) if@uservalid? UserWelcomeJobnewasyncperform(@user) flash[:success]=Elcorreohasidoenviado! redirect_toroot_url else render:action=>'new' endend
Rails automaticamente incluye cualquier archivo que tenga extensión .rb
y se encuentre en el directorio app
por lo tanto podemos llamar directamente a la clase UserWelcomeJob
y crear una nueva tarea que se ejecutará asíncronamente al indicarle async
y llamará al método perform
pasando como parámetro un usuario. De igual manera podríamos ejecutar la tarea síncrona eliminando la llamada async
, UserWelcomeJob.new.perform(@user)
.
Para nuestro segundo caso: incrementar el contador de visitas a cierta página dentro de nuestra aplicación luego de 1 minuto. Por ejemplo: el perfil de un usuario.
Suponemos existe un campo en base de datos llamado
profile_view
cuya finalidad es almacenar esta información.
Lo primero que debemos hacer crear un archivo de “jobs” nuevo:
$ touch app/jobs/page_view_job.rb1 $touchapp/jobs/page_view_jobrb
En este archivo agregamos el siguiente código que nos permitirá actualizar la cantidad de visitas cada vez que ingrese alguien a una página.
class PageViewJob include SuckerPunch::Job workers 4 def perform(user_id) ActiveRecord::Base.connection_pool.with_connection do user = User.find(user_id) user.increment!(profile_view: 1) end end def later(sec, user_id) after(sec) { perform(user_id) } end end123456789101112131415 classPageViewJob includeSuckerPunch::Job workers4 defperform(user_id) ActiveRecord::Baseconnection_poolwith_connectiondo user=Userfind(user_id) userincrement!(profile_view:1) end end deflater(sec,user_id) after(sec){perform(user_id)} endend
En esta tarea podemos apreciar nuevos detalles.
workers 4
indica la cantidad de procesos ligeros “simultaneos” que se podrán crear para cumplir cierta tarea, el valor predeterminado es 2.- Cuando se modifica información con ActiveRecord debemos tener en cuenta el “pool” de conexiones a la base de datos. Para no agotar las conexiones utilizamos la siguiente línea de código
ActiveRecord::Base.connection_pool.with_connection
la cual permite que una vez finalizada la ejecución de dicha tarea la conexión retorne al “pool”. - método later se encarga de llamar al método
perform
luego de ciertos segundos, en nuestro caso 60segundos.
Dentro del controlador de usuarios en el método show
agregamos el siguiente código.
123 defshow PageViewJobnewasynclater(60,params[:id])end
De esta manera llamamos directamente al método later
asíncronamente y el mismo se encargará de llamar al método perfom
que se encargará de actualizar la información en base de datos.
Conclusión.
En esta lección aprendimos a utilizar una de las tantas herramientas de manejo de procesos asíncronos. Elegí esta librería ya que es muy facil de instalar y con ligeras modificaciones en nuestra aplicación podemos mejorar el tiempo de respuesta de la misma. Siéntanse libres en consultar cualquier duda a través de los comentarios.
¡Hasta el próximo capítulo!