As with really anything in programming it can be done a million ways. Some ways are better than others but there is no golden ticket. In my experience, an acceptable solution could be by way of a service object, business object, DAO, ORM (no brainer here) or a combination of all of the above depending on how abstract you want to get. In this example I'll walk you through how I solved this problem using the tried and true Observer Design Pattern.
The Observer Pattern was created by the Gang of Four (GoF) - Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides. Long story short back in 95' these very smart guys identified 23 recurring software problems, designed solutions for each called a "design pattern" (which borrowed from proper architect Chris Alexander) and released a book to school us on it. If you don't own this book you definitely should, shame on you. Familiarize yourself with the problems and patterns the GoF describe and lock it away in your brain cache. When time comes, you'll be well equipped to tackle some tough design problem thrown at you. Cool? Good. Here is the definition of the observer pattern:
Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.So given the composite save problem at hand, I looked to design patterns to see if I could use one as a viable solution. I think I was on a long flight to Boston so I spent the entire flight sifting through design patterns and with the help of the book and a beer or two, I was pretty sure the observer pattern would be a good match. Thinking of the problem at a high level we have a possible one-to-many relationship between an object and its composites that needed behavior between the two such that when an object is saved, it needs to notify the composite objects who are waiting intently to save themselves but cannot before that initial object is saved successfully. For the purposes on this blog entry I've created a couple class diagrams that explain the design, then we'll jump into some code. Here's the high level/abstract:
[click to view full image]
- IObservable - Any class that needs to be observable must implement this interface. It contains methods to attach() as a observer, detach() an observer and to notify() its observers of an event. Simply put, if you think of a pub/sub technique, an object implementing IObservable will be the publisher.
- IObserver - Any class that can attach itself as an observer to an an object that implements IObservable must implement this interface. It has the API that IObservable will call when it notifies its observers.
Now here is the UML with concrete implementations:
[click image for full view]
- We have a Person that has an Address - here's our composite relationship.
- It's a one to many relationship. A Person can have multiple Addresses.
- Private member - observers - is of type IObservable. Whats the brackets for?
- There is a save() method is each object. To keep things simple just imagine that save() is delegating to a DAO or using some snazzy ActiveRecord pattern to actual persist the record. Bottom line is don't worry about it for this example.
- Person is observable by way of implementing IObservable. That little symbol with the interface name above each class is called a "lollipop", no joke. It means this class means "implements" whatever name is shown.
- Address is an observer by implementing IObserver.
- addEmail() has an optional second parameter "attachAsObserver" that is default = true. Remember this, you'll see how this plays into a clean API when using this pattern.
A concrete implementation of Person might look like this:
[click image for full view]You'll notice that:
- attach() will push an observer into the private subscribers array. This method is responsible for allowing observers subscribe to it (Person).
- notify() loops over all observers and invokes update("some_event") on the observer that tells the observer, "hey dude, just fired "some_event". In the same breathe it is also inspecting the result of notify() that sniffs for a return of false which means something went wrong so return false and stop notify execution. I know that there are possible scenarios where you would not want to stop processing and move along but exploring that is out of the scope of this example. Although if you go ahead and re-factor this example to do this I'd love to see it!
- We've overridden addAddress() which manages our address collection. This is essentially the setter for Address but called it add instead of set because of the one-to-many relationship. Notice that the we've added a second parameter to this method called attachAsSubscriber that has a default = true so its optional. In the logic, we're saying if attasAsObserver is true then automatically attach the Address as an observer to Person.
- save() calls super.save(). Make believe Person extends some base persistence object.
[click image for full view]Here you'll notice that:
- update() will receive the event message and process accordingly. Please do not confuse this with a CRUD operation. As a best practice in you database layer there really shouldn't even be a method called update(), rather call it save(). Anyway, what update() will do is inspect the event that was just fired by IObservable and if it is a "save" event, then return save(). This is a very important piece right here! The return of save() gets bubbled up to the observer. This will ultimately determine the rollback which we'll get to in a second. You can optionally override Address.save() also do the same algorithm as Person.save() if Address also needed to be observable and notify its own observers.But thats up to your requirements. Just want to drive the point home that pattern can be implemented n-levels deep. D observes C who observes B who observes A. A = mack daddy.
- update() by default will return true.
- update() uses the eventInfo structs observable key to get the Person that was just persisted. This allows Address to save() correctly. I chose to go with a struct for eventInfo because an event might be fired in the future that needed to pass a handful of additional info to its observers. This allows for future flexibility.
[click image for full view]
Here we have:
- Created a new Person and Address.
- Associated the Address to the Person. Under the hood this will also attach Address as an observer to Person but the API remains simple. Looks just like a simple setter - addAddress(in Address). It has no idea the observer pattern is going on in the background.
- We are simply making the entire save process transactional and when an observer fails to save or an exception is thrown, we handle it accordingly by rolling everything back. You could have 100 objects in the queue to be save()'d, fail on save() #99 and it'll nicely rollback everything for you.
- And now since we've kept the Observer pattern extremely generic, you can feel free to leverage this implementation for any other event, not just saving.
So in closing, this is just one technique of many so I encourage you to explore some other ways and please point me to ways you've done it in the past. I'm interested in seeing it!
Apologies in advance for any bugs in this code. Everything was typed on the fly but hopefully you get the idea.
Till next time my friends.
Design Patterns with C#
Christopher Alexander, founder of the design pattern in architecture.
GoF, you owe it to them so buy their book