I’m currently designing a complex website that needs to support Undo and Notifications for CRUD operations.
By Undo, I mean that the user may create/modify/delete an item and then decide to cancel what he did (therefore removing the created item, restoring the previous state of the modified item, or bringing back the deleted item). He can do so for a short while after doing that operation (the timeout is 2 minutes, because the system is fairly complex and it may take some time for the user to notice that the modification was incorrect).
By Notifications, I mean that when an item is created/modified/deleted, other users that are interested in that item will receive an on-site notification (the next time they load a new page) and will also receive an e-mail or SMS notification if they asked for it.
Getting either feature done is simple: Undo involves keeping undo information around (old version of modified or deleted items, mostly) while Notifications are as simple as pushing a notification object into the appropriate tables and sending an e-mail/SMS on the fly.
Getting them both to work together creates a lot of additional complexity because Notifications must remain in stasis until the Undo becomes impossible, and I don’t have enough hindsight on the matter to design it in a sane way.
What would be patterns, best practices or pitfalls to avoid when building such a system?
Based on a bit of experimenting both in thought and in code, I managed to extract a series of principles.
Based on these principles, I decided to architecture two distinct layers on top of the data access layer:
This way, operations always pass through the basic set of operations and thus trigger notifications that are agnostic of whether they are caused by canceling an earlier operation or running its inverse operation manually — the system cleverly handles multiple notifications about the same object by grouping them together and letting them cancel each other out.
Conversely, the “undo” facilities merely call the code from the semantic layer, without requiring any knowledge about the notifications being sent out.
Obviously, there will still be a little amount of coupling, because it sometimes is necessary to add a semantic operation only because it’s the “undo” inverse of an existing operation. For instance, one can cancel the creation of a discussion, but cannot delete the message once it is sent (because there might be replies on it). So, a
cancelDiscussionfunction might exist where adeleteDiscussionis unavailable. I suspect such situations are rare enough to safely accept.