أنماط تصميم بايثون لعمارة نظيفة

أنشئ تطبيقات برمجة بايثون قابلة للصيانة باستخدام أنماط تصميم SOLID

البنية النظيفة غيرت طريقة مطوري البرمجيات في بناء تطبيقات قابلة للتوسع والصيانة من خلال التركيز على فصل المهام وإدارة الاعتماديات.

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

قاعة مؤتمر تقنية مزدهرة

فهم البنية النظيفة في بايثون

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

الفلسفة الأساسية

القاعدة الأساسية بسيطة ولكن قوية: يجب ألا تعتمد منطق العمل على البنية التحتية. يجب أن تعمل الكيانات، وحالات الاستخدام، وقواعد العمل الخاصة بك بشكل مستقل عن استخدامك لـ PostgreSQL أو MongoDB، FastAPI أو Flask، AWS أو Azure.

في بايثون، تتوافق هذه الفلسفة تمامًا مع طبيعة “النوعية الدودية” وبرمجة البروتوكولات في اللغة، مما يسمح بفصل نظيف دون الحاجة إلى الالتزامات التي تتطلبها اللغات المُصنفة بشكل صارم.

طبقات البنية النظيفة الأربعة

طبقة الكيانات (النطاق): كائنات العمل النقي مع قواعد العمل على مستوى المؤسسة. هذه هي POJOs (Plain Old Python Objects) بدون اعتماديات خارجية.

طبقة حالات الاستخدام (التطبيق): قواعد العمل الخاصة بالتطبيق التي تنسق تدفق البيانات بين الكيانات والخدمات الخارجية.

طبقة محولات الواجهات: تحول البيانات بين التنسيق الأكثر ملاءمة لحالات الاستخدام والكيانات، والتنسيق المطلوب من قبل الوكالات الخارجية.

طبقة الإطارات والمحركات: جميع التفاصيل الخارجية مثل قواعد البيانات، الإطارات الويب، والخدمات الخارجية.

مبادئ SOLID في بايثون

تُشكل مبادئ SOLID أساس البنية النظيفة. دعنا نستكشف كيف تتجلى كل مبدأ في بايثون. لمراجعة شاملة للأنماط التصميمية في بايثون، راجع دليل الأنماط التصميمية في بايثون.

مبدأ المسؤولية الواحدة (SRP)

يجب أن يكون لكل فئة سبب واحد لتغييرها:

# سيء: مسؤوليات متعددة
class UserManager:
    def create_user(self, user_data):
        # إنشاء مستخدم
        pass
    
    def send_welcome_email(self, user):
        # إرسال بريد
        pass
    
    def log_creation(self, user):
        # تسجيل في ملف
        pass

# جيد: مسؤوليات منفصلة
class UserService:
    def __init__(self, repository, email_service, logger):
        self.repository = repository
        self.email_service = email_service
        self.logger = logger
    
    def create_user(self, user_data):
        user = User(**user_data)
        self.repository.save(user)
        self.email_service.send_welcome(user)
        self.logger.info(f"تم إنشاء المستخدم: {user.id}")
        return user

مبدأ المفتوح/المغلق (OCP)

يجب أن تكون الكيانات البرمجية مفتوحة للتوسع ولكن مغلقة للتعديل:

from abc import ABC, abstractmethod
from typing import Protocol

# استخدام البروتوكول (Python 3.8+)
class PaymentProcessor(Protocol):
    def process_payment(self, amount: float) -> bool:
        ...

class CreditCardProcessor:
    def process_payment(self, amount: float) -> bool:
        # منطق بطاقة الائتمان
        return True

class PayPalProcessor:
    def process_payment(self, amount: float) -> bool:
        # منطق باي بال
        return True

# قابلة للتوسع دون تعديل الكود الحالي
class CryptoProcessor:
    def process_payment(self, amount: float) -> bool:
        # منطق العملات المشفرة
        return True

مبدأ استبدال لisko (LSP)

يجب أن يكون المُستبدلون قابلين للاستبدال دون أن يؤثر ذلك على البرنامج:

from abc import ABC, abstractmethod

class DataStore(ABC):
    @abstractmethod
    def save(self, key: str, value: str) -> None:
        pass
    
    @abstractmethod
    def get(self, key: str) -> str:
        pass

class PostgreSQLStore(DataStore):
    def save(self, key: str, value: str) -> None:
        # تنفيذ PostgreSQL
        pass
    
    def get(self, key: str) -> str:
        # تنفيذ PostgreSQL
        return ""

