Goal
The goal is to split out the monolithic lith
app into multiple modular apps.

But why?
There is nothing inherently wrong with a monolithic Django app, particularly if publishing of apps is not an immediate concern. A single Django app with domains as submodules is an idiomatic and natural way to write Python.
Django apps on the other hand is Django’s recommended mechanism to separate distinct domains. While this is technically breaks Inversion of Control as the domain is now tied into the framework’s interface, as long as each domain’s internals are properly hidden and interfaces are correctly respected, practical DDD is still possible.
Least of all the reasons to go for multiple Django apps is the familiarity of the community. Being the default way of working is pretty hard to beat!
The game plan
To illustrate, lith
currently has this directory structure in place for its templates:
src/lith/templates
`-- lith
|-- auth
|-- dashboard
| `-- partials
|-- marketing
| `-- partials
`-- partials
I can point out three app candidates based on this structure:
authnz
for authentication and authorization (auth
is a reserved app name)dashboard
for top-level dashboard concerns (specific concerns would get their own app)marketing
for public-facing pages
Creating the apps
To start, I create the apps:
$ make "startapp authnz"
$ make "startapp dashboard"
$ make "startapp marketing"
And installed them:
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"authnz.apps.AuthnzConfig",
"dashboard.apps.DashboardConfig",
"marketing.apps.MarketingConfig",
"crispy_forms",
"crispy_bootstrap5",
]
Moving the logic to each app
The actual refactoring is an involved process, and each detail is better explored in the ensuing branch. But the steps came down to these:
- Start by moving the tests to their respective apps
- Copy the source files to their respective apps
- Use the IDE’s refactoring tools to autoupdate the import paths
- Delete the
lith
app - Guided by tests, update the import paths that the IDE can’t figure out
- Any non-app specific template / static file goes to the top-level
template
andstatic
directories, respectively - Run the linter and formatter
- Do a manual test to confirm nothing broke
Final Outcome
The refactored structure now looks like this:
src/
|-- authnz
| |-- __init__.py
| |-- admin.py
| |-- apps.py
| |-- forms.py
| |-- migrations
| | |-- 0001_initial.py
| | `-- __init__.py
| |-- models.py
| |-- models_tests.py
| |-- templates
| | `-- authnz
| |-- urls.py
| |-- views.py
| `-- views_tests.py
|-- config
| |-- __init__.py
| |-- __pycache__
| |-- asgi.py
| |-- settings.py
| |-- urls.py
| `-- wsgi.py
|-- dashboard
| |-- __init__.py
| |-- admin.py
| |-- apps.py
| |-- migrations
| | `-- __init__.py
| |-- models.py
| |-- templates
| | `-- dashboard
| | `-- partials
| |-- urls.py
| |-- views.py
| `-- views_tests.py
|-- manage.py
|-- marketing
| |-- __init__.py
| |-- admin.py
| |-- apps.py
| |-- migrations
| | `-- __init__.py
| |-- models.py
| |-- templates
| | `-- marketing
| | `-- partials
| |-- tests.py
| |-- urls.py
| |-- views.py
| `-- views_tests.py
|-- static
| `-- lith
| |-- css
| | `-- vendor
| | `-- tabler
| `-- img
`-- templates
`-- lith
`-- partials
Conclusion
In this lab, I refactored the monolithic lith
Django app into reusable apps and used the existing test suite to protect from regression bugs.
The source is available at the feature/009-refactor-to-apps
branch.