Offering Services

Due to Django's constraints and need to iterate fast on a product, business logic tends to sneak into views and models. These trends lead to spaghetti code, concerns not being properly separated and inability to write solid OOP/functional code.

While this is fine when a project is starting out, these issues create technical debt that will grow exponentially over time. The more technical debt you have the harder it is to add and change things.

I found that the easiest way to reduce technical debt is to slowly start moving things into other "buckets". One of those buckets is a services bucket.

What is a service you ask? Great question! Here is a basic explanation of it:

A service describes a system interaction.

Also, a service can have more than one business model interacting with each other.

Here is an example of a method in a view that has too many things in it:

# imagine this is an app that collects taxes
def perform_update(self, serializer):  
    user = self.get_object()
    if user.role == 'tax_collector':
        user.tax_deduction = 0
        user.title = 'Awesome Tax Collector'
    elif user.role == 'company':
        user.tax_deduction = 50
        user.title = 'Company Rep'
    elif user.role == 'contractor':
        user.tax_deduction = 25
        user.title = 'Individual Contractor'

   user.save()
   return Response(dict(), status=status.HTTP_200_OK)

Now imagine we have a folder named your_app/services/ and in that folder there is a tax_deducter.py that looks something like this:

class TaxDeducter:  
    def __init__(self, user):
        self.user = user

    def deduct_from_user(self):
        if user.role == 'tax_collector':
            user.tax_deduction = 0
            user.title = 'Awesome Tax Collector'
        elif user.role == 'company':
            user.tax_deduction = 50
            user.title = 'Company Rep'
        elif user.role == 'contractor':
            user.tax_deduction = 25
            user.title = 'Individual Contractor'

        return user

Purpose of a TaxDeducter is to figure out what type of user do we have and what an appropriate tax deduction will be. In addition to that, we also set a title to know from whom are we collecting taxes from.

Now our view will look something like this:

def preform_update(self, serializer):  
    user = self.get_object()
    td = TaxDeducter(user)
    user = td.deduct_from_user()
    user.save()
    return Response(dict(), status=status.HTTP_200_OK)

It looks a lot cleaner doesn't it? :) Now I do realize code above is not perfect and has flaws but it does get the point across.

Hopefully, this will help you in your journey of reducing technical debt and being a happier engineer!

Nikola Novakovic

Software engineer that loves business side of things as well. Also lifts a lot of weights.

Subscribe to get regular updates