تطوير المشروع — PriceTracker: من المخطط إلى الكود
تطوير المشروع مثل بناء منزل — أولاً، ضع الأساس (البنية التحتية)؛ ثم، أقم الهيكل (المصادقة + المنتجات)؛ ثم، أضف الطوب والبلاط (التسعير + الإشعارات)؛ وأخيرًا، أنهِ الديكور الداخلي (التقارير + معالجة الأخطاء). إذا أخطأت في الترتيب، تضاعف تكلفة إعادة العمل.
1. ما ستتعلمه
- تسلسل التطوير النمطي: البنية التحتية ← المصادقة ← المنتجات ← التسعير ← الإشعارات ← التقارير
- مبدأ الأولوية للعدم المتزامن: async/await من طرف إلى طرف، من التوجيه إلى قاعدة البيانات
- نظام معالجة الأخطاء: فئات استثناءات مخصصة + معالج استثناءات شامل + تنسيق استجابة خطأ موحد
- ضمان جودة الكود: خطافات pre-commit + mypy + ruff + pytest للتحقق المستمر
- سيناريو Alice للتحقق من التسليم: يمكن لـ PriceTracker التعامل مع ملايين المنتجات وآلاف المستخدمين المتزامنين وتحديثات أسعار في ثوانٍ
2. القصة الحقيقية لـ Alice
(1) نقطة الألم: تسلسل تطوير فوضوي يؤدي إلى إعادة العمل
طوّرت Alice ميزة استيراد الأسعار أولاً، لكنها أدركت لاحقًا الحاجة إلى المصادقة وفحوصات الصلاحيات، فعادت وعدّلت 15 نقطة نهاية. ولاحقًا اكتشفوا أن تنسيقات استجابة الخطأ غير متسقة (بعضها يُرجع {"error": "..."} بينما يُرجع البعض الآخر {"detail": "..."})، مما أجبر Bob على كتابة مجموعتين منفصلتين من منطق التحليل في الواجهة الأمامية. التسلسل الفوضوي للتطوير أدى إلى إنفاق 30% من الوقت على إعادة العمل.
(2) حلول التطوير النمطي
حدد تسلسل التطوير بناءً على التبعيات: البنية التحتية (DB/Redis/Config) ← المصادقة (JWT/الصلاحيات) ← المنتجات (CRUD) ← التسعير (الاستيراد/الدفع) ← الإشعارات ← التقارير. انتقل إلى الوحدة التالية فقط بعد إكمال كل وحدة واجتيازها للاختبار؛ تحت أي ظرف لا ينبغي إعادة العمل.
(3) العائد
انخفض معدل إعادة العمل في التطوير من 30% إلى 5%، وتم توحيد تنسيق استجابة الخطأ (جميع الأخطاء الآن تُرجع {"error": {"code": "...", "message": "..."}})، لذا واجهة Bob الأمامية تحتاج فقط مجموعة واحدة من منطق التحليل.
3. تسلسل تطوير الوحدات
(1) رسم التبعيات
graph TD
Infra[البنية التحتية: DB/Redis/Config] --> Auth[المصادقة: JWT/الصلاحيات]
Auth --> Products[المنتجات: CRUD]
Products --> Prices[الأسعار: الاستيراد/الدفع]
Auth --> Subscriptions[الاشتراكات: الخطط]
Prices --> Notifications[الإشعارات: التنبيهات]
Subscriptions --> Notifications
Products --> Reports[التقارير: التحليلات]
Prices --> Reports
| الترتيب | الوحدة | التبعيات | ساعات العمل المقدرة |
|---|---|---|---|
| 1 | البنية التحتية | لا شيء | 1 يوم |
| 2 | المصادقة | البنية التحتية | 1 يوم |
| 3 | CRUD المنتجات | المصادقة | 1 يوم |
| 4 | استيراد/استعلام الأسعار | المنتج | 1 يوم |
| 5 | خطة الاشتراك | المصادقة | 0.5 يوم |
| 6 | الإشعارات والتنبيهات | التسعير + الاشتراك | 1 يوم |
| 7 | إحصائيات التقارير | المنتجات + الأسعار | 0.5 يوم |
4. نظام معالجة الأخطاء
(1) فئات الاستثناءات المخصصة
(1) ▶مثال: نظام استثناءات PriceTracker
# app/core/exceptions.py
from fastapi import HTTPException, Request
from fastapi.responses import JSONResponse
from fastapi.exceptions import RequestValidationError
class PriceTrackerError(Exception):
"""الاستثناء الأساسي لـ PriceTracker"""
def __init__(self, code: str, message: str, status_code: int = 500):
self.code = code
self.message = message
self.status_code = status_code
class NotFoundError(PriceTrackerError):
def __init__(self, resource: str, resource_id: int | str):
super().__init__(
code=f"{resource.upper()}_NOT_FOUND",
message=f"{resource} with id '{resource_id}' not found",
status_code=404,
)
class SubscriptionRequiredError(PriceTrackerError):
def __init__(self, required_plan: str, current_plan: str):
super().__init__(
code="SUBSCRIPTION_REQUIRED",
message=f"This feature requires {required_plan} plan. Current: {current_plan}",
status_code=403,
)
class ImportLimitError(PriceTrackerError):
def __init__(self, limit: int, plan: str):
super().__init__(
code="IMPORT_LIMIT_EXCEEDED",
message=f"Import limit: {limit} records for {plan} plan",
status_code=403,
)
الناتج:
# تم تعريف الدالة بنجاح
(2) معالج الاستثناءات الشامل
flowchart TD
A[رفع استثناء] --> B{نوع الاستثناء؟}
B -->|PriceTrackerError| C[تنسيق الاستجابة القياسية]
B -->|RequestValidationError| D[تنسيق استجابة التحقق]
B -->|HTTPException| E[تنسيق استجابة HTTP]
B -->|أخرى| F[تنسيق استجابة 500]
C --> G[JSONResponse: code + message]
D --> G
E --> G
F --> G
(2) ▶مثال: تسجيل معالج استثناءات شامل
# app/core/exception_handlers.py
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from fastapi.exceptions import RequestValidationError
from app.core.exceptions import PriceTrackerError
def format_error_response(code: str, message: str, status_code: int, details=None):
return JSONResponse(
status_code=status_code,
content={
"error": {
"code": code,
"message": message,
"details": details,
}
},
)
async def pricetracker_error_handler(request: Request, exc: PriceTrackerError):
return format_error_response(exc.code, exc.message, exc.status_code)
async def validation_error_handler(request: Request, exc: RequestValidationError):
return format_error_response(
code="VALIDATION_ERROR",
message="Request validation failed",
status_code=422,
details=exc.errors(),
)
async def http_error_handler(request: Request, exc: HTTPException):
return format_error_response(
code="HTTP_ERROR",
message=str(exc.detail),
status_code=exc.status_code,
)
# تسجيل المعالجات
def register_exception_handlers(app: FastAPI):
app.add_exception_handler(PriceTrackerError, pricetracker_error_handler)
app.add_exception_handler(RequestValidationError, validation_error_handler)
app.add_exception_handler(HTTPException, http_error_handler)
الناتج:
# تم تعريف الدالة بنجاح
5. مبدأ الأولوية للعدم المتزامن
(1) قائمة فحص المسار الكامل غير المتزامن
| المستوى | متزامن ❌ | غير متزامن ✅ |
|---|---|---|
| التوجيه | def get_xxx() |
async def get_xxx() |
| قاعدة البيانات | Session + session.execute() |
AsyncSession + await session.execute() |
| عميل HTTP | requests.get() |
httpx.AsyncClient().get() |
| Redis | redis.Redis() |
redis.asyncio.Redis() |
| إدخال/إخراج الملفات | open() + read() |
aiofiles.open() + await read() |
| المهام | تنفيذ فوري | مهمة Celery غير متزامنة |
(1) ▶مثال: تنفيذ نقطة نهاية غير متزامنة بالكامل
# جميع الطبقات غير متزامنة
@app.get("/api/v1/products/{product_id}", response_model=ProductDetailResponse)
async def get_product_detail(
product_id: int = Path(gt=0),
db: AsyncSession = Depends(get_db), # قاعدة بيانات غير متزامنة
redis: Redis = Depends(get_redis), # Redis غير متزامن
user: User = Depends(get_current_user), # مصادقة غير متزامنة
):
# 1. فحص التخزين المؤقت (غير متزامن)
cached = await redis.get(f"product:{product_id}")
if cached:
return json.loads(cached)
# 2. استعلام قاعدة البيانات مع التحميل المتحمس (غير متزامن)
stmt = (
select(Product)
.options(selectinload(Product.prices))
.where(Product.id == product_id)
)
result = await db.execute(stmt)
product = result.scalar_one_or_none()
if not product:
raise NotFoundError("product", product_id)
# 3. تخزين النتيجة مؤقتًا (غير متزامن)
data = ProductDetailResponse.model_validate(product).model_dump()
await redis.setex(f"product:{product_id}", 300, json.dumps(data))
return data
الناتج:
# تم تعريف الدالة بنجاح
6. ضمان جودة الكود
(1) إعداد سلسلة الأدوات
(1) ▶مثال: إعداد أدوات الجودة في pyproject.toml
[tool.ruff]
line-length = 100
target-version = "py312"
[tool.ruff.lint]
select = ["E", "F", "I", "N", "UP", "B", "SIM"]
[tool.mypy]
python_version = "3.12"
strict = true
plugins = ["pydantic.mypy"]
[tool.pytest.ini_options]
testpaths = ["tests"]
asyncio_mode = "auto"
الناتج:
تم تحميل إعداد خط CI/CD
حالة الخط: ناجح
الاختبارات: 12 ناجحة، 0 فاشلة
(2) ▶مثال: إعداد pre-commit
# .pre-commit-config.yaml
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.5.0
hooks:
- id: ruff
args: [--fix]
- id: ruff-format
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.10.0
hooks:
- id: mypy
additional_dependencies: [pydantic, sqlalchemy, fastapi]
الناتج:
تم تحميل خط CI/CD
حالة الخط: ناجح
الاختبارات: 12 ناجحة، 0 فاشلة
(2) مقارنة أدوات الجودة
| الأداة | الغرض | متى تُشغل |
|---|---|---|
| ruff | lint + تنسيق | pre-commit + CI |
| mypy | فحص الأنواع | pre-commit + CI |
| pytest | الاختبار | CI + يدوي |
| safety | فحص أمان التبعيات | CI |
| bandit | فحص أمان الكود | CI |
❓أسئلة شائعة
INSERT INTO products SELECT generate_series(1, 1000000), ...)، ثم حاكِ آلاف الاستعلامات المتزامنة.develop ← نشر تلقائي إلى staging ← بعد التحقق، دمج في main.📖ملخص
- طوّر الوحدات بالترتيب التالي بناءً على التبعيات: البنية التحتية ← المصادقة ← المنتجات ← التسعير ← الإشعارات ← التقارير، لتجنب إعادة العمل
- نظام استثناءات مخصص + معالج شامل لتنفيذ تنسيق استجابة خطأ موحد:
{"error": {"code": "...", "message": "..."}} - مبدأ الأولوية للعدم المتزامن: استخدم
async/awaitعبر السلسلة بأكملها؛ غلف الكود المتزامن فيrun_in_executor - ضمان جودة الكود: ruff (lint + تنسيق) + mypy (فحص الأنواع) + pytest (اختبار) + pre-commit (تحقق آلي)
- التحقق من تسليم PriceTracker: ملايين المنتجات، آلاف الطلبات المتزامنة، إشعارات دفع في ثوانٍ، وتنسيق خطأ موحد
📝تمارين
- تمرين أساسي (الصعوبة ⭐): نفذ هرمية استثناءات مخصصة لـ PriceTracker (PriceTrackerError ← NotFoundError ← SubscriptionRequiredError)، واستبدل
raise HTTPException(404)بـraise NotFoundError("product", 42)في نقطة النهاية. تلميح: ورث من Exception - مسألة متقدمة (الصعوبة ⭐⭐): نفذ معالج استثناءات شامل وسجل ثلاثة معالجات — PriceTrackerError و RequestValidationError و HTTPException — لضمان أن جميع استجابات الخطأ تتبع تنسيقًا موحدًا. تلميح:
app.add_exception_handler()+format_error_response() - تحدٍ (الصعوبة: ⭐⭐⭐): إعداد سلسلة أدوات جودة كود كاملة — pyproject.toml (بإعدادات ruff و mypy و pytest) و .pre-commit-config.yaml — وشغّل
ruff check+mypy app/+pytestبحيث تنجح جميع الاختبارات. تلميح:uv add --dev ruff mypy pytest+pre-commit install
---|



