From Monolithic Django to Microservices: Building a Scalable HR SaaS Backend

After three years of evolving a Python‑based HR SaaS backend from a simple MVC prototype to a microservices architecture with domain‑driven design, the author shares the architectural stages, challenges faced, and practical optimizations such as uWSGI‑gevent, Redis pooling, Kong API gateway, and custom RPC framework.

21CTO
21CTO
21CTO
From Monolithic Django to Microservices: Building a Scalable HR SaaS Backend

Background

Before joining Tencent, the author spent three years as a backend developer on an HR SaaS product, guiding it from zero to a modest user base. The service offers web, Android, iOS mini‑program clients and uses a Python‑based RESTful API.

Architecture Evolution Stages

The backend architecture progressed through four major phases: MVC, Service Splitting, Microservice Architecture, and Domain‑Driven Design.

1. MVC

In the early stage the team (max five developers) used Django to quickly prototype features. After designing the database schema, functional views were abstracted. Emphasis was placed on coding standards and static checks because product requirements were still fluid.

MVC architecture diagram
MVC architecture diagram

The architecture used Nginx for load balancing, multiple Django instances for business logic, Celery for asynchronous tasks, Redis for caching, and the Nginx Push Module for real‑time notifications.

Django’s concurrency limits were mitigated by running uWSGI with the gevent worker model.

Redis connection overload was solved with the built‑in connection pool of redis‑py.

MySQL connection overload was addressed using djorm‑ext‑pool for connection reuse.

Celery was configured to use gevent for concurrent task execution.

2. Service Splitting

As the team grew, responsibilities were divided among multiple services. Existing Django apps were refactored into independent services while sharing a common Python library, database, and Redis. Later, the database was scaled to multiple instances.

Service splitting diagram
Service splitting diagram

Services communicate via HTTP, with internal hosts configured in /etc/hosts. The Nginx Push Module, no longer maintained, was replaced by a custom Tornado + ZeroMQ solution (tormq) for push notifications.

Replacing the stale Nginx Push Module with Tornado + ZeroMQ improved long‑connection handling.

Additional issues included lack of retry, error handling, rate limiting, and the operational burden of maintaining Nginx configurations for each new service.

3. Microservice Architecture

The entry layer adopted Kong (built on OpenResty) as an API gateway, implementing custom authentication and rate‑limiting plugins. Deployment scripts register new services via Kong’s admin API.

A lightweight RPC framework named doge was built on gevent + msgpack, using etcd for service discovery and providing client‑side rate limiting, high availability, and load balancing.

Choosing an API gateway involved evaluating Go‑based and Lua‑based options; Kong was selected for its Lua plugin ecosystem and ease of deployment. Two Kong instances run behind a cloud load balancer for high availability.

Although the team considered using the Python‑based thriftpy framework, limited manpower led to the development of the doge framework, inspired by Weibo’s Motan.

Microservice architecture diagram
Microservice architecture diagram

4. Domain‑Driven Design

The latest design extracts a data‑service layer where each service hosts one or more bounded contexts exposing a single aggregate root via RPC. Application services depend on these data services, achieving loose coupling and clearer responsibility boundaries.

At the time of writing, DDD was still being studied and had not yet been fully implemented, but the author expects the architecture to continue evolving in that direction.

Conclusion

Technical choices must serve product and customer needs rather than follow trends blindly. Team composition and resource constraints often force compromises; finding optimal solutions within those constraints is the real challenge. The author notes that Service Mesh is the emerging direction for future microservice architectures.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

architecturePythonDomain-Driven DesignDjangoKong
21CTO
Written by

21CTO

21CTO (21CTO.com) offers developers community, training, and services, making it your go‑to learning and service platform.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.