class RedisStore(DataStore):
    def save(self, key: str, value: str) -> None:
        # تنفيذ Redis
        pass
    
    def get(self, key: str) -> str:
        # تنفيذ Redis
        return ""

# يمكن استخدامها تبادلًا
def process_data(store: DataStore, key: str, value: str):
    store.save(key, value)
    return store.get(key)

مبدأ تقسيم الواجهات (ISP)

لا يجب إجبار العملاء على الاعتماد على واجهات لا يستخدمونها:

# سيء: واجهة سمينة
class Worker(ABC):
    @abstractmethod
    def work(self): pass
    
    @abstractmethod
    def eat(self): pass
    
    @abstractmethod
    def sleep(self): pass

# جيد: واجهات منفصلة
class Workable(Protocol):
    def work(self) -> None: ...

class Eatable(Protocol):
    def eat(self) -> None: ...

class Human:
    def work(self) -> None:
        print("العمل")
    
    def eat(self) -> None:
        print("الأكل")

class Robot:
    def work(self) -> None:
        print("العمل")
    # لا حاجة لطريقة الأكل

مبدأ انعكاس الاعتماديات (DIP)

لا يجب أن تعتمد الوحدات العليا على الوحدات الدنيا. يجب أن تعتمد كلتا الوحدتين على المجرات:

from typing import Protocol

# المجرة
class EmailSender(Protocol):
    def send(self, to: str, subject: str, body: str) -> None:
        ...

# وحدة منخفضة
class SMTPEmailSender:
    def send(self, to: str, subject: str, body: str) -> None:
        # تنفيذ SMTP
        pass

# وحدة عالية تعتمد على المجرة
class UserRegistrationService:
    def __init__(self, email_sender: EmailSender):
        self.email_sender = email_sender
    
    def register(self, email: str, name: str):
        # منطق التسجيل
        self.email_sender.send(
            to=email,
            subject="مرحبا!",
            body=f"مرحبا {name}"
        )

نمط المستودع: تجريد الوصول إلى البيانات

يقدم نمط المستودع واجهة تشبه المجموعة لاستخدام كائنات النطاق، مع إخفاء تفاصيل تخزين البيانات.

تنفيذ المستودع الأساسي

from abc import ABC, abstractmethod
from typing import List, Optional
from dataclasses import dataclass
from uuid import UUID, uuid4

@dataclass
class User:
    id: UUID
    email: str
    name: str
    is_active: bool = True

class UserRepository(ABC):
    @abstractmethod
    def save(self, user: User) -> User:
        pass
    
    @abstractmethod
    def get_by_id(self, user_id: UUID) -> Optional[User]:
        pass
    
    @abstractmethod
    def get_by_email(self, email: str) -> Optional[User]:
        pass
    
    @abstractmethod
    def list_all(self) -> List[User]:
        pass
    
    @abstractmethod
    def delete(self, user_id: UUID) -> bool:
        pass

تنفيذ المستودع باستخدام SQLAlchemy

from sqlalchemy import create_engine, Column, String, Boolean
from sqlalchemy.dialects.postgresql import UUID as PGUUID
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, Session

Base = declarative_base()

class UserModel(Base):
    __tablename__ = 'users'
    
    id = Column(PGUUID(as_uuid=True), primary_key=True)
    email = Column(String, unique=True, nullable=False)
    name = Column(String, nullable=False)
    is_active = Column(Boolean, default=True)

class SQLAlchemyUserRepository(UserRepository):
    def __init__(self, session: Session):
        self.session = session
    
    def save(self, user: User) -> User:
        user_model = UserModel(
            id=user.id,
            email=user.email,
            name=user.name,
            is_active=user.is_active
        )
        self.session.add(user_model)
        self.session.commit()
        return user
    
    def get_by_id(self, user_id: UUID) -> Optional[User]:
        user_model = self.session.query(UserModel).filter(
            UserModel.id == user_id
        ).first()
        
        if not user_model:
            return None
        
        return User(
            id=user_model.id,
            email=user_model.email,
            name=user_model.name,
            is_active=user_model.is_active
        )
    
    def get_by_email(self, email: str) -> Optional[User]:
        user_model = self.session.query(UserModel).filter(
            UserModel.email == email
        ).first()
        
        if not user_model:
            return None
        
        return User(
            id=user_model.id,
            email=user_model.email,
            name=user_model.name,
            is_active=user_model.is_active
        )
    
    def list_all(self) -> List[User]:
        users = self.session.query(UserModel).all()
        return [
            User(
                id=u.id,
                email=u.email,
                name=u.name,
                is_active=u.is_active
            )
            for u in users
        ]
    
    def delete(self, user_id: UUID) -> bool:
        result = self.session.query(UserModel).filter(
            UserModel.id == user_id
        ).delete()
        self.session.commit()
        return result > 0

