All programs need to react to events. When X happens, do Y. Even the most trivial “Hello, World” application prints its output in response to the “program was started” event. And as our programs gain features, the number of events they need to respond to (such as button clicks, or incoming network messages) grows, meaning the strategy we use for handling events will have a big impact on the overall maintainability of our codebase.
In this post I want to compare four different ways you can respond to a simple event. For our example, the event is a “Create User” button being clicked in a Windows Forms application. And in response to that event our application needs to do the following things:
- Ensure that the entered user data is valid
- Save the new user into the database
- Send the user a welcome email
- Update the GUI to add the new user to a ListBox of users
Approach 1 – Imperative Code
This first approach is in one sense the simplest. When the event happens, just do whatever is needed. So in this example, right in the button click handler, we’d construct whatever objects we needed to perform those four tasks:
private void buttonCreateUser_Click(object sender, EventArgs e) { // get user var user = new User() { Name = textBoxUserName.Text, Password = textBoxPassword.Text, Email = textBoxEmail.Text }; // validate user if (string.IsNullOrEmpty(user.Name) || string.IsNullOrEmpty(user.Password) || string.IsNullOrEmpty(user.Email)) { MessageBox.Show("Invalid User"); return; } // save user to database using (var db = new SqlConnection(@"Server=(localdb)\v11.0;Initial Catalog=EventAggregatorDemo;Integrated Security=true;")) { db.Open(); using (var cmd = db.CreateCommand()) { cmd.CommandText = "INSERT INTO Users (UserName, Password, Email) VALUES (@UserName, @Password, @Email)"; cmd.Parameters.Add("UserName", SqlDbType.VarChar).Value = user.Name; cmd.Parameters.Add("Password", SqlDbType.VarChar).Value = user.Password; cmd.Parameters.Add("Email", SqlDbType.VarChar).Value = user.Email; cmd.ExecuteNonQuery(); } // get the identity of the new user using (var cmd = db.CreateCommand()) { cmd.CommandText = "SELECT @@IDENTITY"; var identity = cmd.ExecuteScalar(); user.Id = Convert.ToInt32(identity); } } // send welcome email try { var fromAddress = new MailAddress(AppSettings.EmailSenderAddress, AppSettings.EmailSenderName); var toAddress = new MailAddress(user.Email, user.Name); const string subject = "Welcome"; const string body = "Congratulations, your account is all set up"; var smtp = new SmtpClient { Host = AppSettings.SmtpHost, Port = AppSettings.SmtpPort, EnableSsl = true, DeliveryMethod = SmtpDeliveryMethod.Network, UseDefaultCredentials = false, Credentials = new NetworkCredential(fromAddress.Address, AppSettings.EmailPassword) }; using (var message = new MailMessage(fromAddress, toAddress) { Subject = subject, Body = body }) { smtp.Send(message); } } catch (Exception emailException) { MessageBox.Show(String.Format("Failed to send email {0}", emailException.Message)); } // update gui listBoxUsers.Items.Add(user.Name); }
What’s wrong with this code? Well multiple things. Most notably it violates the Single Responsibility Principle. Here inside our GUI we have code to talk to the database, code to send emails, and code that knows about our business rules. This means it’s going to be almost impossible to unit test in its current form. It also means that we’ll likely end up with cut and paste code if later we discover that another part of our system needs to create new users, because that code will need to perform the same sequence of actions.
The reason we’re in this mess is that we have tightly coupled the code that publishes the event (in this case the GUI), to the code that handles that event.
So what can we do to fix this? Well, if you know the “SOLID” principles, you know its always a good idea to introduce some “Dependency Injection”. So let’s do that next…
Approach 2 – Dependency Injection
What we could do here is create a few classes each with a single responsibility. A UserValidator validates the entered data, a UserRepository saves users to the database, and an EmailService sends the welcome email. And we give each one an interface, allowing us to mock them for our unit tests.
Suddenly, our create user button click event handler has got a whole lot more simple:
public NewUserForm(IUserValidator userValidator, IUserRepository userRepository, IEmailService emailService) { this.userValidator = userValidator; this.userRepository = userRepository; this.emailService = emailService; InitializeComponent(); } private void buttonCreateUser_Click(object sender, EventArgs e) { // get user var user = new User() { Name = textBoxUserName.Text, Password = textBoxPassword.Text, Email = textBoxEmail.Text }; // validate user if (!userValidator.Validate(user)) return; // save user to database userRepository.AddUser(user); // send welcome email const string subject = "Welcome"; const string body = "Congratulations, your account is all set up"; emailService.Email(user, subject, body); // update gui listBoxUsers.Items.Add(user.Name); }
So we can see we’ve improved things a lot, but there are still some issues with this style of code. First of all, we’ve probably not taken DI far enough. Our GUI still has quite a lot of knowledge about the workflow of handling this event – we need to validate, then save, then send email. And so probably we’d find ourselves wanting to create another class just to orchestrate these three steps.
Another related problem is the construction of objects. Inversion of Control containers can help you out quite a bit here, but it’s not uncommon to find yourself having classes with dependencies on dozens of interfaces, just because you need to pass them on to child objects that get created later. Even in this simple example we’ve got three dependencies in our constructor.
But does the GUI really need to know anything whatsoever about how this event should be handled? What if it simply publishes a CreateUserRequest event and lets someone else handle it?
Approach 3 – Raising Events
So the third approach is to take advantage of .NET’s built-in events, and simply pass on the message that the CreateUser button has been clicked:
public event EventHandler<UserEventArgs> NewUserRequested; protected virtual void OnNewUserRequested(UserEventArgs e) { var handler = NewUserRequested; if (handler != null) handler(this, e); } private void buttonCreateUser_Click(object sender, EventArgs e) { var user = new User() { Name = textBoxUserName.Text, Password = textBoxPassword.Text, Email = textBoxEmail.Text }; // send an event OnNewUserRequested(new UserEventArgs(user)); } public void OnNewUserCreated(User newUser) { // now a user has been created, we can update the GUI listBoxUsers.Items.Add(newUser.Name); }
What’s going on here is that the button click handler now does nothing except gather up what was entered on the screen and raise an event. It’s now completely up to whoever subscribes to that event to deal with it (performing our tasks of Validate, Save to Database and Send Email), and then they need to call us back on our “OnNewUserCreated” method so we can update the GUI.
This approach is very nice in terms of simplifying the GUI code. But one issue that you can run into is similar to the one faced with the dependency injection approach. You can easily find yourself handling an event only to pass it on to the thing that really needs to handle it. I’ve seen applications where an event is passed up through 7 or 8 nested GUI controls before it reaches the class that knows how to handle it. Can we avoid this? Enter the event aggregator…
Approach 4 – The Event Aggregator
The event aggregator completely decouples the code that raises the event from the code that handles it. The event publisher doesn’t know or care who is interested, or how many subscribers there are. And the event subscriber doesn’t need to know who is responsible for publishing it. All that is needed is that both the publisher and subscriber can talk to the event aggregator. And it may be acceptable to you to use a Singleton in this case, although you can inject it if you prefer.
So in our next code sample, we see that the GUI component now just publishes one event to the event aggregator (a NewUserRequested event) when the button is clicked, and subscribes to the NewUserCreated event in order to perform its GUI update. It needs no knowledge of who is listening to NewUserRequested or who is publishing NewUserCreated.
public CreateUserForm() { InitializeComponent(); EventPublisher.Instance.Subscribe<NewUserCreated> (n => listBoxUsers.Items.Add(n.User.Name)); } private void buttonCreateUser_Click(object sender, EventArgs e) { // get user var user = new User() { Name = textBoxUserName.Text, Password = textBoxPassword.Text, Email = textBoxEmail.Text }; EventPublisher.Instance.Publish(new NewUserRequested(user)); }
As you can see, this approach leaves us with trivially simple code in our GUI class. The subscribers too are simplified since they don’t need to be wired up directly to the class publishing the event.
Benefits of the Event Aggregator Approach
There are many benefits to this approach beyond the decoupling of publishers from subscribers. It is conceptually very simple and easy for developers to get up to speed on. You can introduce new types of messages easily without making changes to public interfaces. It’s very unit testing friendly. It also discourages chatty interactions with dependencies and encourages a more asynchronous way of working – send a message with enough information for the handlers to deal with it, and then wait for the response, which is simply another message on the event bus.
There are several upgrades to a vanilla event aggregator that you can create to make it even more powerful. For example, you can give subscribers the capability to specify what thread they want to handle a message on (e.g. GUI thread or background thread). Or you can use WeakReferences to reduce memory leaks when subscribers forget to unsubscribe. Or you can put global exception handling around each callback to a subscriber so you can guarantee that when you publish a message every subscriber will get a chance to handle it, and the publisher will be able to continue.
There are many situations in which the event aggregator really shines. For example, imagine you need an audit trail tracking many different events in your application. You can create a single Auditor class that simply needs to subscribe to all the messages of interest that come through the event aggregator. This helps keep cross-cutting concerns in one place.
Another great example is a single event that can be fired from multiple different places in the code, such as when the user requests help within a GUI application. We simply need to publish a HelpRequested message to the aggregator with contextual information of what screen they were on, and a single subscriber can ensure that the correct help page is launched.
Where Can I Get An Event Aggregator?
Curiously, despite the extreme usefulness of this pattern, there doesn’t seem to be an event aggregator implementation that has emerged as a “winner” in the .NET community. Perhaps this is because it is so easy to write your own. And perhaps also because it really depends what you are using it for as to what extensions and upgrades you want to apply. Here’s a few to look at to get you started:
- Udi Dahan’s Domain Event pattern
- Uses an IDomainEvent marker interface on all messages
- Integrates with the container to find all handlers of a given event
- Has a slightly odd approach to threading (pubs and subs always on the same thread)
- José Romaniello’s Event Aggregator using Reactive Extensions
- A very elegant and succinct implementation using the power of Rx
- Subscriptions are Disposable
- Use the power of Rx to filter out just events you are interested in, and handle on whatever thread you want
- Laurent Bugnion’s Messenger (part of MVVM Light)
- Uses weak references to prevent memory leaks
- Can pass “tokens” for context
- Includes passing in object as the subscriber, allowing you to unsubscribe from many events at once
- Allows you to subscribe to a base type and get messages of derived types
- PRISM Event Aggregator (from Microsoft patterns and practices)
- A slightly different approach to the interface – events inherit from EventBase, which has Publish, Subscribe and Unsubscribe methods.
- Supports event filtering, subscribing on UI thread, and optional strong references (weak by default)
I’ve made a few myself which I may share at some point too. Let me know in the comments what event aggregator you’re using.
No comments:
Post a Comment