Refactoring an Overgrown Notifications Class in Django
Tags: refactoring, django, python, technical
Published: 2018-01-25 21:58:45

---
title: "Refactoring an Overgrown Notifications Class in Django"
published: true
tags: refactoring,django,python,technical
canonical_url: 
cover_image: https://source.unsplash.com/IBWIy0tZ2Vw/1000x420
---

Photo by [Rajesh Appalla](https://unsplash.com/@ideazinfinite) on [Unsplash](https://unsplash.com/photos/IBWIy0tZ2Vw)

After working with the same code base for a while, there comes a time when a developer starts noticing ways that certain parts of it can be improved, both in terms of performance and in terms of code quality. Code also frequently evolves over time to keep up with changing requirements. Oftentimes, these changes can be greatly simplified. In the case of the notifications class in [NextTier](https://nexttier.com/)’s Django API, both were&nbsp;true.

The API handles the interactions of students with their college application tasks and their counselors. Students can find colleges and scholarships to apply to, take tests to help guide them towards a career and keep track of their college application process through tasks specifically tailored for each college. Counselors can follow up with a student’s progress directly through messages and verifying their dashboard, and indirectly through a system of notifications.

The notification sending class has gone through many changes over the past two years, needing to do more and more to keep up with evolving requirements. As we now have a much better idea of what we need in a notifications system, and are starting to feel performance slowdown, it was time to do a ruthless analysis of the notification sending class, SendNotificationMixin.

#### The Initial&nbsp;State

This is what the notification sending class looked like before the refactor:

{% gist https://gist.github.com/gabivoicu/4d1e2198c0ba25971184a08783382913 %}

The issues I identified and wanted to address with the refactor:

Arguments

- send\_notification takes in nine arguments, without counting the extra keyword arguments that can be passed in ( \*\*kwargs); this urgently needs to be improved as debugging this method is extremely hard. Trying to isolate which particular argument passed in causes an error or creates a specific piece of text in an email has proven extremely time consuming in the&nbsp;past.

Logs

- The method has logs that are very difficult to read and many unnecessary events that add noise to the&nbsp;logs.

Complexity

- The send\_notificationmethod code tries to do too many things, which makes it extremely convoluted.

The method is essentially in charge of creating three types of notifications:

1. Notification objects in the database (that serve for the web app center notifications)
2. Mobile push notifications
3. Emails

While most events that trigger notifications in the app require all three types of notifications, an increasing number do not, and new requirements for mobile push notifications will mean removing even more push notifications. Based on this I’m planning to split this method into three separate methods that will each handle the creation of one single type of notification, and be called accordingly by methods generated by each event. A method called by an event will be responsible for knowing which types of notifications it needs to&nbsp;create.

Inheritance

- view classes inherit from SendNotificationMixin. View classes are not notification classes, so inheritance does not make sense in this scenario. This will be replaced with composition. Though rare, there were a couple of times when inheritance has caused method overwrites.

Arguments (again)

- send\_notification needs to be called with a very particular set of arguments. Their creation takes up many lines of code inside the view class methods. These add noise and clutter to the views, and will be replaced with a call to a method from SendNotificationMixin that will know how to send the proper notifications for the event created in that&nbsp;view.

Performance

- Performance is a big issue for events that require many notifications to be sent, for example if a counselor creates a new task and assigns it to 100 students. From my testing, the problem is caused both by notification sending code and by the actual calls made to outside services. The solution will be two pronged: for the notification sending code, the refactor will also focus on reducing the time complexity of the code; for the calls to the email and push notification sending services, they will be moved to Django’s Channels, an asynchronous task processor.

An Excessively-large Object

- An entire Django Request object is passed in from the view methods all the way down to the email sending method to only use some of its properties in some of the methods. Outside of it being an unnecessary to send the entire object for only some properties, this also causes an issue for events that trigger notifications outside of API calls, where there is no HTTP request to pass&nbsp;along.

Inheritance (again)

- The class inherits from SendEmailMixin. While an argument could potentially be made about whether or not a notification sending class is a type of email sending class (and therefore inheritance would be an appropriate solution), SendNotificationMixin does not make use of any of the benefits inheritance brings. As long as the only method the notifications class calls from SendEmailMixin is send\_email, composition makes more sense. This will also help cleanup more view classes that inherit from the email sending&nbsp;class.

Standardization (or lack&nbsp;thereof)

- Initially the app was created by a consultancy, with multiple people working on it which resulted in there being a few different ways in which methods are called and data structures are created. This creates a lot of unnecessary complexity and makes debugging and writing new code more complicated than it needs to&nbsp;be.

#### Phase 1

In this first part of the refactor, I moved all the notification sending code out of the views, to make them smaller and easier to read. A typical notification sending view looked like this before the refactor:

{% gist https://gist.github.com/gabivoicu/e380ff4eea8a5f0f25e5975399562711 %}

This POST handles the creation of a completed achievement, sending notifications for that achievement and creating feed events. Lines 30 to 54 handle the sending of the notifications and none of that code needs to be at the top level, visible inside the view method. It will be replaced by a call to the send\_completed\_phase\_achievement\_notifications method that lives on the SendNotificationMixin class. Now the post method is a whole lot easier to&nbsp;read:

{% gist https://gist.github.com/gabivoicu/1cce2145c5db9e0edce985e657d18167 %}

(There are obviously other things that could be improved in post, but they are outside the scope of the notifications refactor so they will need to be addressed another&nbsp;time.)

I repeated this step for all of the other view classes, and created 22 new methods on SendNotificationMixin that handle the creation of notifications based on specific&nbsp;events.

{% gist https://gist.github.com/gabivoicu/ad58e1d7326539b32e42843281cfde0f %}

#### Phase 2

In this part, we get to remove the HTTP request object that gets passed to send\_notification and send\_email. Having to pass this object to methods that send notifications makes it really hard to send those notifications for events that happen outside of an HTTP request like a phase migration script, a file upload account creation, or other events. Removing request will also help simplify the methods sending notifications, ensuring they take fewer arguments.

Request is used by send\_email to generate a link for a user to click and log in. The object is passed through four or five separate methods to get&nbsp;here:

{% gist https://gist.github.com/gabivoicu/2a113ff4d969995b2a2a04c165a07e93 %}

We are now able to replace all instances in the code that look like&nbsp;this:

```
context = {
            'invitee': invitee,
            'link': the_right_host(request)
}
```

With a new environment variable that will hold the different values of the host for the different environments:

```
context = {
            'invitee': invitee,
            'link': settings.HOST_URL
}
```

With this change, send\_email no longer needs request passed in, which means send\_notification also no longer needs request passed in. This allows us to get rid of that object everywhere in the code base, resulting in a massive PR with 1,379 lines changed, 311 of them deleted entirely. Deleting code feels _good&nbsp;_😁.