साफ़ आर्किटेक्चर के लिए पाइथन डिज़ाइन पैटर्न

SOLID डिज़ाइन पैटर्न के साथ बनाएं Python एप्लिकेशन जो आसानी से बनाए रखे जा सकें

साफ़ आर्किटेक्चर ने विकसकों को स्केलेबल, मेन्टेनएबल एप्लिकेशन्स बनाने के तरीके को क्रांतिकारी बनाया है, जिसमें अलग-अलग चिंताओं का प्रबंधन और डिपेंडेंसी मैनेजमेंट पर जोर दिया गया है।

पाइथन में, ये सिद्धांत भाषा की डायनामिक प्रकृति के साथ मिलकर फ्लेक्सिबल, टेस्टेबल सिस्टम्स बनाते हैं जो बिजनेस रिक्वायरमेंट्स के साथ विकसित होते हैं बिना टेक्निकल डेब्ट में बदलने के।

वाइब्रेंट टेक कॉन्फ्रेंस हॉल

साफ़ आर्किटेक्चर को पाइथन में समझना

साफ़ आर्किटेक्चर, जिसे रॉबर्ट सी. मार्टिन (अंकल बॉब) ने पेश किया था, सॉफ्टवेयर को केंद्रीय परतों में व्यवस्थित करता है जहां डिपेंडेंसीज़ कोर बिजनेस लॉजिक की ओर इशारा करते हैं। यह आर्किटेक्चरल पैटर्न सुनिश्चित करता है कि आपकी एप्लिकेशन की महत्वपूर्ण बिजनेस रूल्स फ्रेमवर्क्स, डेटाबेस, और बाहरी सर्विसेज से स्वतंत्र रहें।

कोर दर्शन

मूलभूत सिद्धांत सरल है लेकिन शक्तिशाली: बिजनेस लॉजिक को इन्फ्रास्ट्रक्चर पर निर्भर नहीं होना चाहिए। आपके डोमेन एंटिटीज़, यूज़ केसेज, और बिजनेस रूल्स यह काम कर सकते हैं कि आप PostgreSQL या MongoDB, FastAPI या Flask, AWS या Azure का उपयोग कर रहे हैं या नहीं।

पाइथन में, यह दर्शन “डक टाइपिंग” और प्रोटोकॉल-ओरिएंटेड प्रोग्रामिंग के साथ पूरी तरह से मेल खाता है, जो स्टैटिकली टाइप्ड भाषाओं में आवश्यक अनुष्ठान के बिना साफ़ अलगाव प्रदान करता है।

साफ़ आर्किटेक्चर की चार परतें

एंटिटीज़ परत (डोमेन): बाहरी डिपेंडेंसीज़ के बिना प्यूर बिजनेस ऑब्जेक्ट्स के साथ एंटरप्राइज़-वाइड बिजनेस रूल्स। ये POJOs (प्लेन ओल्ड पाइथन ऑब्जेक्ट्स) होते हैं।

यूज़ केसेज परत (एप्लिकेशन): एंटिटीज़ और बाहरी सर्विसेज के बीच डेटा के प्रवाह को ऑर्केस्ट्रेट करने वाले एप्लिकेशन-स्पेसिफिक बिजनेस रूल्स।

इंटरफेस एडैप्टर्स परत: यूज़ केसेज और एंटिटीज़ के लिए सबसे सुविधाजनक फॉर्मेट और बाहरी एजेंसियों द्वारा आवश्यक फॉर्मेट के बीच डेटा को कन्वर्ट करता है।

फ्रेमवर्क्स और ड्राइवर्स परत: डेटाबेस, वेब फ्रेमवर्क्स, और बाहरी एपीआई जैसे सभी बाहरी विवरण।

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

# प्रोटोकॉल का उपयोग (पाइथन 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:
        # PayPal लॉजिक
        return True

# मौजूदा कोड को मॉडिफाई किए बिना आसानी से एक्सटेंसिबल
class CryptoProcessor:
    def process_payment(self, amount: float) -> bool:
        # क्रिप्टोक्यूरेंसी लॉजिक
        return True

