راهنمای جامع طراحی معماری مقیاسپذیر در جنگو
نوشته شده توسط: سروش خانی
•
در دنیای توسعه وب، بسیاری از برنامهنویسان جنگو با مشکل “Fat Models” (مدلهای سنگین) یا “Fat Views” (ویوهای سنگین) مواجه میشوند. وقتی پروژه بزرگ میشود، منطق برنامه در هم تنیده شده و تست کردن یا تغییر دادن هر بخش به کابوس تبدیل میشود.
برای جلوگیری از این فاجعه، ما نیاز به یک معماری لایهای (Layered Architecture) داریم.
در این مقاله، یاد میگیریم چگونه جنگو را از حالت "همه چیز در مدل" خارج کرده و به سمت یک معماری حرفهای هدایت کنیم.
۱. مشکل اصلی: تلهی معماری پیشفرض جنگو
در معماری استاندارد Django (یعنی Model-Template-View)، تمایل طبیعی این است که:
- Fat Models: تمام منطق محاسباتی و تجاری را داخل متدهای مدل بنویسیم.
- Fat Views: تمام کنترلهای جریان برنامه و تعامل با دیتابیس را در ویو بنویسیم.
نتیجه چیست؟ کد شما به شدت به فریمورک وابسته میشود (Coupled) و تستنویسی (Unit Testing) برای منطقهای پیچیده بسیار دشوار میگردد.
۲. معماری پیشنهادی: الگوی Service Layer
بهترین راه برای حل این مشکل، معرفی یک لایه میانی به نام Service Layer است. در این معماری، ما لایهها را به این صورت تقسیم میکنیم:
1. Models: فقط مسئول تعریف ساختار داده و روابط (Schema) هستند.
2. Services: قلب تپنده برنامه. تمام منطق تجاری (Business Logic) اینجا قرار میگیرد.
3. Selectors (یا Querysets): مسئول تمام عملیات خواندن و فیلتر کردن دادهها از دیتابیس.
4. Views/API: فقط مسئول دریافت درخواست، اعتبارسنجی اولیه و بازگرداندن پاسخ (Response) هستند.
۳. پیادهسازی عملی با مثال (سیستم سفارشدهی)
فرض کنید میخواهیم سیستمی طراحی کنیم که یک سفارش را ثبت میکند، موجودی انبار را کم میکند و ایمیل تایید ارسال میکند.
مرحله اول: مدلهای ساده (Thin Models)
مدلها نباید بدانند که "سفارش" چه فرآیندی را طی میکند؛ آنها فقط میدانند که دادهها چگونه ذخیره میشوند.
models.py
from django.db import models
class Product(models.Model):
name = models.CharField(max_length=255)
price = models.DecimalField(max_digits=10, decimal_places=2)
stock = models.IntegerField()
class Order(models.Model):
product = models.ForeignKey(Product, on_delete=models.CASCADE)
quantity = models.IntegerField()
total_price = models.DecimalField(max_digits=10, decimal_places=2)
created_at = models.DateTimeField(auto_now_add=True)
is_paid = models.BooleanField(default=False)
مرحله دوم: لایه سلکتورها (Selectors)
برای اینکه ویوها مستقیماً با Querysetهای پیچیده درگیر نشوند، منطق "خواندن" را جدا میکنیم.
selectors.py
from .models import Order, Product
def get_active_products():
"""محصولاتی که موجودی دارند"""
return Product.objects.filter(stock__gt=0)
def get_customer_orders(user_id):
"""سفارشهای یک کاربر خاص"""
return Order.objects.filter(user_id=user_id)
مرحله سوم: لایه سرویس (The Service Layer)
اینجاست که جادو اتفاق میافتد. تمام منطق "نوشتن" و تغییر وضعیت در اینجا است.
services.py
from django.db import transaction
from .models import Order, Product
class OrderService:
@staticmethod
@transaction.atomic # برای اطمینان از اینکه یا همه چیز ذخیره شود یا هیچ چیز
def create_order(product_id: int, quantity: int, user) -> Order:
# 1. پیدا کردن محصول
product = Product.objects.get(id=product_id)
# 2. چک کردن موجودی
if product.stock < quantity:
raise ValueError("موجودی کافی نیست!")
# 3. محاسبه قیمت
total_price = product.price * quantity
# 4. ایجاد سفارش
order = Order.objects.create(
product=product,
quantity=quantity,
total_price=total_price,
user=user
)
# 5. کسر از موجودی انبار
product.stock -= quantity
product.save()
# 6. (اختیاری) فراخوانی سرویس ایمیل یا ارسال به RabbitMQ
# send_order_confirmation_email(user.email, order.id)
return order
مرحله چهارم: ویوی تمیز (Thin View)
حالا ویو بسیار ساده و خوانا است. وظیفه ویو فقط مدیریت پروتکل HTTP است.
views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from .services import OrderService
class OrderCreateView(APIView):
def post(self, request):
product_id = request.data.get('product_id')
quantity = request.data.get('quantity')
try:
# ویو فقط سرویس را صدا میزند و نتیجه را برمیگرداند
order = OrderService.create_order(
product_id=product_id,
quantity=quantity,
user=request.user
)
return Response({"status": "success", "order_id": order.id}, status=201)
except ValueError as e:
return Response({"error": str(e)}, status=400)
except Exception:
return Response({"error": "خطای سیستمی"}, status=500)
۴. مزایای این معماری (چرا این کار را انجام دهیم؟)
۱. قابلیت تستپذیری بالا (Testability)
در معماری سنتی، برای تست کردن منطق "کم کردن موجودی"، شما مجبور بودید یک درخواست HTTP کامل (Request/Response) ارسال کنید. اما در این معماری، شما میتوانید مستقیماً متد `OrderService.create_order` را تست کنید بدون اینکه درگیر لایههای وب شوید.
۲. رعایت اصل Single Responsibility (SRP)
هر کلاس یا تابع فقط یک کار انجام میدهد:
- Model: نگهداری داده.
- Service: اجرای عملیات.
- Selector: استخراج داده.
- View: مدیریت ورودی/خروجی.
۳. مقیاسپذیری و نگهداری (Maintainability)
اگر فردا تصمیم بگیرید به جای دیتابیس SQL از یک سرویس خارجی برای مدیریت موجودی استفاده کنید، شما فقط لایه services.py را تغییر میدهید. ویوها و مدلهای شما دستنخورده باقی میمانند.
۴. جلوگیری از Race Conditions
با استفاده از transaction.atomic در لایه سرویس، ما تضمین میکنیم که اگر در حین ساخت سفارش، سیستم کرش کرد، موجودی محصول بیهوده کم نشود.
۵. چه زمانی از این معماری استفاده نکنیم؟
اگر در حال ساخت یک پروژه کوچک، MVP یا یک سایت ساده شخصی هستید، این معماری ممکن است باعث Over-engineering شود. برای پروژههای کوچک، همان ساختار پیشفرض جنگو سرعت توسعه شما را بالاتر میبرد. اما به محض اینکه پروژه قرار است توسط تیمی توسعه یابد یا پیچیدگیهای تجاری آن افزایش مییابد، این معماری یک ضرورت است.
جمعبندی
یک معماری قدرتمند در جنگو یعنی جداسازی لایهها. با استفاده از لایههای Service و Selector، شما کدی مینویسید که نه تنها در حال حاضر کار میکند، بلکه در آینده نیز در برابر تغییرات و توسعه، انعطافپذیر و مقاوم خواهد بود.