
Ever found yourself writing the same SQL query or ORM call in three different controllers? Or worseโtrying to unit test business logic but getting stuck because itโs hard-coded to a live database?
Thatโs where the Repository Pattern saves the day. It acts as a mediator between your domain/business logic and the data mapping layer.
The Architecture at a Glance
Instead of your service talking directly to the database, it talks to an Interface.
Client/Service: "I need user #42."
Repository: "I'll go get that for you (I don't care if it's from SQL, NoSQL, or a Cache)."
Data Source: Returns the raw data.
๐ป The "Before vs. After"
The "Spaghetti" Way (Logic + DB Mixed):
# Controller
def get_user_dashboard(user_id):
user = db.query("SELECT * FROM users WHERE id = ?", user_id) # Leaking DB logic
stats = redis.get(f"stats_{user_id}")
return render_template(user=user, stats=stats)
The Repository Way (Clean & Decoupled):
# Service
def get_user_dashboard(user_id):
user = user_repo.get_by_id(user_id) # Abstracted
stats = user_repo.get_activity_stats(user_id)
return render_template(user=user, stats=stats)
Why bother?
Testability: You can easily "mock" the repository during unit tests without needing a real database.
Don't Repeat Yourself (DRY): Centralize your data access logic. If a query needs to change, you change it in one place.
Flexibility: Want to switch from Postgres to MongoDB? You only update the Repository implementation; your business logic stays exactly the same.
Separation of Concerns: Your API/Controller stays "thin" and focused only on handling requests.
The "Golden Rule"
The Repository Pattern is powerful, but don't over-engineer! If you are building a very simple CRUD app with only 2-3 tables, adding a repository layer might just be unnecessary boilerplate.
Use it when your business logic starts getting complex or when you plan to support multiple data sources.

Top comments (0)