Notification Architecture - wordjelly/Auth GitHub Wiki
Notification Architecture
Requirements:
- In the configuration file : mailer_class name
- A model that implements the Auth::Concerns::NotificationConcern
- A model that implements the Auth::Concerns::NotificationResponseConcern
- In the configuration file : notification class name
- 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.
- 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.
- Next, for each resource in the resources obtained above, it tries to send the notification by email, sms, mobile and desktop.
- For this , methods are provided in the notification concern, which return true of false for each of the above.
- 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
- 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.)
- 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:
- parent_notification_id : a string id of the parent notification
- webhook_identifier: a string identifier with which this notification response can be tracked using
- responses: an array, to hold the string versions of json objects that constitute the responses from sending the notification
- 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:
- 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.
- 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:
- 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'
- 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.
- After the block is called(and thus after the notification_response object is saved), then email.deliver_now is called.