“To understand is to perceive patterns” — Isaiah Berlin
Design patterns help you solve common problems and keep your code maintainable, and extensible.
The observer pattern is one such pattern.
A set of observers keep watching a change in an object. On any change in the object, the observers are notified of the change. This pattern is used when a change in an object should trigger changes in the observing objects.
In the case of web applications, all state is maintained in the database entries and tables. There are many scenarios when you need to post-process an entry or trigger a job on data change in a table. The observer pattern is used for this purpose.
Observer pattern
Essentially what happens is observers subscribe to changes in the subject. When there is a change in the subject it notifies the observers. Observers subscribe to these events to take actions based on specific changes in the subject.
Once the observers receive this notification, they take the action they are supposed to take.
There are many ways this pattern can be implemented. Here are the most common ways I have personally witnessed —
Many web frameworks have this pattern built-in or there are libraries that bring in its functionality. You can simply add observers in model code that get triggered on record creation, update, or deletion.
A typical observer in web frameworks looks like this. Example shows how Ruby on Rails provides the feature to implement observers.
Drawbacks — As the project grows it becomes hard to maintain the observers. You tend to create multiple services and applications. What if a service needs to trigger a job in another service, inbuilt observer patterns can complicate the code. And keeping track of observers in all projects can be a nightmare.
This is the most common approach I have seen programmers take, primarily because of time constraints. A simple background job is run to scan through the database tables and based on the changes the observer code is run either synchronously or asynchronously.
Drawbacks — This approach is an expensive one. Database scans take time and since the jobs are run periodically the changes done by these jobs are delayed. Moreover, these jobs become a single point of failure.
Observer pattern using transaction logs of DB
Transaction logs are generated by the database master. These logs help the slave to catch up with the changes. In this design, DB transaction logs are read to find changes in various tables, and observers are triggered based on the type of events. Queues can be used to solve the producer-consumer problem.
There are many advantages to this design, here are a few of them —
We had a spaghetti of code that was triggering observers in different web services. Implementing this scheme took us some time but it was worth it. We use this pipeline to denormalize objects, maintain audit logs, push data to analytics, etc.