404 Not Found

404 Not Found


nginx

تطوير المشروع — PriceTracker: من المخطط إلى الكود

تطوير المشروع مثل بناء منزل — أولاً، ضع الأساس (البنية التحتية)؛ ثم، أقم الهيكل (المصادقة + المنتجات)؛ ثم، أضف الطوب والبلاط (التسعير + الإشعارات)؛ وأخيرًا، أنهِ الديكور الداخلي (التقارير + معالجة الأخطاء). إذا أخطأت في الترتيب، تضاعف تكلفة إعادة العمل.

1. ما ستتعلمه


2. القصة الحقيقية لـ Alice

(1) نقطة الألم: تسلسل تطوير فوضوي يؤدي إلى إعادة العمل

طوّرت Alice ميزة استيراد الأسعار أولاً، لكنها أدركت لاحقًا الحاجة إلى المصادقة وفحوصات الصلاحيات، فعادت وعدّلت 15 نقطة نهاية. ولاحقًا اكتشفوا أن تنسيقات استجابة الخطأ غير متسقة (بعضها يُرجع {"error": "..."} بينما يُرجع البعض الآخر {"detail": "..."})، مما أجبر Bob على كتابة مجموعتين منفصلتين من منطق التحليل في الواجهة الأمامية. التسلسل الفوضوي للتطوير أدى إلى إنفاق 30% من الوقت على إعادة العمل.

(2) حلول التطوير النمطي

حدد تسلسل التطوير بناءً على التبعيات: البنية التحتية (DB/Redis/Config) ← المصادقة (JWT/الصلاحيات) ← المنتجات (CRUD) ← التسعير (الاستيراد/الدفع) ← الإشعارات ← التقارير. انتقل إلى الوحدة التالية فقط بعد إكمال كل وحدة واجتيازها للاختبار؛ تحت أي ظرف لا ينبغي إعادة العمل.

(3) العائد

انخفض معدل إعادة العمل في التطوير من 30% إلى 5%، وتم توحيد تنسيق استجابة الخطأ (جميع الأخطاء الآن تُرجع {"error": {"code": "...", "message": "..."}})، لذا واجهة Bob الأمامية تحتاج فقط مجموعة واحدة من منطق التحليل.


3. تسلسل تطوير الوحدات

(1) رسم التبعيات

100%
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

PYTHON
# 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,
        )

الناتج:

TEXT
# تم تعريف الدالة بنجاح

(2) معالج الاستثناءات الشامل

100%
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) ▶مثال: تسجيل معالج استثناءات شامل

PYTHON
# 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)

الناتج:

TEXT
# تم تعريف الدالة بنجاح

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) ▶مثال: تنفيذ نقطة نهاية غير متزامنة بالكامل

PYTHON
# جميع الطبقات غير متزامنة
@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

الناتج:

TEXT
# تم تعريف الدالة بنجاح

6. ضمان جودة الكود

(1) إعداد سلسلة الأدوات

(1) ▶مثال: إعداد أدوات الجودة في pyproject.toml

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"

الناتج:

TEXT
تم تحميل إعداد خط CI/CD
حالة الخط: ناجح
الاختبارات: 12 ناجحة، 0 فاشلة

(2) ▶مثال: إعداد pre-commit

YAML
# .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]

الناتج:

TEXT
تم تحميل خط CI/CD
حالة الخط: ناجح
الاختبارات: 12 ناجحة، 0 فاشلة

(2) مقارنة أدوات الجودة

الأداة الغرض متى تُشغل
ruff lint + تنسيق pre-commit + CI
mypy فحص الأنواع pre-commit + CI
pytest الاختبار CI + يدوي
safety فحص أمان التبعيات CI
bandit فحص أمان الكود CI

❓أسئلة شائعة

س هل ترتيب التطوير مهم حقًا؟
ج مهم جدًا. طوّر الوحدات المعتمد عليها أولاً؛ الوحدات المطورة لاحقًا يمكنها استخدام الوظائف المكتملة مباشرة، مما ي avoids إعادة العمل. البنية التحتية ← المصادقة ← منطق الأعمال هي قاعدة ذهبية.
س كيف أختار بين الاستثناءات المخصصة و HTTPException؟
ج استخدم الاستثناءات المخصصة (بما فيها code و message) لأخطاء الأعمال، واستخدم HTTPException لأخطاء مستوى HTTP. الاستثناءات المخصصة تُنسق بشكل موحد باستخدام معالج شامل.
س أليس pre-commit بطيئًا جدًا؟
ج Ruff سريع للغاية (تنفيذ Rust)، بينما Mypy أبطأ قليلاً لكنه يؤدي فحوصات تزايدية بسرعة. الوقت الإجمالي أقل من 5 ثوانٍ، ومقابل ذلك تحصل على ضمان جودة الكود مع كل commit — وهو أكثر كفاءة بكثير من إصلاح المشاكل بعد فشل CI.
س هل وضع strict في mypy يستحق العناء؟
ج نعم، يستحق. الوضع الصارم يلتقط المزيد من أخطاء الأنواع؛ رغم أن جهد الإعداد الأولي مرتفع، فإنه يقلل أخطاء وقت التشغيل على المدى الطويل. تلميحات الأنواع في FastAPI + Pydantic مناسبة بشكل طبيعي للوضع الصارم.
س كيف تتحقق من "مليون عنصر + آلاف الطلبات المتزامنة"؟
ج شغّل اختبارات حمل باستخدام Locust أو k6. أولاً، حضّر مليون سجل اختبار (INSERT INTO products SELECT generate_series(1, 1000000), ...)، ثم حاكِ آلاف الاستعلامات المتزامنة.
س كيف يجب تصميم عملية مراجعة الكود؟
ج يُرسل Bob/Alice طلب سحب ← CI يشغل تلقائيًا lint واختبارات ← Charlie يراجع الكود ← دمج في develop ← نشر تلقائي إلى staging ← بعد التحقق، دمج في main.

📖ملخص


📝تمارين

  1. تمرين أساسي (الصعوبة ⭐): نفذ هرمية استثناءات مخصصة لـ PriceTracker (PriceTrackerError ← NotFoundError ← SubscriptionRequiredError)، واستبدل raise HTTPException(404) بـ raise NotFoundError("product", 42) في نقطة النهاية. تلميح: ورث من Exception
  2. مسألة متقدمة (الصعوبة ⭐⭐): نفذ معالج استثناءات شامل وسجل ثلاثة معالجات — PriceTrackerError و RequestValidationError و HTTPException — لضمان أن جميع استجابات الخطأ تتبع تنسيقًا موحدًا. تلميح: app.add_exception_handler() + format_error_response()
  3. تحدٍ (الصعوبة: ⭐⭐⭐): إعداد سلسلة أدوات جودة كود كاملة — pyproject.toml (بإعدادات ruff و mypy و pytest) و .pre-commit-config.yaml — وشغّل ruff check + mypy app/ + pytest بحيث تنجح جميع الاختبارات. تلميح: uv add --dev ruff mypy pytest + pre-commit install

---|

Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%