From MVC to Microservices: Lessons from Evolving a Python SaaS Backend

The author recounts a three‑year journey of scaling a Python‑based HR SaaS backend from a simple Django MVC prototype to a microservice architecture with domain‑driven design, detailing each architectural stage, the challenges faced, and the concrete optimizations applied.

ITPUB
ITPUB
ITPUB
From MVC to Microservices: Lessons from Evolving a Python SaaS Backend

1. MVC

At the project's start, a small team (<5 developers) built the product prototype using Django, focusing on rapid feature delivery rather than architectural concerns. After designing the database schema, functional views were abstracted, and extensive placeholder code was added to accommodate evolving product requirements. The main effort was establishing team‑wide coding standards and linting rules.

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

Django concurrency was improved by running uWSGI with the gevent worker model.

Redis connection overload was mitigated using the built‑in connection pool of redis‑py.

MySQL connection pressure was reduced with djorm‑ext‑pool connection pooling.

Celery workers were configured to use gevent for higher parallelism.

2. Service Splitting

As the team grew and feature count increased, maintaining a monolithic Django codebase became untenable. The existing modular app structure (high cohesion within apps, low coupling between apps) facilitated the extraction of shared functionality into a common Python library while keeping a single database and Redis instance initially. Later, multiple database instances were added to handle load.

Inter‑service communication was performed via HTTP, with internal hosts configured in /etc/hosts. However, this introduced maintenance overhead, lack of retries, error handling, and rate‑limiting, leading to reliability issues.

The Nginx Push Module became obsolete; a custom solution using Tornado + ZeroMQ (tormq service) was built for push notifications.

3. Microservice Architecture

An OpenResty‑based Kong API Gateway was introduced to centralize authentication, rate limiting, and other cross‑cutting concerns. Deployment scripts automatically registered new services with Kong via its admin API.

To replace direct HTTP calls, a custom RPC framework named doge (inspired by Weibo’s Motan) was built on gevent + msgpack, using etcd for service discovery and providing client‑side rate limiting, high availability, and load balancing.

Choosing Kong was driven by its Lua‑based plugin model, which allowed rapid development of custom plugins without the need to learn Go. The gateway was run in a two‑node cluster behind a cloud load balancer for high availability.

Although the team considered adopting the pure‑Python thriftpy RPC framework used by Ele.me, limited manpower and varying skill levels made the custom doge solution more practical.

4. Domain‑Driven Design (DDD)

The next architectural goal was to separate data services from application services. Each data service would encapsulate one or more bounded contexts, exposing a single aggregate root via RPC. Application services could depend on multiple data services, achieving a clear dependency direction from higher‑level services to lower‑level data services.

At the time of writing, DDD was still in the design phase and not yet implemented, but the author expects the backend to continue evolving toward this pattern.

Conclusion

Technical choices should serve product and customer needs rather than follow trends blindly. Team composition and skill gaps often force compromises; finding optimal solutions within those constraints is the real challenge. The author also notes that Service Mesh is emerging as the next‑generation microservice infrastructure, which they plan to explore further.

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.

BackendPythonMicroservicesDomain-Driven DesignDjangoKong
ITPUB
Written by

ITPUB

Official ITPUB account sharing technical insights, community news, and exciting events.

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.