Claude
Skills
Sign in
Back

system-architecture

Included with Lifetime
$97 forever

System architecture guidance for Python/React full-stack projects. Use during the design phase when making architectural decisions — component boundaries, service layer design, data flow patterns, database schema planning, and technology trade-off analysis. Covers FastAPI layer architecture (Routes/Services/Repositories/Models), React component hierarchy, state management, and cross-cutting concerns (auth, errors, logging). Produces architecture documents and ADRs. Does NOT cover implementation (use python-backend-expert or react-frontend-expert) or API contract design (use api-design-patterns).

Design

What this skill does


# System Architecture

## When to Use

Activate this skill when:
- Designing a new module, service, or major feature that requires structural decisions
- Choosing between architectural approaches (e.g., where to place logic, how to structure data flow)
- Planning database schema changes or refactoring existing schema
- Making frontend state management decisions (server state vs client state, context vs store)
- Evaluating technology trade-offs for a new capability
- Creating or reviewing Architecture Decision Records (ADRs)
- Setting up a new project or major subsystem from scratch

**Input:** If `plan.md` exists (from `project-planner`), read it for context about the feature scope and affected modules. Otherwise, work from the user's request directly.

**Output:** Write architecture decisions to `architecture.md` and create ADRs in `docs/adr/ADR-NNN-<title>.md`. Tell the user: "Architecture written to `architecture.md`. Run `/api-design-patterns` for API contracts or `/task-decomposition` for implementation tasks."

Do NOT use this skill for:
- Writing implementation code (use `python-backend-expert` or `react-frontend-expert`)
- API contract design or endpoint specifications (use `api-design-patterns`)
- Testing patterns or strategies (use `pytest-patterns` or `react-testing-patterns`)
- Deployment or infrastructure decisions (use `docker-best-practices` or `deployment-pipeline`)

## Instructions

### Project Layer Architecture

The standard Python/React full-stack architecture follows a layered pattern with strict dependency direction.

#### Backend Layers (FastAPI)

```
HTTP Request
    ↓
┌─────────────────────┐
│   Routers (routes/)  │  ← HTTP concerns: request parsing, response formatting, status codes
│                      │     Uses: Depends() for injection, Pydantic schemas for validation
├─────────────────────┤
│   Services           │  ← Business logic: orchestration, validation rules, domain operations
│   (services/)        │     No HTTP awareness. Raises domain exceptions, not HTTPException.
├─────────────────────┤
│   Repositories       │  ← Data access: queries, CRUD operations, database interactions
│   (repositories/)    │     No business logic. Returns model instances or None.
├─────────────────────┤
│   Models (models/)   │  ← SQLAlchemy ORM models: table definitions, relationships, indexes
│   Schemas (schemas/) │  ← Pydantic v2 models: request/response contracts, validation
└─────────────────────┘
    ↓
Database
```

**Dependency direction rules:**
- Routers depend on Services (never on Repositories directly)
- Services depend on Repositories (never on Routers)
- Repositories depend on Models (never on Services)
- Schemas are shared across layers but define no dependencies themselves
- Never skip layers: no direct database access from routes

**Dependency injection pattern:**
```python
# Router depends on Service via Depends()
@router.post("/users", response_model=UserResponse)
async def create_user(
    data: UserCreate,
    service: UserService = Depends(get_user_service),
) -> UserResponse:
    return await service.create_user(data)

# Service depends on Repository via constructor injection
class UserService:
    def __init__(self, repo: UserRepository) -> None:
        self.repo = repo

# Repository depends on AsyncSession via Depends()
class UserRepository:
    def __init__(self, session: AsyncSession) -> None:
        self.session = session
```

#### Frontend Layers (React/TypeScript)

