Make Your Django Project Less Confusing with Design Pattern

Widyanto H Nugroho
7 min readMar 21, 2022

--

New features are added to the software every time and are part of the software development process. As a programmer, we generally want to change as little existing code as possible while also easily integrating that new feature into the existing code. A design pattern is a proven and tested solution for a specific software design problem. Luckily, there is a design pattern for most software design problems. In this article, I will give examples of how I apply three design patterns for my project. Note that this is not an article about learning design patterns. It’s more on how I apply them. Hopefully, you can gain new insights to apply them yourself.

In this article, I use Django Rest Framework (DRF) for my backend service on my Software Development course project. The service's purpose is to store data, serve business logic, and provide APIs for our web and mobile clients to use.

SOLID Principle

SOLID is an acronym for object-oriented design principles by Robert C. Martin. It consists of:

  • Single Responsibility Principle: A class should have one and only one reason to change.
  • Open-closed Principle: A class should be open for extension but closed for modification.
  • Liskov Substitution Principle: Every subclass or derived class should be substitutable for their base or parent class.
  • Interface Segregation Principle: Objects should not be forced to implement interfaces they don’t use.
  • Dependency Inversion Principle: Objects must depend on abstractions, not concrete classes.

SOLID gives us a general guideline on how we should develop our code. Design patterns give us solutions that try to follow these principles on common software development problems.

MVC Pattern

Is Django MVC?

Model-View-Controller (MVC) is an architectural pattern invented by Xerox PARC in the 70s. Being the framework used to build user interfaces in SmallTalk, it gets an early mention in the GoF book. Today, MVC is a very popular pattern in web application frameworks. A variant of the common question is whether Django is an MVC framework.

The answer is both yes and no. The MVC pattern advocates the decoupling of the presentation layer from the application logic. MVC is very rigid about what models, views, and controllers do. However, Django takes a much more practical view to web applications. Due to the nature of the HTTP protocol, each request for a web page is independent of any other request. Django’s framework is designed like a pipeline to process each request and prepare a response.

If you familiar with MVC in SpringBoot, there is Controller, Service, and Repository in SpringBoot. In our Django convention, we manage to create View — Service — Accessor. So, in our project. We implement MVC in Django Rest Framework.

app/
├─ common/
├─ module_name/
│ ├─ accessor.py
│ ├─ interfaces.py
│ ├─ service.py
│ ├─ views.py
├─ urls.py
├─ injector.py
├─ urls.py
├─ injector.py

Our code structure become like that.

# views.pyuser_service = injector.get(UserServices)
class UserViewSet(viewsets.ViewSet):
@action(
detail=False,
url_path="get",
methods=["get"],
)
def get_user(self, request: Request) -> Response:
pass
# services.py
class UserServices:
@inject
def __init__(self, user_accessor: IUserAccessor) -> None:
self.user_accessor = user_accessor
# accessor
class UserAccessor(IUserAccessor):
def __init__(self) -> None:
pass
def get(self, user_id: int) -> Optional[User]:
pass
# urls.py
router = SimpleRouter(trailing_slash=False)
router.register("api/users", UserViewSet, basename="user")
urlpatterns = [
path("", include(router.urls)),
]

Strategy Pattern

In my opinion, the strategy pattern is the easiest and basic pattern to implement.

In a strategy pattern, a class contains an abstract instance as an attribute. This class can then call the “strategy” by invoking the attribute’s method. You can think of “strategy pattern” as a synonym of composition…

We all know that Python don’t have interface, everything is class. But, we can use abc package to create AbstractClass that will be used by ConcreteClass. My project uses MVC architecture. Here is the sample service layer struct definition and the corresponding composed accessor.

# interfaces.py
from abc import ABC, abstractmethod
class IUserAccessor(ABC):
@abstractmethod
def get(self, user_id: int) -> Optional[User]:
raise NotImplementedError
# accessor.py
class UserAccessor(IUserAccessor):
def get(self, user_id: int) -> Optional[User]:
pass
# service.py
class UserServices:
@inject
def __init__(self, user_accessor: IUserAccessor) -> None:
self.user_accessor = user_accessor

