The first rule of Microservices
You almost never need to start with microservices
People want to scale their SaaS like it’s Netflix or Facebook before they even have 10 users.
Software architecture isn’t about building a scalable system from day one. It’s about building the right system for your current needs—nothing more, nothing less. Kubernetes is cool. Splitting backend and frontend is great. But for most projects, they’re unnecessary complexity. Start with a monolith. It’s:
- Faster to build
- Cheaper to develop and run
- Easier to understand — one person can hold the entire system in their head
When (and if) your project grows to the point where microservices become necessary, you’ll have the revenue, team, and data to justify the leap. And in 9 out of 10 cases, your real bottleneck will be database queries, not service boundaries.
Things to Keep in Mind with a Monolith: Modular Design
A monolith doesn’t mean a large mess. The key is modular design—organizing your codebase into clear, independent domains from the start, even within a single application. This is a core principle in Django based development, and Django apps.
What is Modular Design?
Modular design means structuring your monolith as a collection of well-defined, loosely coupled modules—each responsible for a specific business domain (e.g.,
users,billing,notifications,inventory).
Each module should have:
- Its own folder and Django app
- Its own models, views, serializers, and URLs
- Minimal cross-app dependencies
Django Already Gives You Modularity — Use It!
Django’s apps are perfect for modular design. Each django-app is a self-contained unit—exactly what you need for future extraction.
myproject/
├── users/
│ ├── apps.py # AppConfig(label='users')
│ ├── models.py # UserProfile, Preferences
│ ├── views.py
│ ├── urls.py
│ └── tests/
├── billing/
│ ├── apps.py # AppConfig(label='billing')
│ ├── models.py # Invoice, Subscription, Payment
│ ├── views.py
│ ├── serializers.py
│ ├── urls.py
│ └── tasks.py # send_invoice_email
├── notifications/
│ ├── apps.py
│ ├── models.py # NotificationLog
│ ├── services.py # send_email(), send_push()
│ └── tasks.py # Celery tasks
└── core/ # shared utilities, settingsPro tip: Use AppConfig with explicit label and name to avoid naming collisions and make schema separation crystal clear.
# billing/apps.py
from django.apps import AppConfig
class BillingConfig(AppConfig):
name = 'billing'
label = 'billing' # DB table prefix: billing_invoiceWhy It Matters: Future-Proof Extraction
Modular design is your escape hatch.
When the time comes to scale, you don’t rewrite—you extract.
Because billing was already isolated:
- No tangled
importspaghetti - Clear database ownership (
billing_*tables) - Independent tests and admin
- Can be rewritten to FastAPI, Go, or Node with minimal effects to other parts of the project
TL;DR
Start simple. Start monolithic. But build with Django apps.
Each app = one bounded context.
That way, if you ever do need microservices, you’re not refactoring—you’re just promoting an app.
