Clean Architecture in Python: nexusx vs. Mainstream Frameworks
Updated 2026-06
What Is Clean Architecture
Clean Architecture's central claim: business logic depends on no framework, database, UI, or any external agency. Through dependency inversion, high-level policy (use cases) is decoupled from low-level detail (database, API delivery), so the same business logic can be reused across multiple delivery modes.
Three things determine a framework's Clean Architecture maturity:
- Entity definition — is the domain model decoupled from infrastructure (ORM, serialization framework)?
- DTO / response assembly — is there an independent response-construction layer that auto-generates external contracts from entity models?
- Multi-API surface — can the same business logic serve REST, GraphQL, MCP, and other delivery modes simultaneously?
Framework Overview
| Framework | Entity definition | DTO mechanism | Business logic | API generation | Multi-API surface | MCP | DI |
|---|---|---|---|---|---|---|---|
| nexusx | SQLModel + Relationship | DefineSubset metaclass auto-gen | UseCaseService (@query / @mutation) |
Auto (SDL + REST + MCP) | GraphQL + REST + MCP | Native | Loader injection + FromContext |
| Litestar | SQLAlchemy / Pydantic | DTO factory auto-gen | Controller + Service + DI | Semi-auto (Controller decorators) | REST + GraphQL (plugin) + WebSocket | None | Built-in DI (layered scopes) |
| Django + DRF | Django Model | ModelSerializer auto-gen | Fat Model / Service Layer | Auto (ViewSet + Router) | REST + GraphQL (third-party) + Admin | None | No DI container |
| Strawberry | @strawberry.type |
Pydantic integration bridge | Resolver + DataLoader | Auto (SDL from code) | GraphQL only | None | Extensions |
| FastAPI + SQLModel | SQLModel(table=True) | Manual inheritance variants | Manual Service / Repository | Manual (per-endpoint) | REST only | None | Depends |
| Ariadne | External ORM | SDL as DTO | Manual resolver binding | Manual (hand-written SDL) | GraphQL only | None | None |
| Tartiflette | External ORM | SDL as DTO | Manual resolver binding | Manual (hand-written SDL) | GraphQL only | None | Directives |
| Temporalio | dataclass / Pydantic | Parameters / return values | Workflow + Activity separation | N/A (workflow engine) | N/A | N/A | N/A (Worker registration, not traditional DI) |
nexusx's Architecture
┌──────────────────────────────┐
│ Delivery Layer │
│ GraphQL / REST / MCP / CLI │
└──────────┬───────────────────┘
│ Auto-generated by protocol builders
┌──────────▼──────────────────┐
│ Use Case / Business Logic │
│ UseCaseService │
│ @query / @mutation │
│ Method body = logic entry │
└──────────┬──────────────────┘
│ Method may call Resolver to assemble DTO
┌──────────▼───────────────────┐
│ Response Assembly (DTO) │
│ DefineSubset → Resolver │
│ BFS traversal + post_* │
└──────────┬───────────────────┘
│ Load relationships
┌──────────▼──────────────────┐
│ Data Access Layer │
│ ErManager + DataLoader │
│ SQLModel Entity │
└─────────────────────────────┘
Key design decisions:
- UseCaseService methods are the business-logic entry point: the classmethods decorated with
@query/@mutationare simultaneously the external contract (translated by each protocol builder into GraphQL fields / REST endpoints / MCP tools) and the executable body of business logic. The method body can be the full business logic or a thin wrapper calling external pure functions (a project-structure choice, not enforced by the library). - DefineSubset is a declarative DTO: auto-generates a Pydantic model from SQLModel entity metadata; the field set is declared explicitly by the developer via
__subset__(including whether FK columns are included). - Resolver is a model-driven response builder: walks the object tree via BFS, automatically batch-loading relational data.
- The same UseCaseService method serves multiple APIs: translated to different protocols via builders like
create_use_case_graphql_mcp_server/create_use_case_router/build_cli, with zero changes to the business method.
Detailed Framework Analysis
Litestar — Closest to nexusx's Philosophy
Litestar's DTO factory pattern is conceptually similar to nexusx's DefineSubset: auto-generates request/response schemas from SQLAlchemy/Pydantic models, supporting field exclusion and renaming. But Litestar's DTO only serves REST — no unified GraphQL or MCP generation.
Strengths: - DTOs are first-class citizens, auto-generated from models, with field-level control - Built-in DI container with layered scopes (transient / scoped / singleton) - Plugin system for extensibility (SQLAlchemy, Redis, etc.) - Supports multiple serialization backends (Pydantic, msgspec, attrs)
Weaknesses: - GraphQL requires the Strawberry plugin — no unified generation - No MCP support - DTO factory is oriented around OpenAPI schema, not multi-API response assembly - Smaller community, limited production cases
Best for: REST-only projects that value DTO automation and DI.
Django + DRF — Highest Automation
Django's Model → ModelSerializer → ModelViewSet → Router pipeline generates a complete CRUD REST API + Admin UI in four steps. Among all frameworks, this is the highest level of automation.
Strengths: - ModelSerializer auto-generates serializers from Django Models - ModelViewSet + Router auto-generates CRUD endpoints - Django Admin auto-generates an admin UI (an irreplaceable productivity tool) - graphene-django auto-generates GraphQL schemas from Models - Mature ecosystem, many production cases
Weaknesses: - Django Model is tightly coupled with ORM — domain models cannot be independent of the database - No built-in DI container, relies on module-level imports (anti-Clean-Architecture) - Clean Architecture refactoring is expensive, requires manually introducing a Service Layer - No MCP support - Fat Model pattern mixes business logic with data access
Best for: Projects with mostly CRUD delivery needs, where strict layering is not a priority.
Strawberry — Best GraphQL Ecosystem
Strawberry is the most mature code-first GraphQL solution in Python. Pydantic integration auto-bridges models, and DataLoader is built-in to prevent N+1.
Strengths:
- Code-first, SDL auto-generated from Python code
- Pydantic integration (strawberry.experimental.pydantic.type) auto-bridges models
- Built-in DataLoader abstraction, but each resolver must be wired manually (unlike nexusx, which is fully automatic from ORM metadata)
- Supports Relay spec (cursor pagination), Subscription, Federation 2.x
- Active community, high-quality docs
Weaknesses:
- GraphQL only — no REST or MCP support
- Pydantic bridging requires manually defining strawberry.type (unlike nexusx's fully automatic DefineSubset)
- No built-in DI (relies on FastAPI's Depends)
- Response assembly requires hand-written resolver logic
Best for: GraphQL-only projects that value type safety and developer experience.
FastAPI + SQLModel — Minimal Framework Constraint
This is the most "frameworkless" option: a single SQLModel class serves as both ORM model and Pydantic validation model, and FastAPI auto-generates OpenAPI docs from type annotations. Clean Architecture is entirely up to the developer.
Strengths:
- Low learning curve, rich FastAPI documentation
- SQLModel single-class-multi-role reduces boilerplate
- FastAPI's Depends provides lightweight DI
- OpenAPI docs auto-generated
- Largest community, richest ecosystem
Weaknesses: - No DTO auto-generation mechanism (must manually define Create/Update/Read variants) - No business-logic organization pattern (must manually introduce Service/Repository layers) - Each endpoint defined manually — high repetition - No GraphQL or MCP support - Clean Architecture requires substantial manual implementation
Best for: Small-to-medium projects where the team already has FastAPI experience and strict layering is not a priority.
Ariadne / Tartiflette — Schema-first GraphQL
Both are Schema-first (SDL-first) solutions: write the GraphQL schema first, then bind resolvers. The opposite of nexusx's model-driven (code-first) philosophy.
Strengths (Ariadne): - SDL is the front-end/back-end contract — clear collaboration - Ariadne Codegen generates client code (Pydantic models) from the schema - Framework-agnostic, integrates with Django, Flask, FastAPI
Weaknesses: - SDL must be hand-written — high maintenance cost - Manual resolver binding — lots of boilerplate - No DTO conversion layer; resolvers operate directly on Python objects - No REST or MCP support - Tartiflette community activity is questionable
Best for: GraphQL projects where front-end and back-end teams need SDL as a collaboration contract.
Temporalio — Clean Architecture for Workflow Orchestration
Temporalio's Workflow/Activity separation is a natural implementation of dependency inversion: Workflows are the deterministic orchestration layer (similar to Use Cases), Activities are the side-effectful business layer (similar to Repositories/Gateways).
Strengths: - Workflow/Activity separation enforces dependency inversion - Event sourcing auto-persists state - Native support for long-running executions, retries, timeouts - Idempotency is a design constraint, not an optional practice
Weaknesses: - Not an API framework — cannot directly generate REST/GraphQL/MCP - Workflows must be deterministic (no randomness, network calls, or time retrieval inside) - Steep learning curve - Requires Temporal Server infrastructure
Best for: Long-running workflow orchestration (order processing, approval flows, data pipelines); must be used alongside an API framework.
Core-Dimension Comparison
1. Entity-DTO Decoupling
| Framework | Entity→DTO approach | Automation level |
|---|---|---|
| nexusx | DefineSubset metaclass auto-generates Pydantic DTOs from SQLModel metadata | High (declarative) |
| Litestar | DTO factory auto-generates from SQLAlchemy/Pydantic models | High (factory pattern) |
| Django | ModelSerializer auto-generates from Django Model | High (reflection) |
| Strawberry | Pydantic integration bridge, requires manually defining @strawberry.type |
Medium (semi-auto) |
| FastAPI | Manually define Create/Update/Read inheritance variants | Low (pure manual) |
| Ariadne | Hand-written SDL, no DTO layer | None |
nexusx and Litestar lead on DTO automation. nexusx's DefineSubset goes further — it controls both DTO generation and relationship-loading strategy (via Resolver), forming a complete response-assembly pipeline.
2. Multi-API Surface Unification
| Framework | REST | GraphQL | MCP | Unification level |
|---|---|---|---|---|
| nexusx | create_use_case_router() |
GraphQLHandler |
create_use_case_graphql_mcp_server() |
Three-way unified |
| Litestar | Native | Strawberry plugin | None | Two-way (not unified) |
| Django | DRF | graphene-django | None | Two-way (not unified) |
| Strawberry | None (needs FastAPI) | Native | None | Single surface |
| FastAPI | Native | Third-party required | None | Single surface |
nexusx is the only framework that achieves unified auto-generation across three surfaces (REST + GraphQL + MCP). Others require writing adapter code per surface when targeting multiple APIs.
3. Response-Assembly Complexity
When API responses need to assemble nested relational data (e.g. "return a Sprint with its Task list, each Task including its owner"):
| Framework | Approach | N+1 resolution |
|---|---|---|
| nexusx | Resolver BFS traversal + DataLoader batch loading | Automatic (ErManager) |
| Litestar | SQLAlchemy eager load / manual DTO control | Manual (selectinload) |
| Django | select_related / prefetch_related |
Manual (must remember to add) |
| Strawberry | Manual DataLoader integration | Built-in DataLoader |
| FastAPI | Manual query + Pydantic serialization | Manual |
nexusx's Resolver + ErManager combination is the only solution that achieves "declarative DTO definition + automatic batch relationship loading." Other frameworks either require manual loading-strategy control (Django, Litestar, FastAPI) or manual DataLoader logic (Strawberry).
4. Business Logic vs. Delivery Separation
| Framework | Separation approach | Reuse level |
|---|---|---|
| nexusx | UseCaseService methods (business-logic entry) → translated by per-protocol builders | High (one method, four surfaces: GraphQL/REST/MCP/CLI) |
| Litestar | Service injected into Controller via DI | Medium (Service reusable, but Controllers must be written separately) |
| Django | Fat Model / Service Layer | Low (logic mixed into Views/Serializers) |
| Strawberry | Resolver binding | Medium (Resolver reusable, but GraphQL-only) |
| FastAPI | Manually organized Service layer | Medium (Service reusable, but manual wiring required) |
| Temporalio | Activity implemented independently | High (Workflow orchestrates Activities) |
nexusx's UseCaseService pattern achieves complete decoupling of business logic from delivery: business methods focus purely on business, protocol translation is handled by per-protocol builders. Methods are independently testable (no dependency on any protocol runtime), and adding a new protocol only requires adding a new builder — zero changes to business code.
Extension: nexusx's official 4-phase skill recommends a stricter project structure (
methods.pypure functions + user-defined_mount()to mount onto Entities), splitting "business logic" and "protocol entry" into separate layers. This is a project-organization convention, not a library requirement — see the "Extension: 4-phase skill's recommended project structure" section at the end.
nexusx's Unique Position
What It Achieves That Others Don't
- Four-surface unified generation: one set of SQLModel entities + UseCaseService business methods → auto-generated GraphQL + REST + MCP + CLI
- Declarative response assembly: DefineSubset specifies "what"; Resolver + ErManager automatically handle "how"
- MCP as a first-class citizen: AI agents can discover and invoke APIs through four-layer progressive disclosure (list_apps → describe_compose_schema → describe_compose_method → compose_query) — completely absent from other frameworks
- Model-driven contract generation: auto-generates GraphQL SDL and OpenAPI spec from SQLModel entities as the single source of truth for front-end/back-end collaboration
Trade-offs to Be Aware Of
- SQLModel binding: the entity layer is coupled to SQLModel/SQLAlchemy, less flexible than Litestar's multi-ORM backend support
- Python ecosystem niche: nexusx positions itself within the "SQLModel + FastAPI" ecosystem, narrower than Django's "batteries-included" scope
- Community size: as an emerging framework, production cases and community resources are smaller than FastAPI's or Django's
- Learning curve: DefineSubset + Resolver + ErManager + UseCaseService is a lot of concepts — non-trivial initial barrier
Selection Guide
| Scenario | Recommended framework | Reason |
|---|---|---|
| New project needing GraphQL + REST + MCP | nexusx | Three-surface unified generation, only one with MCP |
| REST-only, values DI and DTO automation | Litestar | Most complete DTO factory + built-in DI |
| Fast CRUD + Admin backend | Django + DRF | Highest automation, Admin is irreplaceable |
| GraphQL-only, values type safety | Strawberry | Best Python GraphQL ecosystem |
| Lightweight API, team knows FastAPI | FastAPI + SQLModel | Lowest learning curve, largest community |
| Long-running workflow orchestration | Temporalio | Workflow/Activity is natural dependency inversion |
| Front-end/back-end SDL collaboration | Ariadne | Schema-first, Codegen supports client generation |
Extension: 4-phase Skill's Recommended Project Structure
The methods.py / _mount() / TS SDK generation mentioned earlier are not built-in features of the nexusx library — they are project scaffolding recommended by the official nexusx-4phase skill. The skill is a set of guidance for AI coding agents (in the repo's skill/ directory), splitting "build a complete project with nexusx" into four phases:
| Phase | Goal | Key artifacts |
|---|---|---|
| Phase 1 | ER modeling + mock data | SQLModel entities + Relationship + ER diagram |
| Phase 2 | Business methods + GraphQL | service/<domain>/methods.py pure functions + user-defined _mount() to mount onto Entities |
| Phase 3 | DTOs + multi-protocol | dtos.py (DefineSubset) + service.py (UseCaseService) + REST/MCP/CLI |
| Phase 4 | Front-end SDK | OpenAPI spec → TypeScript SDK (via @hey-api/openapi-ts) |
Core conventions recommended by the skill:
# service/project/methods.py — Phase 2 recommended structure
from sqlmodel import select
async def list_sprints(limit: int = 10) -> list[Sprint]:
"""Pure function: doesn't import nexusx, independently unit-testable."""
async with get_session() as session:
return (await session.exec(select(Sprint).limit(limit))).all()
# src/models.py — user defines _mount helper in their own project
from nexusx import query, mutation
from service.project.methods import list_sprints, create_sprint
def _mount(entity, fn, decorator):
"""Mount a methods.py pure function onto an entity as @query / @mutation."""
setattr(entity, fn.__name__, classmethod(fn))
# ... decorator registration details provided by the skill template
_mount(Sprint, list_sprints, query)
_mount(Sprint, create_sprint, mutation)
Additional benefits of this structure (not in the library itself, but encouraged by the skill):
- Business logic is pure functions (no
clsdependency, no decorator dependency) — unit tests don't need to start any protocol runtime - "Business logic" and "protocol entry" are physically separated — adding a protocol requires zero changes to business files
- Phase 4 uses
@hey-api/openapi-tsto translate the FastAPI-generated OpenAPI spec into a TypeScript SDK, keeping front-end/back-end type contracts in sync
Notes:
_mount()/methods.pyare part of the skill template, not thenexusxlibrary API.pip install nexusxdoes not give you these tools — they live inskills/nexusx-4phase/template/src/models.pyand are provided by the skill for AI agents or developers to copy.- You can completely skip the skill's project structure and write
@querymethods directly inside aUseCaseServiceclass body (the library's native usage). The skill only offers an optional convention with stricter separation of "business logic / protocol entry." - Install the skill:
npx skills add KLR-Pattern/nexusx -s nexusx-4phase -a claude-code(see the repo'sskills/nexusx-4phase/SKILL.mdfor details).