تصميم المشروع — مخطط معمارية PriceTracker الكاملة
تصميم المشروع كمخطط قبل بناء مبنى — إذا بدأت البناء بدون مخطط، قد تجد أن الأساس ليس قويًا كفاية عندما تصل للطابق الثالث، ولن يكون أمامك خيار سوى الهدم والبدء من جديد. المخطط الجيد ليس شيئًا ترسمه ثم تتجاهله؛ بل يظل دليلًا طوال عملية البناء بالكامل.
1. ما ستتعلمه
- تحليل المتطلبات: حالات الاستخدام الأساسية لتتبع أسعار SaaS (المستخدمون، المنتجات، الأسعار، الاشتراكات، الإشعارات)
- التصميم القائم على المجال: تحديد جذور التجميع وكائنات القيمة وأحداث المجال
- قرارات الاختيار التقني: مبررات اختيار FastAPI + PostgreSQL + Redis + Celery + WebSocket
- استراتيجيات إصدارات API: المقايضات بين الإصدار القائم على URL والإصدار القائم على Header
- مخطط المعمارية: طوبولوجيا نشر Charlie — موازنة التحميل، فصل القراءة/الكتابة لقاعدة البيانات، طبقات التخزين المؤقت
2. القصة الحقيقية لـ Alice
(1) المشكلة: كلما زادت الميزات، زاد الفوضى
تطور PriceTracker من API بسيط إلى خدمة بعدد متزايد من الميزات، لكنه كان يفتقر لتصميم موحد — حدود المنتجات والأسعار غير واضحة، منطق الاشتراك مبعثر عبر 10 نقاط نهاية، ونظام الإشعارات مقترن بإحكام مع منطق الأعمال. كلما أُضيفت ميزة جديدة، كان يجب تعديل خمسة ملفات، وأخطاء الارتداد تحدث بشكل متكرر.
(2) حل التصميم القائم على المجال
يُحدد التصميم القائم على المجال (DDD) المجالات الأساسية والحدود من منظور الأعمال: تجميع المنتج، وتجميع السعر، وتجميع المستخدم، وتجميع الاشتراك — لكل تجميع حدود ومسؤوليات واضحة، والتواصل عبر التجميعات يتم عبر الأحداث، مما يُلغي الترابط.
(3) العائد
لإضافة ميزات جديدة، تحتاج فقط لتعديل الكود ضمن التجميع المقابل؛ التغييرات لم تعد تؤثر على النظام بالكامل. منطق الاشتراك مركز في تجميع Subscription، والإشعارات تُطلَّق عبر حدث PriceUpdated، مما يضمن فكًا كاملاً.
3. تحليل المتطلبات
(1) حالات الاستخدام الأساسية
| الدور | حالة الاستخدام | الأولوية |
|---|---|---|
| مستخدم (Alice) | تسجيل/تسجيل الدخول | P0 |
| مستخدم | عرض سعر المنتج | P0 |
| مستخدم | اشتراك إشعار تغير السعر | P1 |
| مسؤول | استيراد أسعار دفعي | P0 |
| مسؤول | إدارة CRUD للمنتجات | P0 |
| واجهة أمامية (Bob) | استدعاء API لاسترجاع البيانات | P0 |
| DevOps (Charlie) | مراقبة صحة النظام | P1 |
| النظام | جمع أسعار تلقائي | P2 |
(1) ▶ مثال: نص لترتيب حالات الاستخدام حسب الأولوية
# scripts/prioritize_use_cases.py
use_cases = [
{"role": "User", "action": "register_login", "priority": "P0", "effort": 2},
{"role": "User", "action": "query_price", "priority": "P0", "effort": 1},
{"role": "User", "action": "subscribe_alert", "priority": "P1", "effort": 3},
{"role": "Admin", "action": "bulk_import", "priority": "P0", "effort": 5},
]
# ترتيب حسب الأولوية؛ ضمن نفس المستوى، ترتيب تصاعدي حسب حجم العمل
order = {"P0": 0, "P1": 1, "P2": 2}
sorted_cases = sorted(use_cases, key=lambda x: (order[x["priority"]], x["effort"]))
for uc in sorted_cases:
print(f"[{uc['priority']}] {uc['role']}: {uc['action']} (effort={uc['effort']}d)")
الناتج:
# تم التنفيذ بنجاح
(2) المتطلبات غير الوظيفية
| المتطلب | الهدف | القيود |
|---|---|---|
| QPS | ملايين (مجموعة) | Nginx LB + مرونة K8s |
| زمن الاستجابة P99 | < 50 ms | تخزين مؤقت Redis + DB غير متزامن |
| التوفر | 99.9% | نسخ متعددة + استرداد تلقائي |
| حجم البيانات | ملايين منتجات + عشرات ملايين أسعار | تقسيم PostgreSQL |
4. تصميم نموذج المجال
(1) مخطط ER
erDiagram
User ||--o{ Subscription : has
User ||--o{ Product : creates
User ||--o{ Alert : sets
Product ||--o{ Price : has
Product ||--o{ Alert : watched_by
Subscription ||--o{ Feature : includes
User {
int id PK
string email UK
string hashed_password
string role
datetime created_at
}
Product {
int id PK
string name
string category
float base_price
string sku UK
int user_id FK
datetime created_at
}
Price {
int id PK
int product_id FK
float price
string currency
string source
datetime recorded_at
}
Subscription {
int id PK
int user_id FK
string plan
datetime starts_at
datetime expires_at
}
Alert {
int id PK
int user_id FK
int product_id FK
float target_price
string status
}
(2) تحديد جذور التجميع
| جذر التجميع | الكيانات المضمنة | قواعد الحدود |
|---|---|---|
| User | المستخدم، الاشتراك، التنبيه | المستخدم يمتلك الاشتراكات والتنبيهات |
| Product | المنتج، السعر | المنتج لديه سجل أسعار |
| PriceImport | معلومات الدفعة، حالة الاستيراد | الاستيراد الدفعي معاملة منفصلة |
(1) ▶ مثال: تعريفات أحداث المجال
# domain/events.py
from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
class EventType(Enum):
PRICE_CHANGED = "price_changed"
ALERT_TRIGGERED = "alert_triggered"
USER_SUBSCRIBED = "user_subscribed"
@dataclass
class DomainEvent:
event_type: EventType
aggregate_id: int
occurred_at: datetime = field(default_factory=datetime.utcnow)
payload: dict = field(default_factory=dict)
# الاستخدام: أحداث تغير السعر
price_event = DomainEvent(
event_type=EventType.PRICE_CHANGED,
aggregate_id=42,
payload={"old_price": 299.9, "new_price": 249.9, "currency": "CNY"},
)
الناتج:
# تم التنفيذ بنجاح
5. قرارات الاختيار التقني
(1) مقارنة النماذج
| الحاجة | الخيار أ | الخيار ب | القرار | السبب |
|---|---|---|---|---|
| أطر الويب | FastAPI | Flask/Django | FastAPI | غير متزامن + توثيق تلقائي + فحص أنواع |
| قاعدة البيانات | PostgreSQL | MySQL/MongoDB | PostgreSQL | JSONB + مشغل غير متزامن ناضج |
| التخزين المؤقت | Redis | Memcached | Redis | هياكل بيانات غنية + استمرار |
| طابور المهام | Celery+Redis | RQ/Dramatiq | Celery | نظام بيئي ناضج + مراقبة قوية |
| الاتصال في الوقت الفعلي | WebSocket | SSE | WebSocket | ثنائي الاتجاه + زمن استجابة منخفض |
| الحاويات | Docker | bare metal/VM | Docker | تناسق + تنسيق |
(2) نظرة عامة على معمارية النظام
flowchart TD
Client[العميل / Bob الواجهة الأمامية] --> Nginx[Nginx موازن تحميل]
Nginx --> API1[FastAPI Pod 1]
Nginx --> API2[FastAPI Pod 2]
Nginx --> APIN[FastAPI Pod N]
API1 --> PG_Master[(PostgreSQL الرئيسي)]
API2 --> PG_Master
APIN --> PG_Master
PG_Master --> PG_Replica[(PostgreSQL النسخة)]
API1 --> Redis[(مجموعة Redis)]
API2 --> Redis
APIN --> Redis
Redis --> CW1[Celery Worker 1]
Redis --> CW2[Celery Worker 2]
CW1 --> PG_Master
CW2 --> PG_Master
API1 --> WS[WebSocket Hub]
API2 --> WS
Prometheus[Prometheus] --> API1
Grafana[Grafana] --> Prometheus
6. استراتيجية إصدارات API
(1) إصدار URL مقابل إصدار Header
| البُعد | إصدار URL /api/v1/ |
إصدار Header Accept: v=1 |
|---|---|---|
| الوضوح | عالٍ (URL صريح) | منخفض (مخفي في الـ header) |
| التوجيه | بسيط (قائم على البادئة) | معقد (يتطلب تحليل middleware) |
| التخزين المؤقت | تخزين مؤقت URL مستقل | يتطلب Vary Header |
| Swagger | تجميع تلقائي | يتطلب إعداد يدوي |
| مُوصى به | ✅ كيف يستخدم PriceTracker | مناسب لـ APIs الداخلية |
(1) ▶ مثال: هيكل توجيه إصدار URL
from fastapi import APIRouter
# مسارات V1
v1_router = APIRouter(prefix="/api/v1", tags=["v1"])
v1_products = APIRouter(prefix="/products", tags=["products"])
v1_prices = APIRouter(prefix="/prices", tags=["prices"])
v1_auth = APIRouter(prefix="/auth", tags=["auth"])
# مسارات V2 (مستقبلية)
v2_router = APIRouter(prefix="/api/v2", tags=["v2"])
# تثبيت المسارات
app.include_router(v1_auth)
app.include_router(v1_products, dependencies=[Depends(get_current_user)])
app.include_router(v1_prices, dependencies=[Depends(get_current_user)])
app.include_router(v1_router)
الناتج:
# تم التنفيذ بنجاح
❓ أسئلة شائعة
Product هو جذر التجميع، لا يمكن تعديل Price إلا من خلال Product ولا يمكن إضافته أو حذفه بشكل مستقل، مما يضمن تناسق البيانات.publish_price_updated_event.delay(product_id, new_price))، بينما نهج أكثر تعقيدًا يستخدم طوابير رسائل (RabbitMQ/Kafka).📖 ملخص
- تحليل المتطلبات يميز بين المتطلبات الوظيفية وغير الوظيفية ويوضح الأولويات والقيود
- DDD: حدد جذور التجميع (User، Product، PriceImport)، وعرّف حدود المعاملة وقواعد التناسق
- اختيار تقني بناءً على حالة الاستخدام: FastAPI (غير متزامن) + PostgreSQL (JSONB) + Redis (هياكل بيانات) + Celery (طوابير مهام)
- إصدارات API تستخدم بادئة URL
/api/v1/، وهي بسيطة وبديهية وصديقة للتخزين المؤقت - طوبولوجيا النشر: Nginx موازن تحميل → FastAPI Pods → PostgreSQL رئيسي-تابع → مجموعة Redis → Celery Workers
📝 تمارين
- سؤال أساسي (الصعوبة: ⭐): ارسم مخطط معمارية نظام لـ PriceTracker (يشمل FastAPI وPostgreSQL وRedis وCelery وNginx)، وسمِّ مسؤوليات كل مكون وتدفق البيانات. تلميح: مخطط تدفق Mermaid
- مسألة متقدمة (الصعوبة ⭐⭐): صمّم مخطط ER لنموذج مجال PriceTracker، حدد ثلاثة جذور تجميع (User، Product، PriceImport)، وعرّف الكيانات المضمنة في كل تجميع وقواعد حدودها. تلميح: مخطط erDiagram Mermaid + جدول جذور التجميع
- تحدٍ (الصعوبة: ⭐⭐⭐): أكمِل المخطط المعماري الكامل لـ PriceTracker — بما في ذلك وثيقة المتطلبات (حالات الاستخدام الأساسية + المتطلبات غير الوظيفية)، جدول مقارنة تقنية، كود هيكل توجيه إصدارات API، ومخطط طوبولوجيا النشر (يشمل قواعد بيانات رئيسي-تابع، طبقات تخزين مؤقت، ومجموعة Celery). تلميح: ادمج جميع المحتوى المغطى في هذا الدرس.
---|



