Python-Entwurfsmuster für saubere Architektur
Erstellen Sie wartbare Python-Anwendungen mit SOLID-Entwurfsmustern
Clean Architecture hat die Art und Weise, wie Entwickler skalierbare, wartbare Anwendungen erstellen, revolutioniert, indem sie die Trennung von Verantwortlichkeiten und das Abhängigkeitsmanagement betonen.
In Python kombinieren sich diese Prinzipien mit der dynamischen Natur der Sprache, um flexible, testbare Systeme zu schaffen, die sich mit den Geschäftsanforderungen weiterentwickeln, ohne technische Schulden anzuhäufen.

Verständnis von Clean Architecture in Python
Clean Architecture, eingeführt von Robert C. Martin (Uncle Bob), organisiert Software in konzentrischen Schichten, bei denen Abhängigkeiten nach innen zu den Kern-Geschäftslogiken zeigen. Dieses Architekturmodell stellt sicher, dass die kritischen Geschäftsregeln Ihrer Anwendung unabhängig von Frameworks, Datenbanken und externen Diensten bleiben.
Die Kernphilosophie
Das grundlegende Prinzip ist einfach, aber mächtig: Geschäftslogik sollte nicht von der Infrastruktur abhängen. Ihre Domänen-Entitäten, Use Cases und Geschäftsregeln sollten unabhängig davon funktionieren, ob Sie PostgreSQL oder MongoDB, FastAPI oder Flask, AWS oder Azure verwenden.
In Python passt sich diese Philosophie perfekt an die “Duck-Typing”- und protokollorientierte Programmierung der Sprache an, was eine saubere Trennung ohne den Aufwand ermöglicht, der in statisch typisierten Sprachen erforderlich ist.
Die vier Schichten der Clean Architecture
Entities Layer (Domain): Reine Geschäftsobjekte mit unternehmensweiten Geschäftsregeln. Dies sind POJOs (Plain Old Python Objects) ohne externe Abhängigkeiten.
Use Cases Layer (Application): Anwendungspezifische Geschäftsregeln, die den Datenfluss zwischen Entitäten und externen Diensten orchestrieren.
Interface Adapters Layer: Wandelt Daten zwischen dem für Use Cases und Entitäten am besten geeigneten Format und dem von externen Agenturen erforderlichen Format um.
Frameworks & Drivers Layer: Alle externen Details wie Datenbanken, Web-Frameworks und externe APIs.
SOLID-Prinzipien in Python
Die SOLID-Prinzipien bilden die Grundlage der Clean Architecture. Lassen Sie uns untersuchen, wie sich jedes Prinzip in Python manifestiert. Für einen umfassenden Überblick über Design-Patterns in Python siehe den Python Design Patterns Guide.
Single Responsibility Principle (SRP)
Jede Klasse sollte einen Grund zur Änderung haben:
# Schlecht: Mehrere Verantwortlichkeiten
class UserManager:
def create_user(self, user_data):
# Benutzer erstellen
pass
def send_welcome_email(self, user):
# E-Mail senden
pass
def log_creation(self, user):
# In Datei protokollieren
pass
# Gut: Getrennte Verantwortlichkeiten
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"Benutzer erstellt: {user.id}")
return user
Open/Closed Principle (OCP)
Software-Entitäten sollten für Erweiterungen offen, aber für Modifikationen geschlossen sein:
from abc import ABC, abstractmethod
from typing import Protocol
# Verwendung von Protocol (Python 3.8+)
class PaymentProcessor(Protocol):
def process_payment(self, amount: float) -> bool:
...
class CreditCardProcessor:
def process_payment(self, amount: float) -> bool:
# Kreditkarten-Logik
return True
class PayPalProcessor:
def process_payment(self, amount: float) -> bool:
# PayPal-Logik
return True
# Leicht erweiterbar ohne Modifikation des bestehenden Codes
class CryptoProcessor:
def process_payment(self, amount: float) -> bool:
# Kryptowährungs-Logik
return True
Liskov Substitution Principle (LSP)
Objekte sollten durch ihre Subtypen ersetzbar sein, ohne das Programm zu beschädigen:
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-Implementierung
pass
def get(self, key: str) -> str:
# PostgreSQL-Implementierung
return ""
class RedisStore(DataStore):
def save(self, key: str, value: str) -> None:
# Redis-Implementierung
pass
def get(self, key: str) -> str:
# Redis-Implementierung
return ""
# Beide können austauschbar verwendet werden
def process_data(store: DataStore, key: str, value: str):
store.save(key, value)
return store.get(key)
Interface Segregation Principle (ISP)
Clients sollten nicht gezwungen sein, auf Schnittstellen zu verzichten, die sie nicht verwenden:
# Schlecht: Fette Schnittstelle
class Worker(ABC):
@abstractmethod
def work(self): pass
@abstractmethod
def eat(self): pass
@abstractmethod
def sleep(self): pass
# Gut: Getrennte Schnittstellen
class Workable(Protocol):
def work(self) -> None: ...
class Eatable(Protocol):
def eat(self) -> None: ...
class Human:
def work(self) -> None:
print("Arbeiten")
def eat(self) -> None:
print("Essen")
class Robot:
def work(self) -> None:
print("Arbeiten")
# Keine eat-Methode erforderlich
Dependency Inversion Principle (DIP)
Hochrangige Module sollten nicht von niederrangigen Modulen abhängen. Beide sollten von Abstraktionen abhängen:
from typing import Protocol
# Abstraktion
class EmailSender(Protocol):
def send(self, to: str, subject: str, body: str) -> None:
...
# Niederrangiges Modul
class SMTPEmailSender:
def send(self, to: str, subject: str, body: str) -> None:
# SMTP-Implementierung
pass
# Hochrangiges Modul hängt von der Abstraktion ab
class UserRegistrationService:
def __init__(self, email_sender: EmailSender):
self.email_sender = email_sender
def register(self, email: str, name: str):
# Registrierungslogik
self.email_sender.send(
to=email,
subject="Willkommen!",
body=f"Hallo {name}"
)
Repository Pattern: Abstrahierung des Datenzugriffs
Das Repository Pattern bietet eine sammlungsähnliche Schnittstelle zum Zugriff auf Domänenobjekte und verbirgt die Details der Datenspeicherung.
Grundlegende Repository-Implementierung
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-Implementierung
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
In-Memory-Repository für Tests
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
Service Layer: Orchestrierung der Geschäftslogik
Die Service Layer implementiert Use Cases und orchestriert den Datenfluss zwischen Repositories, externen Diensten und der Domänenlogik.
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:
# Prüfen, ob Benutzer existiert
existing_user = self.user_repository.get_by_email(email)
if existing_user:
raise UserAlreadyExistsError(f"Benutzer mit der E-Mail {email} existiert bereits")
# Neuen Benutzer erstellen
user = User(
id=uuid4(),
email=email,
name=name,
is_active=True
)
# In Repository speichern
user = self.user_repository.save(user)
# Willkommens-E-Mail senden
self.email_service.send(
to=user.email,
subject="Willkommen!",
body=f"Hallo {user.name}, willkommen auf unserer Plattform!"
)
# Ereignis veröffentlichen
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"Benutzer {user_id} nicht gefunden")
user.is_active = False
user = self.user_repository.save(user)
self.event_publisher.publish('user.deactivated', {
'user_id': str(user.id)
})
return user
Dependency Injection in Python
Die dynamische Natur von Python macht Dependency Injection einfach, ohne dass schwere Frameworks erforderlich sind.
Constructor Injection
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):
# Injektierte Abhängigkeiten verwenden
pass
Einfacher Dependency Container
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"Keine Registrierung gefunden für {interface}")
# Verwendung
def create_container() -> Container:
container = Container()
# Dienste registrieren
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
Hexagonale Architektur (Ports und Adapters)
Die hexagonale Architektur platziert die Geschäftslogik im Zentrum, während Adapter die externe Kommunikation handhaben.
Definieren von Ports (Schnittstellen)
# Input Port (Primär)
class CreateUserUseCase(Protocol):
def execute(self, request: 'CreateUserRequest') -> 'CreateUserResponse':
...
# Output Port (Sekundär)
class UserPersistencePort(Protocol):
def save(self, user: User) -> User:
...
def find_by_email(self, email: str) -> Optional[User]:
...
Implementieren von Adaptern
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 (Datenbank)
# Bereits implementiert als SQLAlchemyUserRepository
Domain-Driven Design Patterns
Value Objects
Unveränderliche Objekte, die durch ihre Attribute definiert sind:
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"Ungültige E-Mail: {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("Betrag kann nicht negativ sein")
if self.currency not in ['USD', 'EUR', 'GBP']:
raise ValueError(f"Nicht unterstützte Währung: {self.currency}")
def add(self, other: 'Money') -> 'Money':
if self.currency != other.currency:
raise ValueError("Kann verschiedene Währungen nicht addieren")
return Money(self.amount + other.amount, self.currency)
Aggregates
Cluster von Domänenobjekten, die als einzelne Einheit behandelt werden:
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("Kann leere Bestellung nicht bestätigen")
if self.status != "pending":
raise ValueError("Bestellung bereits verarbeitet")
self.status = "confirmed"
Domain Events
Domänenereignisse ermöglichen lockere Kopplung zwischen Komponenten und unterstützen ereignisgesteuerte Architekturen. Für produktionsreife ereignisgesteuerte Systeme sollten Sie die Implementierung von Event Streaming mit Diensten wie AWS Kinesis in Betracht ziehen - siehe Building Event-Driven Microservices with AWS Kinesis für eine detaillierte Anleitung.
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)
Moderne Python-Features für Clean Architecture
Python’s moderne Features machen die Implementierung von Clean Architecture eleganter und typsicher. Wenn Sie eine schnelle Referenz für Python-Syntax und Features benötigen, werfen Sie einen Blick auf den Python Cheatsheet.
Typ-Hinweise und Protokolle
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 für Validierung
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 # Unveränderlich machen
Async/Await für I/O-Operationen
Python’s async/await-Syntax ist besonders leistungsfähig für I/O-gebundene Operationen in Clean Architecture, die nicht blockierende Interaktionen mit Datenbanken und externen Diensten ermöglichen. Beim Bereitstellen von Python-Anwendungen auf serverlosen Plattformen wird das Verständnis der Leistungsmerkmale entscheidend - siehe AWS Lambda-Leistung: JavaScript vs Python vs Golang für Einblicke in die Optimierung von Python-serverlosen Funktionen.
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]
Projektstruktur-Best Practices
Eine ordnungsgemäße Projektorganisation ist entscheidend für die Aufrechterhaltung einer sauberen Architektur. Bevor Sie Ihre Projektstruktur einrichten, stellen Sie sicher, dass Sie Python-Virtual-Umgebungen für die Isolierung von Abhängigkeiten verwenden. Der venv Cheatsheet behandelt alles, was Sie über das Verwalten von virtuellen Umgebungen wissen müssen. Für moderne Python-Projekte sollten Sie die Verwendung von uv - Neuer Python-Paket-, Projekt- und Umgebungsmanager in Betracht ziehen, der schnellere Paketverwaltung und Projektaufbau bietet.
my_application/
├── domain/ # Unternehmensgeschäftsregeln
│ ├── __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/ # Anwendungsgeschäftsregeln
│ ├── __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/ # Externe Schnittstellen
│ ├── __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-Ebene
│ ├── __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 # Anwendungs-Einstiegspunkt
├── container.py # Dependency-Injection-Einrichtung
├── pyproject.toml
└── README.md
Testen von Clean Architecture
Unit-Testing von Domain-Logik
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
Integrationstests mit Repository
@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
Testen der Service-Schicht
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()
Häufige Fallstricke und wie man sie vermeidet
Über-Engineering
Implementieren Sie Clean Architecture nicht für einfache CRUD-Anwendungen. Beginnen Sie einfach und refaktorieren Sie, wenn die Komplexität zunimmt.
Leaky Abstractions
Stellen Sie sicher, dass Domain-Entitäten keine Datenbank-Annotationen oder rahmenwerkspezifischen Code enthalten:
# Schlecht
from sqlalchemy import Column
@dataclass
class User:
id: Column(Integer, primary_key=True) # Framework sickert in die Domain ein
# Gut
@dataclass
class User:
id: UUID # Reine Domain-Entität
Zirkuläre Abhängigkeiten
Verwenden Sie Dependency Injection und Schnittstellen, um zirkuläre Abhängigkeiten zwischen den Ebenen zu vermeiden.
Ignorieren des Kontexts
Clean Architecture ist nicht universell anwendbar. Passen Sie die Strenge der Ebenen basierend auf der Projektgröße und dem Teamwissen an.
Nützliche Links
- Clean Architecture von Robert C. Martin
- Python Type Hints Dokumentation
- Pydantic Dokumentation
- FastAPI Offizielle Dokumentation
- SQLAlchemy ORM Dokumentation
- Dependency Injector Bibliothek
- Domain-Driven Design Referenz
- Architekturmuster mit Python
- Martin Fowlers Blog über Architektur
- Python Design Patterns Guide
- Python Cheatsheet
- venv Cheatsheet
- uv - Neuer Python-Paket-, Projekt- und Umgebungsmanager
- AWS Lambda-Leistung: JavaScript vs Python vs Golang
- Erstellung von ereignisgesteuerten Mikrodiensten mit AWS Kinesis