clean-architecture
25
总安装量
25
周安装量
#7953
全站排名
安装命令
npx skills add https://github.com/yonatangross/orchestkit --skill clean-architecture
Agent 安装分布
claude-code
19
opencode
16
gemini-cli
16
codex
13
cursor
13
Skill 文档
Clean Architecture Patterns
Build maintainable, testable backends with SOLID principles and hexagonal architecture.
SOLID Principles ( Python)
S – Single Responsibility
# BAD: One class doing everything
class UserManager:
def create_user(self, data): ...
def send_welcome_email(self, user): ...
def generate_report(self, users): ...
# GOOD: Separate responsibilities
class UserService:
def create_user(self, data: UserCreate) -> User: ...
class EmailService:
def send_welcome(self, user: User) -> None: ...
class ReportService:
def generate_user_report(self, users: list[User]) -> Report: ...
O – Open/Closed (Protocol-based)
from typing import Protocol
class PaymentProcessor(Protocol):
async def process(self, amount: Decimal) -> PaymentResult: ...
class StripeProcessor:
async def process(self, amount: Decimal) -> PaymentResult:
# Stripe implementation
...
class PayPalProcessor:
async def process(self, amount: Decimal) -> PaymentResult:
# PayPal implementation - extends without modifying
...
L – Liskov Substitution
# Any implementation of Repository can substitute another
class IUserRepository(Protocol):
async def get_by_id(self, id: str) -> User | None: ...
async def save(self, user: User) -> User: ...
class PostgresUserRepository:
async def get_by_id(self, id: str) -> User | None: ...
async def save(self, user: User) -> User: ...
class InMemoryUserRepository: # For testing - fully substitutable
async def get_by_id(self, id: str) -> User | None: ...
async def save(self, user: User) -> User: ...
I – Interface Segregation
# BAD: Fat interface
class IRepository(Protocol):
async def get(self, id: str): ...
async def save(self, entity): ...
async def delete(self, id: str): ...
async def search(self, query: str): ...
async def bulk_insert(self, entities): ...
# GOOD: Segregated interfaces
class IReader(Protocol):
async def get(self, id: str) -> T | None: ...
class IWriter(Protocol):
async def save(self, entity: T) -> T: ...
class ISearchable(Protocol):
async def search(self, query: str) -> list[T]: ...
D – Dependency Inversion
from typing import Protocol
from fastapi import Depends
class IAnalysisRepository(Protocol):
async def get_by_id(self, id: str) -> Analysis | None: ...
class AnalysisService:
def __init__(self, repo: IAnalysisRepository):
self._repo = repo # Depends on abstraction, not concrete
# FastAPI DI
def get_analysis_service(
db: AsyncSession = Depends(get_db)
) -> AnalysisService:
repo = PostgresAnalysisRepository(db)
return AnalysisService(repo)
Hexagonal Architecture (Ports & Adapters)
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â DRIVING ADAPTERS â
â ââââââââââââ ââââââââââââ ââââââââââââ ââââââââââââ â
â â FastAPI â â CLI â â Celery â â Tests â â
â â Routes â â Commands â â Tasks â â Mocks â â
â ââââââ¬ââââââ ââââââ¬ââââââ ââââââ¬ââââââ ââââââ¬ââââââ â
â â â â â â
â â¼ â¼ â¼ â¼ â
â âââââââââââââââââââââââââââââââââââââââââââââââââââââââââ â
â â INPUT PORTS â â
â â âââââââââââââââââââ âââââââââââââââââââââââââââââââ â â
â â â AnalysisService â â UserService â â â
â â â (Use Cases) â â (Use Cases) â â â
â â ââââââââââ¬âââââââââ ââââââââââââââââ¬âââââââââââââââ â â
â â ââââââââââââªâââââââââââââââââââââââââââªââââââââââââââââ⣠â
â â â¼ DOMAIN â¼ â â
â â âââââââââââââââââââââââââââââââââââââââââââââââââââ â â
â â â Entities â Value Objects â Domain Events â â â
â â â Analysis â AnalysisType â AnalysisCreated â â â
â â âââââââââââââââââââââââââââââââââââââââââââââââââââ â â
â â âââââââââââââââââââââââââââââââââââââââââââââââââââââââ⣠â
â â OUTPUT PORTS â â
â â ââââââââââââââââââââ ââââââââââââââââââââââââââââââ â â
â â â IAnalysisRepo â â INotificationService â â â
â â â (Protocol) â â (Protocol) â â â
â â ââââââââââ¬ââââââââââ ââââââââââââââââ¬ââââââââââââââ â â
â âââââââââââââªâââââââââââââââââââââââââââªâââââââââââââââââ â
â â¼ â¼ â
â âââââââââââââââââââââ ââââââââââââââââââââââââââââââââââ â
â â PostgresRepo â â EmailNotificationService â â
â â (SQLAlchemy) â â (SMTP/SendGrid) â â
â âââââââââââââââââââââ ââââââââââââââââââââââââââââââââââ â
â DRIVEN ADAPTERS â
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
DDD Tactical Patterns
Entity (Identity-based)
from dataclasses import dataclass, field
from uuid import UUID, uuid4
@dataclass
class Analysis:
id: UUID = field(default_factory=uuid4)
source_url: str
status: AnalysisStatus
created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
def __eq__(self, other: object) -> bool:
if not isinstance(other, Analysis):
return False
return self.id == other.id # Identity equality
Value Object (Structural equality)
from dataclasses import dataclass
@dataclass(frozen=True) # Immutable
class AnalysisType:
category: str
depth: int
def __post_init__(self):
if self.depth < 1 or self.depth > 3:
raise ValueError("Depth must be 1-3")
Aggregate Root
class AnalysisAggregate:
def __init__(self, analysis: Analysis, artifacts: list[Artifact]):
self._analysis = analysis
self._artifacts = artifacts
self._events: list[DomainEvent] = []
def complete(self, summary: str) -> None:
self._analysis.status = AnalysisStatus.COMPLETED
self._analysis.summary = summary
self._events.append(AnalysisCompleted(self._analysis.id))
def collect_events(self) -> list[DomainEvent]:
events = self._events.copy()
self._events.clear()
return events
Directory Structure
backend/app/
âââ api/v1/ # Driving adapters (FastAPI routes)
âââ domains/
â âââ analysis/
â âââ entities.py # Domain entities
â âââ value_objects.py # Value objects
â âââ services.py # Domain services (use cases)
â âââ repositories.py # Output port protocols
â âââ events.py # Domain events
âââ infrastructure/
â âââ repositories/ # Driven adapters (PostgreSQL)
â âââ services/ # External service adapters
â âââ messaging/ # Event publishers
âââ core/
âââ dependencies.py # FastAPI DI configuration
âââ protocols.py # Shared protocols
Anti-Patterns (FORBIDDEN)
# NEVER import infrastructure in domain
from app.infrastructure.database import engine # In domain layer
# NEVER leak ORM models to API
@router.get("/users/{id}")
async def get_user(id: str, db: Session) -> UserModel: # Returns ORM model
return db.query(UserModel).get(id)
# NEVER have domain depend on framework
from fastapi import HTTPException
class UserService:
def get(self, id: str):
if not user:
raise HTTPException(404) # Framework in domain!
Key Decisions
| Decision | Recommendation |
|---|---|
| Protocol vs ABC | Use Protocol (structural typing) |
| Dataclass vs Pydantic | Dataclass for domain, Pydantic for API |
| Repository granularity | One per aggregate root |
| Transaction boundary | Service layer, not repository |
| Event publishing | Collect in aggregate, publish after commit |
Related Skills
repository-patterns– Detailed repository implementationsapi-design-framework– REST API patternsdatabase-schema-designer– Schema design
Capability Details
solid-principles
Keywords: SOLID, single responsibility, open closed, liskov, interface segregation, dependency inversion Solves:
- How do I apply SOLID principles in Python?
- My classes are doing too much
hexagonal-architecture
Keywords: hexagonal, ports and adapters, clean architecture, onion Solves:
- How do I structure my FastAPI app?
- How to separate infrastructure from domain?
ddd-tactical
Keywords: entity, value object, aggregate, domain event, DDD Solves:
- What’s the difference between entity and value object?
- How to design aggregates?