The main idea here is compose shared functionality into a pipeline of functions that all implement some shared behaviour.
defmodule Notification.Event do
# The event (probably a bad name) is where you would put the structified JSON event you got from RabbitMQ
defstruct [:sent_at, :user, :event]
end
defmodule Notification.Plug do
@callback init(opts) :: opts
@callback call(Notification.Event.t) ::
:skip # halts processing early if we should not send anything
| Notification.Event.t # continue processing with an updated Notification.Event.t
| {:send_email, Notifications.Email.t, Notification.Event.t} # deliver an email to the user and continue processing with the new Notifiction.Event.t
| {:send_sms, Notifications.SMS.t, Notification.Event.t} # deliver an sms to the user and continue processing with the new Notifiction.Event.t
end
defmodule Notifications.MattressShipped do
plug CheckIfUserUnsubscribed
plug CheckIfUserOptedIn, default: true
plug SendMattressShippedEmail
plug SendMattressShippedSMS
end
This lets us compose a different set of functionality for each type of event and it makes it easy for the team supporting this code to answer questions like, "is this notification opt-in or opt-out? Does it check for unsubscribed users?".