Notification Architecture - wordjelly/Auth GitHub Wiki

Notification Architecture

Requirements:

  1. In the configuration file : mailer_class name
  2. A model that implements the Auth::Concerns::NotificationConcern
  3. A model that implements the Auth::Concerns::NotificationResponseConcern
  4. In the configuration file : notification class name
  5. In the configuration file : notification_response class name

Entry Point

The entry point for all notifications is a module in the auth/lib directory called notify.rb It has just one class method called send_notification(notification). Here notification should be a model that implements the Auth::Concerns::NotificationConcern So from any point in your app, you can directly call

Auth::Notify.send_notification(notification_instance)

Here the send_notification method, simply calls

notification_instance.send_notification

After this the rest of the handling is done by the notification_concern.

  1. send_notification first calls send_to, which returns a hash, this hash contains the resources to which the notification should be sent. Normally while creating the notification object, the resources are already defined.
  2. Next, for each resource in the resources obtained above, it tries to send the notification by email, sms, mobile and desktop.
  3. For this , methods are provided in the notification concern, which return true of false for each of the above.
  4. Described below is the method by which notifications are sent for each type of sending method, i.e email,sms, mobile and desktop

Sms Notification

  1. Calls send_sms_background, which normally will call send_sms directly. send_sms_background should be overridden in your notification object if you want to send the sms by a background job(basically call send_sms inside the background job.)
  2. send_sms looks like this:
    def send_sms(resource)
        if self.sms_send_count < max_retry_count
	    self.sms_send_count+=1
	    create_notification_response(yield,"sms")
	    self.save
        end
    end

It checks if the sms_send_count is greater less than the max_retry_count(this can be overriden in your notification concern to suit your needs), and then calls create_notification_response.

send_sms is expected to be called as follows, with a block:

notification.send_sms do 
   ## expected to return a string which is a valid json object.
   ## basically the yield , yields whatever is the return value of this block.
   ## if send_sms is not called with a block, then nil will be added to the notification response.
end

create_notification_response looks like this:


def create_notification_response(response,type)
		notification_response = Auth.configuration.notification_response_class.constantize.new(notification_type: type, parent_notification_id: self.id.to_s)
		notification_response.add_response(response)
		notification_response.set_webhook_identifier(response)
		notification_response.save
end

It checks the configuration for a notification response class, instantiates a new notification_response object, and then adds the #response to that notification response object.

After that it calls:

notification_response_object.set_webhook_identifier(response)

This method is provided by the notification_response_concern. It should be overridden by whatever object implements this concern.

In the case of the dummy app, that object is NotiResponse. And the method is overridden as follows:

def set_webhook_identifier(response)
	Auth::Mailgun.set_webhook_identifier(self,response)
	Auth::TwoFactorOtp.set_webhook_identifier(self,response)
end

Both the above are modules provided by the engine, both have a method called set_webhook_identifier, which then takes the response (2nd argument) and sets the webhook_identifier field on the notification_response object(1st argument 'self')

Finally the last line in the block above is:

notification_response.save

So the notification_response object is finally saved after setting the webhook_identifier.

Notification Response Object and Concern.

It is necessary to create a model that implements the notification_response concern. The function of this object is a way to keep track of the notifications sent, and acknowledge their send success or failure by means of webhooks.

The notification_response object has four attributes:

  1. parent_notification_id : a string id of the parent notification
  2. webhook_identifier: a string identifier with which this notification response can be tracked using
  3. responses: an array, to hold the string versions of json objects that constitute the responses from sending the notification
  4. notification_type: a string that tells the type of notification , it can be 'sms','email','mobile' or 'desktop'

Flow for Email Notification:

This is exactly the same as for Sms.

send_email_background -> calls send_email, and should be overridden to call send_email inside a background job, just like for sms.

However there are two issues:

  1. send_email is called in a background job because it did not load the mailgun_variables when using deliver_later , so instead do it in a background job.
  2. the code in the background job is as follows:
        notification = params[:notification_class].capitalize.constantize.find(params[:notification_id])
        email = Auth.configuration.mailer_class.constantize.notification(resource,self)
        email = add_webhook_identifier_to_email(email)
      
        notification.send_email(resource) do 
          JSON.generate(email.message.mailgun_variables)      
        end
      
        email.deliver_now

Above:

  1. the add_webhook_identifier_to_email is a method called from the Auth::Mailgun module. Where it adds to the email.message.mailgun_variables which is a hash, a key called 'webhook_identifier' => 'Bson::ObjectId'
  2. the next line notification.send_email(resource) is similar to send_sms , i.e it is called with a block and follows the same logic as the send_sms wrt the create_notification_response. The block here just returns the hash of the mailgun_variables as a JSON string, and this is stored as the yield value in the notification_response object.
  3. After the block is called(and thus after the notification_response object is saved), then email.deliver_now is called.

Webhooks Architecture