cosmic-django

Book

After writing this, I was inspired to further explore the applicability of other architecture patterns to Django.

That ended up becoming my first book!

Django Architecture Patterns is available at: https://a.co/d/baRzank

Intro

Cosmic Python is a very interesting book with interesting concepts. But as a Django fan, I kept thinking how all that would apply to Django. So I implemented the example project while reflecting on best practices and referencing other sources.

If you look at their Django implementation in Appedix D, the authors do something weird with the project structure. It’s like they think Django is an ORM as opposed to a web framework with batteries included… They totally threw the batteries away and did their own thing, and that’s the opposite of what I want to do.

Let me know what you think at https://github.com/brunodantas/cosmic-django/issues

Starting out: TDD

I started my project with the startproject command, although I always have Cookiecutter Django in mind. The latter is aimed towards big production-ready projects though.

Surprisingly, Cosmic Python is a sort of sequel to another book about Test Driven Development. I don’t know anyone who does TDD, but I’m open to different perspectives.

I decided to start with the final version of the test definitions because I don’t want to do all the architecture/requirement changes that they do along the book.

I won’t get into detail on them at first, but if you like tests, you can see their final form in the repo.

One of my goals with this project is making all tests pass. This seems challenging, but at least then I could say my project works.

I’ll write the tests as I go along though. My own approach won’t be really TDD this time, but maybe in a future project.

Domain Modeling

This is the part of your code that is closest to the business, the most likely to change, and the place where you deliver the most value to the business. Make it easy to understand and modify.

That’s an interest concept. If you’ve read the book, you know that they put this model stuff away from the ORM/DB, interestingly. It sure feels like throwing the baby out with the bathwater in the case of Django. They kinda admit it by basically saying Django is just for simple CRUD apps. Which feels wrong.

All that makes me want to skip on separate domain models and use the ORM models like everyone does. But I have to say, it actually feels like a compelling idea to have another separation layer between the domain and the infrastructure. Because doing both on the Django models.py feels like those things coupled, the model is doing too many things, and the code is a mix of business logic and DB operations and whatnot.

I’d say one of the things I find most interesting about the book is how much it emphasizes the principle of Separation of Concerns.

Let’s see what others have to say about Django models.

Models should encapsulate every aspect of an “object,” following Martin Fowler’s Active Record design pattern.
https://docs.djangoproject.com/en/5.2/topics/db/models/

An object that wraps a row in a database table or view, encapsulates the database access, and adds domain logic on that data.
https://www.martinfowler.com/eaaCatalog/activeRecord.html

The Django docs are firmly on the camp of the so-called fat models.

Note that the Active Record pattern apparently does three things. Is that too much? Wikipedia says it’s too much[citation needed]. If you google active record bad, you see people saying that it’s ok for simple CRUD apps.

Two Scoops of Django says the popular pattern called fat models can lead to the anti-pattern called god object. That aligns with what I think, meaning that models that do too many things are bad. Their solution is to separate business logic into helper functions somewhere else, which can be segregated into a core app. This makes me think of some things we’ll see later, but how about domain models?

Looking at what the authors’ domain models end up storing, it’s the same data as the ORM models. What differs is that one has business logic methods and the other has DB related methods… plus annoying mappers between the two. There must be a way to avoid mapping same-y objects back and forth.

Which brings us back to the idea of helper functions. It’s like the perfect middle ground. We can have an Active Record AND have a core app with our helper/utils functions. Who needs OO methods anyway?

More specifically, we’ll put the functions in a logic module.

Repository Pattern

We’ll introduce the Repository pattern, a simplifying abstraction over data storage, allowing us to decouple our model layer from the data layer.

This is a pretty interesting pattern. Separating things and decoupling even more, and then doing dependency inversion so that the Repository calls the domain model. We can do that. But should we? The authors aren’t sure either, saying it’s a lot of work and that it may be good if you plan on migrating away from Django. Which brings me to this post:

