# 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:

1. **Entity definition** — is the domain model decoupled from infrastructure (ORM, serialization framework)?
2. **DTO / response assembly** — is there an independent response-construction layer that auto-generates external contracts from entity models?
3. **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` / `@mutation` are 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.py` pure 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

1. **Four-surface unified generation**: one set of SQLModel entities + UseCaseService business methods → auto-generated GraphQL + REST + MCP + CLI
2. **Declarative response assembly**: DefineSubset specifies "what"; Resolver + ErManager automatically handle "how"
3. **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
4. **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

1. **SQLModel binding**: the entity layer is coupled to SQLModel/SQLAlchemy, less flexible than Litestar's multi-ORM backend support
2. **Python ecosystem niche**: nexusx positions itself within the "SQLModel + FastAPI" ecosystem, narrower than Django's "batteries-included" scope
3. **Community size**: as an emerging framework, production cases and community resources are smaller than FastAPI's or Django's
4. **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:

```python
# 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 `cls` dependency, 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-ts` to translate the FastAPI-generated OpenAPI spec into a TypeScript SDK, keeping front-end/back-end type contracts in sync

**Notes**:

- `_mount()` / `methods.py` are part of the **skill template**, not the `nexusx` library API. `pip install nexusx` does not give you these tools — they live in `skill/template/src/models.py` and are provided by the skill for AI agents or developers to copy.
- You can completely skip the skill's project structure and write `@query` methods directly inside a `UseCaseService` class body (the library's native usage). The skill only offers an optional convention with stricter separation of "business logic / protocol entry."
- Install the skill: `ln -s $(pwd)/skill ~/.claude/skills/nexusx-4phase` (see the repo's [`skill/SKILL.md`](https://github.com/allmonday/nexusx/blob/master/skill/SKILL.md) for details).
