Django Monolith to Services Blueprint
Carve high-change capabilities out of a large Django monolith into separate services, each with its own PostgreSQL database and async messaging. ORM foreign keys across the seam are replaced with API calls and event-driven read models.
What and Why
Large Django monoliths accrete apps that share one database and one deployment, coupling teams and release cycles together. This blueprint extracts high-change or differently-scaled capabilities into separate services so they can be developed, deployed, and scaled on their own. Django apps with clean boundaries are good extraction candidates; cross-cutting models that nearly everything imports are not, and trying to extract them first usually fails. The goal is targeted decomposition, not converting the whole system to microservices for its own sake.
Phases
Assessment. Map Django apps, their models, and the foreign-key relationships between them. Identify apps whose tables are referenced widely versus those that are self-contained: self-contained apps extract cleanly, while tightly-linked ones need an anti-corruption layer, a translation boundary, introduced before any move. Measure change frequency to prioritize the capabilities that most benefit from independence.
Boundary definition. Define each extracted service's synchronous contract with OpenAPI and its asynchronous events over AMQP, a standard messaging protocol. Break direct ORM foreign keys that cross the seam by replacing in-database joins with API calls or with denormalized read models the service maintains from events.
Extraction. Stand up the new service using Django REST Framework, or FastAPI for lighter, async-heavy services. Route the capability through a facade in the monolith using the strangler fig pattern and shift traffic gradually while the monolith remains the source of truth until parity is proven.
Data decomposition. Give the service its own PostgreSQL database following the database-per-service principle. Use the expand-and-contract pattern to split shared tables in safe, reversible steps, and the saga pattern to coordinate writes that previously lived inside a single database transaction.
Cutover. Move reads first, then writes, behind feature flags. Run dual writes briefly to confirm parity between monolith and service, then make the service authoritative and remove the monolith's old code path.
Key Risks and Mitigations
- Data consistency: Breaking ORM foreign keys removes referential integrity enforced by the database. Enforce the relevant invariants in application logic and reconcile asynchronously, accepting eventual consistency where the business allows it.
- Distributed complexity: Add distributed tracing, retries with backoff, and circuit breakers so the monolith's synchronous habits do not leak into the new services and create cascading failures.
- Downtime: Extract one capability at a time and never split the shared database in a single migration, since a botched data split is the hardest failure to recover from.
Recommended Tooling
Django REST Framework or FastAPI for service runtimes, PostgreSQL per service, RabbitMQ for asynchronous messaging, and Kubernetes for orchestration with OpenTelemetry for tracing across the new boundaries.
Success Metrics
Track deployment frequency and lead time for the extracted capabilities to confirm they gained independence, and MTTR to ensure reliability holds as the system becomes distributed.
Prerequisites
A Django app structure with identifiable boundaries, a test suite covering current behavior, container orchestration, and a message broker provisioned before the first extraction begins.
Sequencing and Rollback
Sequence extractions so self-contained Django apps move before tightly-linked ones, and reads before writes within each capability. Keep the monolith authoritative during the dual-write window so rollback is disabling the new write path and reverting reads to the monolith. The hardest-to-reverse step is splitting the shared database, so isolate it: complete the API and event decoupling first, prove parity, and only then physically separate the data, with a tested backup and a rehearsed restore in place before the split. Confirm an event-replay capability exists so a newly extracted service can rebuild its read models from the event stream, which makes both onboarding new consumers and recovering from a bad deploy straightforward during and after the migration.