लिस्कोव सब्सटिट्यूशन सिद्धांत (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:
        # Check if user exists
        existing_user = self.user_repository.get_by_email(email)
        if existing_user:
            raise UserAlreadyExistsError(f"User with email {email} already exists")

        # Create new user
        user = User(
            id=uuid4(),
            email=email,
            name=name,
            is_active=True
        )

        # Save to repository
        user = self.user_repository.save(user)

        # Send welcome email
        self.email_service.send(
            to=user.email,
            subject="Welcome!",
            body=f"Hello {user.name}, welcome to our platform!"
        )

        # Publish event
        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 {user_id} not found")

        user.is_active = False
        user = self.user_repository.save(user)

        self.event_publisher.publish('user.deactivated', {
            'user_id': str(user.id)
        })

        return user

Python में निर्भरता इंजेक्शन

Python की गतिशील प्रकृति निर्भरता इंजेक्शन को बिना भारी फ्रेमवर्क की आवश्यकता के सरल बनाती है।

कंस्ट्रक्टर इंजेक्शन

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):
        # Use injected dependencies
        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"No registration found for {interface}")

# Usage
def create_container() -> Container:
    container = Container()

    # Register services
    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

हेक्सागोनल आर्किटेक्चर (पोर्ट्स और एडैप्टर्स)

हेक्सागोनल आर्किटेक्चर व्यवसाय तर्क को केंद्र में रखती है और एडैप्टर्स बाहरी संचार का प्रबंधन करती हैं।

पोर्ट्स (इंटरफेस) को परिभाषित करना

# Input Port (Primary)
class CreateUserUseCase(Protocol):
    def execute(self, request: 'CreateUserRequest') -> 'CreateUserResponse':
        ...

# Output Port (Secondary)
class UserPersistencePort(Protocol):
    def save(self, user: User) -> User:
        ...

    def find_by_email(self, email: str) -> Optional[User]:
        ...

एडैप्टर्स को लागू करना

from pydantic import BaseModel, EmailStr

# Input Adapter (REST API)
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))

# Output Adapter (Database)
# Already implemented as 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"Invalid email: {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("Amount cannot be negative")
        if self.currency not in ['USD', 'EUR', 'GBP']:
            raise ValueError(f"Unsupported currency: {self.currency}")

    def add(self, other: 'Money') -> 'Money':
        if self.currency != other.currency:
            raise ValueError("Cannot add different currencies")
        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("Cannot confirm empty order")
        if self.status != "pending":
            raise ValueError("Order already processed")
        self.status = "confirmed"

डोमेन इवेंट्स

डोमेन इवेंट्स घटकों के बीच ढीले कनेक्शन को सक्षम बनाते हैं और इवेंट-ड्राइवन आर्किटेक्चर का समर्थन करते हैं। उत्पादन-स्तर के इवेंट-ड्राइवन सिस्टम के लिए, AWS Kinesis जैसे सेवाओं के साथ इवेंट स्ट्रीमिंग को लागू करने का विचार करें—Building Event-Driven Microservices with 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()

पाइडैन्टिक फॉर वैलिडेशन

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('Name cannot contain numbers')
        return v

    class Config:
        frozen = True  # Make immutable

एसिंक्रोनस/एवेट फॉर आई/ओ ऑपरेशन्स

पाइथन का एसिंक्रोनस/एवेट सिंटैक्स क्लीन आर्किटेक्चर में आई/ओ-बाउंड ऑपरेशन्स के लिए विशेष रूप से शक्तिशाली है, जो डेटाबेस और बाहरी सेवाओं के साथ नॉन-ब्लॉकिंग इंटरैक्शन्स की अनुमति देता है। जब आप पाइथन एप्लिकेशन्स को सर्वरलेस प्लेटफॉर्म्स पर डिप्लॉय करते हैं, तो प्रदर्शन विशेषताओं को समझना महत्वपूर्ण हो जाता है—AWS लैम्ब्डा प्रदर्शन: जावास्क्रिप्ट vs पाइथन vs गोलैंग पाइथन सर्वरलेस फंक्शन्स को ऑप्टिमाइज़ करने के लिए अंतर्दृष्टि प्रदान करता है।

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/           # UI/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():
    # Arrange
    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)

    # Act
    user = service.register_user("test@example.com", "Test")

    # Assert
    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  # शुद्ध डोमेन ऑब्जेक्ट

सर्कुलर डिपेंडेंसीज

डिपेंडेंसी इंजेक्शन और इंटरफेस का उपयोग करके लेयर्स के बीच सर्कुलर डिपेंडेंसीज तोड़ें।

कॉन्टेक्स्ट को नज़रअंदाज़ करना

क्लीन आर्किटेक्चर एक-आकार-सबके-लिए नहीं है। प्रोजेक्ट आकार और टीम विशेषज्ञता के आधार पर लेयर स्ट्रिक्टनेस को समायोजित करें।

उपयोगी लिंक्स