مستودع في الذاكرة للاختبار

class InMemoryUserRepository(UserRepository):
    def __init__(self):
        self.users: dict[UUID, User] = {}
    
    def save(self, user: User) -> User:
        self.users[user.id] = user
        return user
    
    def get_by_id(self, user_id: UUID) -> Optional[User]:
        return self.users.get(user_id)
    
    def get_by_email(self, email: str) -> Optional[User]:
        for user in self.users.values():
            if user.email == email:
                return user
        return None
    
    def list_all(self) -> List[User]:
        return list(self.users.values())
    
    def delete(self, user_id: UUID) -> bool:
        if user_id in self.users:
            del self.users[user_id]
            return True
        return False

طبقة الخدمة: تنسيق منطق العمل

تُنفذ طبقة الخدمة حالات الاستخدام وتنسق تدفق البيانات بين مستودعات الخدمة الخارجية والمنطق النطاق.

from typing import Optional
from uuid import uuid4

class UserAlreadyExistsError(Exception):
    pass

class UserNotFoundError(Exception):
    pass

class UserService:
    def __init__(
        self,
        user_repository: UserRepository,
        email_service: EmailSender,
        event_publisher: 'EventPublisher'
    ):
        self.user_repository = user_repository
        self.email_service = email_service
        self.event_publisher = event_publisher
    
    def register_user(self, email: str, name: str) -> User:
        # التحقق من وجود المستخدم
        existing_user = self.user_repository.get_by_email(email)
        if existing_user:
            raise UserAlreadyExistsError(f"المستخدم ذو البريد الإلكتروني {email} موجود بالفعل")
        
        # إنشاء مستخدم جديد
        user = User(
            id=uuid4(),
            email=email,
            name=name,
            is_active=True
        )
        
        # حفظ في المستودع
        user = self.user_repository.save(user)
        
        # إرسال بريد ترحيب
        self.email_service.send(
            to=user.email,
            subject="مرحبا!",
            body=f"مرحبا {user.name}، ترحيبًا بكم على منصتنا!"
        )
        
        # نشر الحدث
        self.event_publisher.publish('user.registered', {
            'user_id': str(user.id),
            'email': user.email
        })
        
        return user
    
    def deactivate_user(self, user_id: UUID) -> User:
        user = self.user_repository.get_by_id(user_id)
        if not user:
            raise UserNotFoundError(f"المستخدم {user_id} غير موجود")
        
        user.is_active = False
        user = self.user_repository.save(user)
        
        self.event_publisher.publish('user.deactivated', {
            'user_id': str(user.id)
        })
        
        return user

حقن الاعتماديات في بايثون

تُسهل الطبيعة الديناميكية لبايثون حقن الاعتماديات دون الحاجة إلى الإطارات الثقيلة.

حقن الاعتماديات عبر المُنشئ

class OrderService:
    def __init__(
        self,
        order_repository: 'OrderRepository',
        payment_processor: PaymentProcessor,
        notification_service: 'NotificationService'
    ):
        self.order_repository = order_repository
        self.payment_processor = payment_processor
        self.notification_service = notification_service
    
    def place_order(self, order_data: dict):
        # استخدام الاعتماديات المحقونة
        pass

حاوية الاعتماديات البسيطة

from typing import Dict, Type, Callable, Any

class Container:
    def __init__(self):
        self._services: Dict[Type, Callable] = {}
        self._singletons: Dict[Type, Any] = {}
    
    def register(self, interface: Type, factory: Callable):
        self._services[interface] = factory
    
    def register_singleton(self, interface: Type, instance: Any):
        self._singletons[interface] = instance
    
    def resolve(self, interface: Type):
        if interface in self._singletons:
            return self._singletons[interface]
        
        factory = self._services.get(interface)
        if factory:
            return factory(self)
        
        raise ValueError(f"لا يوجد تسجيل متوفر لـ {interface}")

