Wellfire Interactive // Expertise for established Django SaaS applications

And now the reasons for moving Django models (This Old Pony #37)

A few weeks ago I wrote about some strategies for moving Django models, i.e. between different Django apps. Unlike other classes like forms and views, models have a hard association with the installed “app”. Moving these classes involves some “creative destruction” in the database if you don’t take the necessary precautions[0].

Some of you thought this was begging the question about the benefits of having separated models and apps, so this week let’s go into a bit more detail about _why _you’d want to move models to a different app - or have different apps at all.
 

Simple organization

The most obvious reason for maintaining separate apps is because it allows you to organize more than a few lines of a code in a readable and approachable manner.

If this sounds silly stop and think about the ratio of time spent looking at and reading code to time spent actually writing code. It’s vitally important.

Django apps are a natural way of grouping models, command, forms, and views that all pertain to a common “thing” whether that “thing” represents a domain of objects or a particular set of actions for an application.
 

Isolation: collaboration and testing

We’re off to a good start, but I don’t think this is terribly persuasive on its own. You can use different Python modules without separate apps. 

However, because we’re organizing Django apps based on some “common thing”, like a common service or feature, there’s a high likelihood that we’ve organized the code in such a way that it makes working on and testing discrete feature changes easier.

If you’re working on an update to how subscriptions behave, you should be able to focus on the subscriptions package. This means, ideally, removing the distraction of other code that doesn’t pertain to subscriptions at all.

If you’re testing updates to how the user notification system works, you should be able to test that discretely (before testing in toto). Mocks can be incredibly helpful in testing isolated functionality, but this quickly becomes cumbersome and horrible if you need to mock the entire world.

Strictly speaking breaking your code out into Django apps isn’t necessary to achieve this, but it does go a long way toward this goal.
 

Reusability and the road to standalone status

Reusable or “standalone” Django apps are Django apps that are installable as packages from outside of a Django project (e.g. Django Rest Framework, Haystack, Django CMS, and Django Organizations[1]). 

This isn’t an obvious win for everyone since the primary benefit is accrued to you once you have the need to actually reuse a package.

If you’re working at an agency, this is an obvious win.

If your company has several in-house Django sites, this is likely a significant win.

If you have just the one Django site, the benefits are less obvious. However if your site gets to the point where it needs to scale or you want to add additional service offerings, then even with one _user facing site _you’re likely benefits from a multiple application architecture and - guess what - reusable apps are a huge boon now.

But this is in and of itself not the necessary justification we’re looking for using multiple Django apps.
 

Hierarchy and enforcing architecture

This, for me, is where the meat meets the potatoes.

To motivate the most persuasive argument, let’s get to the most persuasive criticism of using multiple Django apps.

We quickly discovered that if we had these ForeignKey’s crossing different apps, then perhaps they weren’t really separate apps to begin with. In fact, we really just had one “app” that could be organized into different packages.[2]

Or in other words, everything was so tightly coupled that it made little sense to keep models apart.

If the data you’re _modeling _with your models really is so coupled such that numerous back and forth relationships are warranted then you know what, maybe this really does demand to be organized into one app.

But I’m going to venture something else. This problem instead suggests some problems with the design of your architecture, and to the greatest degree possible you should work toward fixing this.

Here’s my whiteboard-to-phone-camera diagram below of a package hierarchy in a Django site. It includes Django agnostic Python modules at the top level, then Django itself (plus standalone apps) in the middle, and your own site/project at the bottom.

A hand drawn diagram of Python/Django package hierarchy

Within the third layer at the bottom, in your own Django site/project, the apps are structured in sub-levels. For example there might be more “global” packages like a user profile or custom permissions app, and then more detailed apps like “Zeta” in the diagram above.

If this were reproduced in a more detailed format we’d see a directed graph showing the direction of dependency, or at the very least the explicit direction of dependency[3]. The goal is to minimize the number of edges (dependencies) pointing in both directions. 

“Now”, you might say, “this is an unnecessary exercise if we use fewer apps or even one app. In that case, all of these dependencies just go away.”

Except they don’t go away, the difference is now they’re quietly hidden in one models module. The lack of imports doesn’t mean there are no dependencies among models (or other modules). 

The separation created by app boundaries makes bidirectional imports problematic, migrations included, but it also enforces some discipline in how the application is designed. If it can’t then you may be in a situation where you really _do _have a gigantic single app or you have some challenges in your development process that modules can’t fix.
 

Convention

There’s yet one more reason to organize your models and apps this way: convention.

Convention doesn’t matter for passion projects or solo ventures. But if you have a project which more than one person will ever work, on which you might ever need to onboard new developers, it helps if your project follows the conventions in its ecosystem, whether that’s a language, a framework, or anything else.

The losses from following, say, a Rails convention, in a Django app might cause more trouble from confusion in other developers than it saves you in immediate organizational benefit.
 

Alternatives

This isn’t a tract, and there’s no downside to discussing the alternatives.

The first and most obvious is one single app with a single models module. Why would you _choose _this? Maybe because you’re starting your app and it’s not clear where the lines should be drawn. And further, you’re not sure how much time is worth investing in the architectural design (classic case of MVP).

From here you can start using a models package and breaking out models into separate modules under “models”.

There’s an example of this in the Discourse app[4]. Yes, it’s a Rails app. But that’s the convention in Rails. And hey, it works[5]. But can you look at that and tell me how those modules are related? Probably not.

You have more freedom than that example, so you could use a few domain related modules for logically grouping models if one models.py is too verbose.

models/ \_\_init\_\_.py bands.py instruments.py producers.py

Just make sure you avoid those circular dependencies. And if you do manage to avoid them, sleep soundly knowing that you’re now implicitly maintaining multiple Django apps.

Merry namespacing,
Ben

[0] https://www.getdrip.com/broadcasts/632141934/2008f96b5be016533b795
[1] Got multiple user accounts? https://github.com/bennylope/django-organizations
[2] Otherwise I think this is good advice, on the whole: https://blog.doordash.com/tips-for-building-high-quality-django-apps-at-scale-a5a25917b2b5
[3] I’d count reverse relations as implicit directionality
[4] https://github.com/discourse/discourse/tree/master/app/models I also maintain a Python Discourse client and trying to read the “documentation” for the Discourse project has not made me regret Wellfire’s commitment to Django
[5] It works well here, too, because Rails is designed for this

Learn from more articles like this how to make the most out of your existing Django site.