Observer Synchronization
Synchronize multiple screens by having them all be observers to a shared area of domain data.
08 September 2004
This is part of the Further Enterprise Application Architecture development writing that I was doing in the mid 2000’s. Sadly too many other things have claimed my attention since, so I haven’t had time to work on them further, nor do I see much time in the foreseeable future. As such this material is very much in draft form and I won’t be doing any corrections or updates until I’m able to find time to work on it again.
In some applications you have multiple screens available that display presentations of a common area of data. If a change is made to the data through one of these screens, you want all the other screens to update correctly. However you don't want each screen to know about the others, because that would increase the complexity of the screens and make it harder to add new ones.
Observer Synchronization uses a single area of domain oriented data and has each screen be an observer of that data. Any change in one screen propagates to that domain oriented data and thence to the other screens. This approach was a large part of the Model View Controller approach.
How it Works
The essence of this approach is that each screen, with its associated screen state, acts as an Observer on a common area of session data. All changes to the session data result in events which the screens listen to and respond by reloading from the session data. Once you have this mechanism set up, then you can ensure synchronization simply by coding each screen to update the session data. Even the screen that makes the change doesn't need to refresh itself explicitly, since the observer mechanism will trigger the refresh in the same way as if another screen made the change.
Perhaps the biggest issue in this design is deciding what granularity of events to use and how to set up the propagating and observer relationships. At the very fine-grained level each bit of domain data can have separate events to indicate exactly what changed. Each screen registers for only the events that may invalidate that screen's data. The most coarse grained alternative is to use an Event Aggregator to funnel all events into a single channel. That way each screen does a reload if any piece of domain data changes, whether or not it would have affected the screen.
As usual the trade-off is between complexity and performance. The coarse-grained approach is much simpler to set up and less likely to breed bugs. However a coarse grained approach leads to lots of uneccessary refreshes of the screen, which might impact performace. As usual my advice is to start coarse grained and introduce appropriate fine-grained mechanisms only when needed after measuring an actual performance problem.
Events tend to be hard to debug since you can't see the chain of invocations by looking at the code. As a result it's important to keep the event propagation mechanism as simple as you can, which is why I favor coarse-grained mechanisms. A good rule of thumb is to think of your objects as layered and to only allow observer relationships between layers. So one domain object should not observe another domain object, only presentation objects should observe domain objects.
Another thing to be very wary of is chains of events, where one event causes another event to fire. Such event chains can quickly get very hard to follow because you can't understand the behavior by looking at the code. As a result I tend to discourage events within a layer and prefer either a single line of events, or going through an Event Aggregator.
When an observer registers for an event, you get a reference from the subject to the observer. If the observer doesn't remove itself from the subject when you get rid of the screen, you have a zombie reference and a memory leak. If you destroy and create domain data with each session and your session is short, this may not lead to a problem. However a long lived domain object could lead to a serious leak.
When to Use It
Observer Synchronization is a particularly vital pattern when you have multiple active windows that share common data. In this situation the alternative, having windows signal each other to refresh, gets quite complicated as each window needs to know about other windows and when they are likely to need a refresh. Adding new windows means updating the information. With this approach adding new windows is very straightforward and each window can be setup to maintain its own relationship with the common domain data.
The principal disadvantage of Observer Synchronization is the implicit behavior triggerred by events which is difficult to visualize from the code. As a result bugs in event propagation can be very hard to find and fix.
Observer Synchronization can also be used with simpler navigational styles, although in these cases Flow Synchronization is a reasonable alternative. The value of Observer Synchronization may not outweigh the complexity introduced by using events for updates.
Further Reading
This pattern is obviously very similar to Observer and is a central part of Model View Controller. I see the main difference between this and observer is that this is a particular use of observer - albeit the most common one. I see this part of several patterns that make up Model View Controller.
Acknowledgements
Patrik Nordwall pointed out the problem with observers and memory leaks.
Example: Albums and Performers (C#)
Figure 1: Screens to display albums and performers.
Consider an application like Figure 1. We have active screens to edit names of performers and the albums they appear from. If I edit the title of the album “Kind of Blue” I want my edits to appear not just in the text box and form title of the album screen, but also in the list entries for Miles Davis and John Coltrane.
Figure 2: Domain objects for album and peformer.
In this case I'm holding the domain data in a couple of simple domain objects for performers and albums Figure 2. When I change the data in one of these classes I need to propagate an event.
class Album : DomainObject
public string Title { get { return _title; } set { _title = value; SignalChanged(); } } string _title;
class DomainObject...
public void SignalChanged() { if (Changed != null) Changed (this, null); } public event DomainChangeHandler Changed;
public delegate void DomainChangeHandler (DomainObject source, EventArgs e);
Here I just have a simple changed event defined in a Layer Supertype. This event doesn't give any information about the change, just that some change has occurred - suitable for a coarse-grained synchronization from the client.
In the form, I create a new album form by passing in an album. The constructor does its usual gui stuff, sets the album reference, wires up the event listeners and finally loads data from the album.
class FrmAlbum...
public FrmAlbum(Album album) { InitializeComponent(); this._album = album; observeDomain(); load(); } private Album _album; private void observeDomain() { _album.Changed += new DomainChangeHandler(Subject_Changed); foreach (Performer p in _album.Performers) p.Changed +=new DomainChangeHandler(Subject_Changed); } private void Subject_Changed(DomainObject source, EventArgs e) { load(); }
The event listening is wired up so that any change in a dependent domain object causes the form to reload its data.
class FrmAlbum...
private void load() { txtTitle.Text = _album.Title; this.Text = _album.Title; lstPerformers.DataSource = performerNames(); } private string[] performerNames() { ArrayList result = new ArrayList(); foreach (Performer p in _album.Performers) result.Add(p.Name); return (string[]) result.ToArray(typeof (string)); }
If I change the title in the album, I make that change directly on the underlying domain object.
class FrmAlbum...
private void txtTitle_TextChanged(object sender, EventArgs e) { this._album.Title = txtTitle.Text; }
You could acheive much of this using data binding as long as each form can participate in the binding with the shared domain objects. Another variation would be to use an Event Aggregator - this would allow each form to register only with the aggregator and not to have to register with each domain object. In the absence of performance issues, I would do it that way - I didn't here as I prefer to keep examples as independent of each other as possible.
If you're using Supervising Controller or Passive View the controller would act as the observer of the domain events. If you're using Presentation Model then the Presentation Model would act as the observer.