Here, UserService depends on the interface IUserAccessorand doesn’t directly depend on the concrete UserAccessor. This allows us to have multiple implementations of UserAccessorand exchange them to fit the current need.

That’s good and all, but on my application, there is only one concrete implementation of UserAccessor, so what’s the use then? Mocking. I use strategy pattern as a way to isolate each layer when creating tests. Let’s say we want to create a simple test. We can mock the accessor because the service depend on interface of accessor not directly to its ConcreteClass.

Singleton Pattern

If you guys ever use Java’s Springboot, Springboot has a decorator @Component @Controller @Servicethat is used to instantiate components for MVC patterns to be a singleton. If you guys ever noticed, we won’t need to initiate the object that has @Component in an MVC Component class. In Python, we also can do that using injector package.

How to do this?

All you need to do is just map or bind the class to its concrete class. After you bind, you can use decorator @inject to automatically inject the instance of the classes you need. Also, you need to use type-hint in __init__ function of the class you want to inject .

# modules.py
class UserModule(Module):
def configure(self, binder: Binder) -> None:
binder.bind(IUserAccessor, to=UserAccessor, scope=singleton)
binder.bind(UserServices, to=UserServices, scope=singleton)
# services.py
class UserServices:
@inject
def __init__(self, user_accessor: IUserAccessor) -> None:
self.user_accessor = user_accessor
# injector.py
from injector import Injector
from .auth.modules import AuthModule
injector = Injector(
[
UserModule,
]
)

As you can see, I map the interface of UserAcessorcalled IUserAccessor and inject it to UserService . This will result in initiation of the code, python will try to create those object whose registered in binder.bind and register the module to injector.py.

What is the advantage of doing this?

Imagine that the UserService need to be in multiple place and we need to instantiate on each place using UserService() . This will result in creating multiple instance of UserService but same purpose. And each UserService has UserAccessor that also instantiate manually using UserAccessor() . We will create multiple UserAccessor and multiple UserService . Can you imagine that we are wasting our memory to create instance of same object with same purpose multiple times !??

With Singleton Pattern and help of injector package, we just create one instance of each class for every place and every class you need.

Adapter Pattern

We found a usage of adapter patterns in our DRF backend application. Django provides a way to send email programmatically with its send_mail API. The only thing that we need to specify is what provider will we use to send the email. In my case, we use SendGrid as the email service and we specify the SendgridBackend as the email backend for our DRF application.

In settings.py of Django, we just need to add 2 lines in order to enable email.

Using SendGrid as the email backend

With the email backend setting is done, all we have to do is to call the send_mail function from Django and it will automatically adapt the email sending mechanism to utilize the SendGrid service we set up earlier.

Using the send_mail function from Django without knowing what the email provider/backend is

We can change the email service easily from the settings if we need to and the “email sending” part of the code will remain the same.

Finale

Design principles and design patterns are powerful tools for designing systems. After learning them about a year ago, I was afraid of writing code for some time because I’m always thinking about the future of my codes. During that time, I would spend about 2 hours thinking of the possible extension of my code and 30 minutes actually coding. After a while, I became used to the *patterns* of coding that will most likely yield adaptive code. So you can write code without going through what I did, here are some closing tips on when to not use design patterns:

  • Don’t use design patterns on codes that stand alone. Examples include helper codes such as hasher, decoder, etc.
  • Don’t use design patterns on codes that the closest use case where the design pattern will be useful is far-fetched. We use design patterns to handle change easier. But when the required change for the pattern to be useful is quite far, it’s better to drop the pattern because coding design patterns require more care. An example is when you’re writing code to manage categories of articles. You might be tempted to implement composite pattern in case a requirement update needs it to be nested. However, if it’s not required now, better write a simple entity and update it later when the requirement is actually given.
  • Don’t use design patterns because you like the pattern. Each pattern has a very specific use case. I used to really like the command pattern. It turns out misusing patterns is really costly.

Source

--

--