A Notification Handling System
Description and Specifications:
The goal was to build a notification system that allows users to subscribe to changes to a variety of entities in a tracking system consisting of project groups, deliverable items, and tasks that are assigned for the deliverables. Users should be able to subscribe to receive email notification of relevant changes to one or more discrete items at any of these tiers. Additionally, users should be able to specify a notification frequency from a range of discrete options such as "weekly", "daily", "hourly", "immediately", etc.
Challenges and Solutions:
Single-Rule Subscription to Multiple Entity ID References
Because a notification 'rule' refers to a particular type of notification (e.g. change to a project group's details, or editing a task or deliverable), the user/rule pair could not represent a unique key. It was conceivable (and likely) that a particular user_id/rule_id combination might occur more than once if, for example, a user subscribed to notifications each time two or more different groups were updated. However, good database normalization practice dictated that columns such as 'group_id' or 'item_id' should not be included in the subscriptions table (see the ERD to the right), since they would only be filled in certain cases, and irrelevant in others. For this reason, a SubscriptionLink table was used to contain links back to specific subscriptions, adding information about the specific entity ID (e.g. a group or item ID) that was being subscribed to. The advantage to this table is not only that a variety of entity IDs can be references, but also that multiple columns could be used if ever needed, not necessarily only foreign keys to entity IDs.
Notification Timing and Intervals
When planning how to structure notification timing, we decided to specify discrete options such as "weekly", "daily", and so on. With this in mind, we had two choices: a single cron job that would run frequently and look for relevant changes over a variety of time intervals, or one cron job per time interval.
We ultimately chose the former. A single script running on regular intervals would select all subscribers and all subscriptions, compile arrays of the subscriptions for each user, run the rule for each if it hadn't been run recently within its specified interval, compile a notification email containing pertinent information about changes since the last notification sent out to that user for that rule, and then send off a single email containing all of the notifications before proceeding to the next subscriber's notification array.
To avoid overlapping script runs (i.e. if the script took more than one minute to run), 'flock' was used in the shell script that was run by the command line. This pass-through command creates a temporary lock file while running the specified script, and - in turn - only runs if the lock file is not currently in place:
( flock -s 200 php /path/to/cron/script/ourcronjob.php ) 200>/var/lock/mylockfile
Rule-Specific Notification Triggers and Algorithms
The system was programmed in a highly object-oriented structure, including classes for notifications (corresponding to the NotificationLog table), subscriptions and other objects corresponding to the tables used in the database structure. Typical save() and load() methods were implemented on these classes, and then a separate NotificationService class was implemented to include more complex methods used in handling sets of tasks such as checking if a subscription to a particular entity ID already existed, and saving a new one if not. However, because the algorithms for determining which notifications to include in a mailing could vary so widely from rule to rule, a Worker Bee pattern was used to produce subclasses that extended the overall NotificationRule class in order to specify a concrete process() method that should be used to compile an array of notification messages to be included in each mailing to each user for a given rule. The mailings, in turn, would then contain all relevant notifications. For example, if a mailing went out on Friday at noon, the user might get all group 1 notifications for the past week along with all group 2 notifications over the past day. At noon on any other day, perhaps only group 2 notifications would be sent out for the previous day's changes.