For example: in general, people don’t often swap out their data access layer without also doing other massive rewrites. And in Django-based applications it’s even less likely to try to swap out the ORM without other huge code changes happening at the same time, because the Django ORM is probably the single most tightly-integrated component of the entire framework — if you stop using it, you’re throwing away so much other stuff that “why are we even still using Django” becomes a really significant question.
https://www.b-list.org/weblog/2020/mar/16/no-service/

That is, if you get to the point of replacing Django itself, I think you have bigger problems…

The other advantage is being able to unit test things without using the DB, which doesn’t seem like a big deal IMO.

Other than that, the dependency inversion is super cool, but I don’t see enough reason to add another abstraction layer to hide away the main storage of the system. If it was a secondary storage though, it would make a lot more sense to me. So I’m skipping this pattern.

Service Layer

It often makes sense to split out a service layer, sometimes called an orchestration layer or a use-case layer.

This pattern is another interesting separation of concerns. From what I understand, this is where we connect the business logic to the technical aspects. This interacts with the Repository (the ORM model in our case), does validation, calls domain functions. Seems like a nice straightforward addition to our project.

It would suck if this pattern was considered harmful by Django people though…

But it’s ok, because the service layer fits well with the choice we made back in Domain Modeling. This translates to a service module in our core app, which is endorsed by Two Scoops. And I’m sorry to disappoint my forefathers.

Unit of Work

If the Repository pattern is our abstraction over the idea of persistent storage, the Unit of Work (UoW) pattern is our abstraction over the idea of atomic operations. It will allow us to finally and fully decouple our service layer from the data layer.

This sounds like Django’s transaction.atomic with extra steps. Correct me if I’m wrong, but I think we’re safe to use transaction.atomic instead and call it one of the included batteries.

For the sake of separation of concerns, let’s put all our UoWs in the custom Managers.

Aggregates

An AGGREGATE is a cluster of associated objects that we treat as a unit for the purpose of data changes.

This chapter talks about managing business logic invariants by containing them in a new class… I don’t like this one very much. Personallty, I’d rather apply Design by Contract for that. Then we can use something like deal, or if you’re into Functional Programming, ensures.

Events and the Message Bus

If we have two things that can be transactionally isolated (e.g., an order and a product), then we can make them eventually consistent by using events.

Unless I’m missing something, this is clearly another included battery of Django: Signals. Some say you should avoid them like the plague due to maintenance concerns, and it’s a powerful way of decoupling things. Just be careful and try to implement good monitoring on this.

Following the conventions of this chapter, the Message Bus will be the Signals. Our UoW (Managers) will publish events, which will be received by the Domain (logic module) and by Handlers (signal_receivers). On the first iteration at least.

Event-Driven Architecture

In this chapter, we’ll start to make events more fundamental to the internal structure of our application.

Even more decoupling. This is about managing system complexity when adding complex use cases. Basically, we’ll have an API that fires events, which will be picked up by service handlers, which then interacts with the rest of the system.

Command Pattern

Commands are sent by one actor to another specific actor with the expectation that a particular thing will happen as a result. When we post a form to an API handler, we are sending a command. We name commands with imperative mood verb phrases like “allocate stock” or “delay shipment.”

This is about events that return values. Django Signals can do that too.

Microservices

We use events to talk to the outside world. This kind of temporal decoupling buys us a lot of flexibility in our application integrations, but as always, it comes at a cost.

Seems like a great idea. Skipping this because I don’t want to deal with microservices in this project. Let’s say I chose the Majestic Monolith.

Command-Query Responsibility Segregation (CQRS)

Most Users Aren’t Going to Buy Your Furniture

The idea of View models is awesome, but the specific approach is gonna depend a lot on the application. Let’s see the options from this chapter.

Dependency Injection

Dependency injection (DI) is regarded with suspicion in the Python world. And we’ve managed just fine without it so far in the example code for this book!

This is a good concept, and I think we can go even further by doing a kind of Metaprogramming with Django Settings. This seem more powerful than the suggested bootstrap script.