DEV Community

Christopher Tabula
Christopher Tabula

Posted on • Updated on

Structuring your Django Project with DRF & DDD

There are many ways to structure a Django project. Finding the design that ensures maintainability and scalability is difficult. This is my objective.

I’ve been studying Domain Driven Design and how to incorporate it in Django with Django REST Framework. I want to design a robust architecture that's easy for documentation, and effective for both collaboration and solo projects. Whether I'll achieve this or not, this endeavor will be an important experience for me.

The project is still ongoing and there are topics in DDD that I have not fully explored. I'm going to do my best to study them, and hopefully, release an initial stable version of this by the end of this year. Without further ado, let's begin.

Overview

This is the initial structure:

project/
  api/
    migrations/
    services/
      blogs/
        read_blog.py
    __init__.py
    apps.py
  project/
    __init__.py
    asgi.py
    settings.py
    urls.py
    wsgi.py
  resources/
    migrations/
    repositories/
      base_repository.py
      blog_repository.py
    serializers/
      blog_serializer.py
    __init__.py
    admin.py
    apps.py
    models.py
Enter fullscreen mode Exit fullscreen mode

Right now, it has three applications: api, project, and resources. Make sure to create them and install the djangorestframework and django-cors-headers packages.

Project

The project folder is our core application. The folder name is based on the name you enter for your django project, so you don't have to follow the name provided in this example. By default, this is where the settings, wsgi, asgi, and urls are stored. We can also store other files here such as classes for JWT, custom middlewares, and so on. Since we're using other packages, we need to configure our settings file:

project/settings.py

INSTALLED_APPS = [
    
    'corsheaders',
    'rest_framework',
    'api.apps.ApiConfig',
    'resources.apps.ResourcesConfig',
]

MIDDLEWARE = [
    
    'corsheaders.middleware.CorsMiddleware',
    
]

# this is set to true for now
CORS_ORIGIN_ALLOW_ALL = True
Enter fullscreen mode Exit fullscreen mode

We'll revisit this folder later once we establish our api services.

Resources

The resources folder is where we place our models, repositories, and serializers. Our models will contain the schema for our database tables, while our repositories will handle fetching of objects from the persistence layer (ORM). Lastly, the serializers will render our querysets into JSON.

resources/models.py

from django.contrib.auth.models import User
from django.db import models

class Blog(models.Model):
  content = models.TextField()
  summary = models.CharField(max_length=100)
  title = models.CharField(max_length=100)
  created_on = models.DateTimeField(auto_now_add=True)

  author = models.ForeignKey(
    User,
    on_delete=models.CASCADE,
    default=None
  )

  def __str__(self):
    return self.title
Enter fullscreen mode Exit fullscreen mode

For our repositories, we need a base repository file where all base methods are registered and can be inherited by different model repositories. Each model repository can have their own methods for aggregate and complex queries:

resources/repositories/base_repository.py

from django.db import InternalError, DatabaseError
from django.http import Http404, HttpResponseServerError

class BaseRepository(object):
  def find(self, id, value_list=()):
    try:
      if len(value_list):
        return self.model.objects.get(pk=id).values(*value_list)
      else:
        return self.model.objects.get(pk=id)
    except self.model.DoesNotExist:
      raise Http404
    except InternalError:
      raise HttpResponseServerError
    except DatabaseError:
      raise Http404
Enter fullscreen mode Exit fullscreen mode

resources/repositories/blog_repository.rb

from resources.models import Blog
from resources.repositories.base_repository import BaseRepository

class BlogRepository(BaseRepository):
  def __init__(self):
    self.model = Blog
Enter fullscreen mode Exit fullscreen mode

resources/serializers/blog_serializer.py

from rest_framework import serializers
from resources.models import Blog

class BlogSerializer(serializers.ModelSerializer):
  class Meta:
    model = Blog

    fields = [
      'id',
      'content'
      'summary',
      'title',
      'created_on'
    ]
Enter fullscreen mode Exit fullscreen mode

You may be wondering what's the importance of the repository since we already have the ORM. One reason is that this allows the project to migrate to different databases with ease, especially if the ORM methods vary from the default ORM or if the database has no ORM package for Django. When this happens, we only need to update the queries in the repository without touching the services in our api. Here's an example:

                       From RDBMS
      ModelServices        |       ModelRepository
ModelRepository().find(id) | Model.objects.get(pk=id)

                        To NoSQL
      ModelServices        |       ModelRepository
ModelRepository().find(id) | Model.find_one({"_id": id})
Enter fullscreen mode Exit fullscreen mode

API

The api is where all the class views for our endpoints are stored. For now, it only has the services folder where sub-folders based on the models are stored, and each sub-folder has files dedicated for each CRUD operation. In this example, we have a blogs folder and a read blog file for viewing specific blog record:

api/services/blogs/read_blog.py

from rest_framework.views import APIView
from rest_framework.response import Response

from resources.repositories.blog_repository import BlogRepository
from resources.serializers.blog_serializer import BlogSerializer

class ReadBlogService(APIView):
  def get(self, request, id, format=None):
    blog = BlogRepository().find(id)

    return Response(BlogSerializer(blog, many=False).data)
Enter fullscreen mode Exit fullscreen mode

Final adjustments

Now, we can edit the project.urls file to register the new blog service:

project/urls.py

from django.contrib import admin
from django.urls import path

from api.services.blogs.read_blog import ReadBlogService

urlpatterns = [
    path('admin/', admin.site.urls),
    path('blog/<int:id>', ReadBlogService.as_view()),
]
Enter fullscreen mode Exit fullscreen mode

Conclusion

This is what I've conceptualized so far. It may seem complicated but that's because it's meant for medium-scale and large-scale projects. It won't be useful for small applications. Of course, I don't expect this to be the ideal choice for Django programmers. As I've mentioned before, there are many ways to structure a Django project. There are undoubtedly other designs out there that are better and battle-tested. But I'll do my best to make this a potential option.

What are your thoughts on this?

Discussion (0)