```
┌─────────────────────┐
│   Pages (pages/)     │  ← Route-level components: data fetching, layout composition
├─────────────────────┤
│   Layouts            │  ← Page structure: navigation, sidebars, content areas
│   (layouts/)         │
├─────────────────────┤
│   Features           │  ← Domain-specific: UserProfile, OrderList, ChatPanel
│   (features/)        │     Composed from shared components + hooks
├─────────────────────┤
│   Shared Components  │  ← Reusable UI: Button, Modal, Table, Form, Input
│   (components/)      │     No business logic. Configurable via props.
├─────────────────────┤
│   Hooks (hooks/)     │  ← Custom hooks: useAuth, usePagination, useDebounce
│   API (api/)         │  ← API client functions, TanStack Query configurations
├─────────────────────┤
│   Types (types/)     │  ← Shared TypeScript interfaces and type definitions
└─────────────────────┘
```

**Component dependency direction:**
- Pages import Features and Layouts
- Features import Shared Components and Hooks
- Shared Components import only other Shared Components and Types
- Hooks import API functions and Types
- API functions import Types only

### Decision Framework

When facing architectural decisions, follow this structured process:

#### Step 1: Define the Problem
- What capability is needed?
- What are the non-functional requirements? (performance, scalability, maintainability)
- What constraints exist? (team size, timeline, existing infrastructure)

#### Step 2: Identify Options
- List 2-3 viable architectural approaches
- For each option, document:
  - How it works (brief technical description)
  - Advantages
  - Disadvantages
  - Risks

#### Step 3: Evaluate Against Criteria

| Criterion | Weight | Description |
|-----------|--------|-------------|
| Maintainability | High | Can the team understand, modify, and debug this easily? |
| Testability | High | Can each component be tested in isolation? |
| Performance | Medium | Does it meet latency and throughput requirements? |
| Team familiarity | Medium | Does the team have experience with this approach? |
| Operational cost | Low | What are the infrastructure and maintenance costs? |
| Future flexibility | Low | How easily can this evolve as requirements change? |

#### Step 4: Decide and Document
- Choose the option that best satisfies the weighted criteria
- Document the decision in an ADR (see `references/architecture-decision-record-template.md`)
- Record what was NOT chosen and why — this context is valuable for future decisions

#### Step 5: Communicate
- Share the ADR with the team
- Identify any migration or rollout steps needed
- Flag reversibility: is this a one-way door or a two-way door?

### Database Schema Design

#### Design Principles

1. **Start normalized (3NF)** — Denormalize only for proven performance bottlenecks, not speculation
2. **One migration per logical change** — Each Alembic migration should represent a single, coherent schema modification
3. **Always include downgrade** — Every migration must have a working `downgrade()` function
4. **Index strategically:**
   - Primary keys (automatic)
   - Foreign keys (always)
   - Columns in WHERE clauses of frequent queries
   - Composite indexes for multi-column lookups
   - Partial indexes for filtered queries (e.g., `WHERE is_active = true`)

#### SQLAlchemy 2.0 Async Patterns

```python
# Model definition with Mapped types (SQLAlchemy 2.0 style)
class User(Base):
    __tablename__ = "users"

    id: Mapped[int] = mapped_column(primary_key=True)
    email: Mapped[str] = mapped_column(String(255), unique=True, index=True)
    is_active: Mapped[bool] = mapped_column(default=True)
    created_at: Mapped[datetime] = mapped_column(server_default=func.now())

    # Relationships: ALWAYS use eager loading with async
    posts: Mapped[list["Post"]] = relationship(
        back_populates="author",
        lazy="selectin",  # or "joined" — NEVER "lazy" with async
    )
```

**Async session rules:**
- One `AsyncSession` per request — never share across concurrent tasks
- Use `async with` context manager for automatic cleanup
- Map session boundaries to transaction boundaries
- Use `selectin` or `joined` loading — lazy loading is incompatible with asyncio
- Use `run_sync()` only as a last resort for legacy code

#### Migration Planning

1. Schema change → Generate migration: `alembic revision --autogenerate -m "description"`
2. Review generated migration — verify column types, indexes, constraints
3. Test upgrade: `alembic upg
Files: 3
Size: 30.8 KB
Complexity: 54/100
Category: Design

Related in Design