# الاستخدام
def create_container() -> Container:
    container = Container()
    
    # تسجيل الخدمات
    container.register_singleton(
        Session,
        sessionmaker(bind=create_engine('postgresql://...'))()
    )
    
    container.register(
        UserRepository,
        lambda c: SQLAlchemyUserRepository(c.resolve(Session))
    )
    
    container.register(
        EmailSender,
        lambda c: SMTPEmailSender()
    )
    
    container.register(
        UserService,
        lambda c: UserService(
            c.resolve(UserRepository),
            c.resolve(EmailSender),
            c.resolve(EventPublisher)
        )
    )
    
    return container

البنية السداسية (الports والadapters)

تضع البنية السداسية منطق العمل في المركز مع adapters التي تتعامل مع الاتصالات الخارجية.

تعريف Ports (الواجهات)

# port المدخل (الأساسي)
class CreateUserUseCase(Protocol):
    def execute(self, request: 'CreateUserRequest') -> 'CreateUserResponse':
        ...

# port المخرج (الثانوي)
class UserPersistencePort(Protocol):
    def save(self, user: User) -> User:
        ...
    
    def find_by_email(self, email: str) -> Optional[User]:
        ...

تنفيذ Adapters

from pydantic import BaseModel, EmailStr

# adapter المدخل (واجهة REST)
from fastapi import FastAPI, Depends, HTTPException

class CreateUserRequest(BaseModel):
    email: EmailStr
    name: str

class CreateUserResponse(BaseModel):
    id: str
    email: str
    name: str

app = FastAPI()

@app.post("/users", response_model=CreateUserResponse)
def create_user(
    request: CreateUserRequest,
    user_service: UserService = Depends(get_user_service)
):
    try:
        user = user_service.register_user(
            email=request.email,
            name=request.name
        )
        return CreateUserResponse(
            id=str(user.id),
            email=user.email,
            name=user.name
        )
    except UserAlreadyExistsError as e:
        raise HTTPException(status_code=400, detail=str(e))

# adapter المخرج (قاعدة البيانات)
# تم تنفيذه بالفعل كـ SQLAlchemyUserRepository

أنماط التصميم الموجهة بالمنطقة

الكائنات القيمية

الكائنات غير القابلة للتغيير المُعرفة بخصائصها:

from dataclasses import dataclass
from typing import Pattern
import re

@dataclass(frozen=True)
class Email:
    value: str
    
    EMAIL_PATTERN: Pattern = re.compile(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$')
    
    def __post_init__(self):
        if not self.EMAIL_PATTERN.match(self.value):
            raise ValueError(f"بريد إلكتروني غير صحيح: {self.value}")
    
    def __str__(self):
        return self.value

@dataclass(frozen=True)
class Money:
    amount: float
    currency: str
    
    def __post_init__(self):
        if self.amount < 0:
            raise ValueError("لا يمكن أن يكون المبلغ سالبًا")
        if self.currency not in ['USD', 'EUR', 'GBP']:
            raise ValueError(f"عملة غير مدعومة: {self.currency}")
    
    def add(self, other: 'Money') -> 'Money':
        if self.currency != other.currency:
            raise ValueError("لا يمكن إضافة عملات مختلفة")
        return Money(self.amount + other.amount, self.currency)

المجموعات

مجموعة من كائنات النطاق المعاملة كوحدة واحدة:

from dataclasses import dataclass, field
from typing import List
from datetime import datetime

@dataclass
class OrderItem:
    product_id: UUID
    quantity: int
    price: Money
    
    def total(self) -> Money:
        return Money(
            self.price.amount * self.quantity,
            self.price.currency
        )

@dataclass
class Order:
    id: UUID
    customer_id: UUID
    items: List[OrderItem] = field(default_factory=list)
    status: str = "pending"
    created_at: datetime = field(default_factory=datetime.now)
    
    def add_item(self, product_id: UUID, quantity: int, price: Money):
        item = OrderItem(product_id, quantity, price)
        self.items.append(item)
    
    def remove_item(self, product_id: UUID):
        self.items = [
            item for item in self.items 
            if item.product_id != product_id
        ]
    
    def total(self) -> Money:
        if not self.items:
            return Money(0, "USD")
        
        return sum(
            (item.total() for item in self.items),
            Money(0, self.items[0].price.currency)
        )
    
    def confirm(self):
        if not self.items:
            raise ValueError("لا يمكن تأكيد طلب فارغ")
        if self.status != "pending":
            raise ValueError("الطلب تم معالجته بالفعل")
        self.status = "confirmed"

الأحداث النطاقية

تُمكّن الأحداث النطاقية من فصل المكونات ودعم العمليات المبنية على الأحداث. لأنظمة الأحداث المبنية على المقياس الإنتاجي، يُنصح بتنفيذ تدفق الأحداث باستخدام خدمات مثل AWS Kinesis—راجع بناء أنظمة ميكروسيرفيس مبنية على الأحداث باستخدام AWS Kinesis للحصول على دليل مفصل.

from dataclasses import dataclass
from datetime import datetime
from typing import List, Callable

@dataclass
class DomainEvent:
    occurred_at: datetime = field(default_factory=datetime.now)

@dataclass
class OrderConfirmed(DomainEvent):
    order_id: UUID
    customer_id: UUID
    total: Money

class EventPublisher:
    def __init__(self):
        self._handlers: Dict[Type, List[Callable]] = {}
    
    def subscribe(self, event_type: Type, handler: Callable):
        if event_type not in self._handlers:
            self._handlers[event_type] = []
        self._handlers[event_type].append(handler)
    
    def publish(self, event: DomainEvent):
        event_type = type(event)
        handlers = self._handlers.get(event_type, [])
        for handler in handlers:
            handler(event)

ميزات بايثون الحديثة لتصميم بنية نظيفة

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

التلميحات الخاصة بالأنواع والبروتوكولات

from typing import Protocol, runtime_checkable

@runtime_checkable
class Serializable(Protocol):
    def to_dict(self) -> dict:
        ...
    
    @classmethod
    def from_dict(cls, data: dict) -> 'Serializable':
        ...

def serialize(obj: Serializable) -> dict:
    return obj.to_dict()

استخدام Pydantic للتحقق من البيانات

from pydantic import BaseModel, Field, validator
from typing import Optional

class CreateUserDTO(BaseModel):
    email: EmailStr
    name: str = Field(..., min_length=2, max_length=100)
    age: Optional[int] = Field(None, ge=0, le=150)
    
    @validator('name')
    def name_must_not_contain_numbers(cls, v):
        if any(char.isdigit() for char in v):
            raise ValueError('لا يمكن أن تحتوي الاسم على أرقام')
        return v
    
    class Config:
        frozen = True  # جعله غير قابل للتغيير

استخدام Async/Await للعمليات المُستندة إلى المدخلات/المخرجات

إن تركيب async/await في بايثون قوي بشكل خاص للعمليات المُستندة إلى المدخلات/المخرجات في بنية نظيفة، مما يسمح بالتفاعل غير المتزامن مع قواعد البيانات والخدمات الخارجية. عند نشر تطبيقات بايثون على منصات بدون خادم، يصبح فهم خصائص الأداء أمرًا حيويًا—انظر أداء AWS Lambda: JavaScript مقابل بايثون مقابل Golang للحصول على رؤى حول تحسين وظائف بايثون بدون خادم.

from typing import List
import asyncio

class AsyncUserRepository(ABC):
    @abstractmethod
    async def save(self, user: User) -> User:
        pass
    
    @abstractmethod
    async def get_by_id(self, user_id: UUID) -> Optional[User]:
        pass

class AsyncUserService:
    def __init__(self, repository: AsyncUserRepository):
        self.repository = repository
    
    async def register_user(self, email: str, name: str) -> User:
        user = User(id=uuid4(), email=email, name=name)
        return await self.repository.save(user)
    
    async def get_users_batch(self, user_ids: List[UUID]) -> List[User]:
        tasks = [self.repository.get_by_id(uid) for uid in user_ids]
        results = await asyncio.gather(*tasks)
        return [u for u in results if u is not None]

أفضل الممارسات في تنظيم المشروع

تنظيم المشروع بشكل صحيح ضروري للحفاظ على بنية نظيفة. قبل إعداد هيكل المشروع، تأكد من استخدام بيئات بايثون الافتراضية لعزل التبعيات. تغطي قائمة المراجع الخاصة بـ venv كل ما تحتاج معرفته حول إدارة البيئات الافتراضية. بالنسبة للمشاريع الحديثة في بايثون، فكّر في استخدام uv - أداة إدارة حزم، مشاريع، وبيئات جديدة لبايثون، والتي توفر إدارة حزم أسرع وإعداد مشاريع.

my_application/
├── domain/                 # قواعد العمل داخل المؤسسة
│   ├── __init__.py
│   ├── entities/
│   │   ├── __init__.py
│   │   ├── user.py
│   │   └── order.py
│   ├── value_objects/
│   │   ├── __init__.py
│   │   ├── email.py
│   │   └── money.py
│   ├── events/
│   │   ├── __init__.py
│   │   └── user_events.py
│   └── exceptions.py
├── application/            # قواعد العمل التطبيقية
│   ├── __init__.py
│   ├── use_cases/
│   │   ├── __init__.py
│   │   ├── create_user.py
│   │   └── place_order.py
│   ├── services/
│   │   ├── __init__.py
│   │   └── user_service.py
│   └── ports/
│       ├── __init__.py
│       ├── repositories.py
│       └── external_services.py
├── infrastructure/         # واجهات خارجية
│   ├── __init__.py
│   ├── persistence/
│   │   ├── __init__.py
│   │   ├── sqlalchemy/
│   │   │   ├── models.py
│   │   │   └── repositories.py
│   │   └── mongodb/
│   │       └── repositories.py
│   ├── messaging/
│   │   ├── __init__.py
│   │   └── rabbitmq_publisher.py
│   ├── external_services/
│   │   ├── __init__.py
│   │   └── email_service.py
│   └── config.py
├── presentation/           # طبقة واجهة المستخدم/API
│   ├── __init__.py
│   ├── api/
│   │   ├── __init__.py
│   │   ├── dependencies.py
│   │   ├── routes/
│   │   │   ├── __init__.py
│   │   │   ├── users.py
│   │   │   └── orders.py
│   │   └── schemas/
│   │       ├── __init__.py
│   │       └── user_schemas.py
│   └── cli/
│       └── commands.py
├── tests/
│   ├── unit/
│   ├── integration/
│   └── e2e/
├── main.py                 # نقطة دخول التطبيق
├── container.py            # إعداد تزويج التبعيات
├── pyproject.toml
└── README.md

اختبار بنية النظافة

اختبار وحدة منطق النطاق

import pytest
from uuid import uuid4

def test_user_creation():
    user = User(
        id=uuid4(),
        email="test@example.com",
        name="Test User"
    )
    assert user.email == "test@example.com"
    assert user.is_active is True

def test_order_total_calculation():
    order = Order(id=uuid4(), customer_id=uuid4())
    order.add_item(
        uuid4(),
        quantity=2,
        price=Money(10.0, "USD")
    )
    order.add_item(
        uuid4(),
        quantity=1,
        price=Money(5.0, "USD")
    )
    assert order.total().amount == 25.0

اختبار التكامل مع مستودع البيانات

@pytest.fixture
def in_memory_repository():
    return InMemoryUserRepository()

def test_user_repository_save_and_retrieve(in_memory_repository):
    user = User(
        id=uuid4(),
        email="test@example.com",
        name="Test User"
    )
    
    saved_user = in_memory_repository.save(user)
    retrieved_user = in_memory_repository.get_by_id(user.id)
    
    assert retrieved_user is not None
    assert retrieved_user.email == user.email

اختبار طبقة الخدمة

from unittest.mock import Mock

def test_user_registration():
    # التحضير
    mock_repository = Mock(spec=UserRepository)
    mock_repository.get_by_email.return_value = None
    mock_repository.save.return_value = User(
        id=uuid4(),
        email="test@example.com",
        name="Test"
    )
    
    mock_email = Mock(spec=EmailSender)
    mock_events = Mock(spec=EventPublisher)
    
    service = UserService(mock_repository, mock_email, mock_events)
    
    # التنفيذ
    user = service.register_user("test@example.com", "Test")
    
    # التأكيد
    assert user.email == "test@example.com"
    mock_repository.save.assert_called_once()
    mock_email.send.assert_called_once()
    mock_events.publish.assert_called_once()

الأخطاء الشائعة وكيفية تجنبها

التصميم المفرط

لا تستخدم بنية النظافة لتطبيقات CRUD البسيطة. ابدأ ببساطة وقم بإجراء التعديلات عند زيادة التعقيد.

التمثيلات المتسربة

تأكد من أن كيانات النطاق لا تحتوي على ملاحظات قاعدة البيانات أو الكود الخاص بالمنصات:

# سيء
from sqlalchemy import Column

@dataclass
class User:
    id: Column(Integer, primary_key=True)  # تسرب المنصة إلى النطاق

# جيد
@dataclass
class User:
    id: UUID  # كيان نظيف

الاعتماديات الدائرية

استخدم تزويج التبعيات والواجهات لتفكيك الاعتماديات الدائرية بين الطبقات.

تجاهل السياق

ليست بنية النظافة مناسبة لكل شيء. قم بتعديل صرامة الطبقات بناءً على حجم المشروع وخبرة الفريق.

روابط مفيدة