Loading...
Loading...
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).
npx skill4agent add hieutrtr/ai1-skills system-architectureplan.mdproject-plannerarchitecture.mddocs/adr/ADR-NNN-<title>.mdarchitecture.md/api-design-patterns/task-decompositionpython-backend-expertreact-frontend-expertapi-design-patternspytest-patternsreact-testing-patternsdocker-best-practicesdeployment-pipelineHTTP 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# 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┌─────────────────────┐
│ 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
└─────────────────────┘| 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? |
references/architecture-decision-record-template.mddowngrade()WHERE is_active = true# 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
)AsyncSessionasync withselectinjoinedrun_sync()alembic revision --autogenerate -m "description"alembic upgrade headalembic downgrade -1Is the data from the server?
├── YES → Use TanStack Query (useQuery, useMutation)
│ Configure staleTime, gcTime, query keys
│
└── NO → Is it needed across multiple components?
├── YES → Is it complex with actions/reducers?
│ ├── YES → Use Zustand store
│ └── NO → Use React Context
│
└── NO → Use useState / useReducer locally[resource, ...identifiers]["users", userId]["posts", { page, limit }]queryOptions()staleTimeinvalidateQueries()refetch()isPendingisErrordatachildrensrc/
├── pages/
│ ├── HomePage.tsx → /
│ ├── LoginPage.tsx → /login
│ ├── users/
│ │ ├── UserListPage.tsx → /users
│ │ └── UserDetailPage.tsx → /users/:id
│ └── settings/
│ └── SettingsPage.tsx → /settingsLogin Request
↓
Backend: Validate credentials → Generate JWT (access + refresh tokens)
↓
Frontend: Store access token in memory, refresh token in httpOnly cookie
↓
API Calls: Attach access token via Authorization header
↓
Token Expired: Use refresh token to obtain new access token
↓
Refresh Failed: Redirect to loginDepends()userlogin()logout()isAuthenticated| Layer | Error Type | Action |
|---|---|---|
| Router | | Return HTTP error response with status code |
| Service | Domain exceptions | Raise custom exceptions (e.g., |
| Repository | Database exceptions | Catch and re-raise as domain exceptions or let propagate |
| Frontend | API errors | Display user-friendly messages, retry where appropriate |
class AppError(Exception):
"""Base application error."""
class NotFoundError(AppError):
"""Resource not found."""
class ConflictError(AppError):
"""Resource conflict (duplicate, version mismatch)."""
class ValidationError(AppError):
"""Business rule violation."""@app.exception_handler(NotFoundError)
async def not_found_handler(request: Request, exc: NotFoundError):
return JSONResponse(status_code=404, content={"detail": str(exc)})console.*class Settings(BaseSettings):
model_config = SettingsConfigDict(env_file=".env")
database_url: str
redis_url: str = "redis://localhost:6379"
jwt_secret: str
debug: bool = FalseVITE_API_URLimport.meta.envarchitecture.md# Architecture: [Feature/System Name]
## Overview
[1-2 sentence summary of the architectural approach]
## Layer Structure
[Backend and frontend layer descriptions from this skill's patterns]
## Key Decisions
[Summary of decisions made, with links to ADRs]
## Database Schema
[Entity descriptions, relationships, key indexes]
## Cross-Cutting Concerns
[Auth, error handling, logging approach]
## Next Steps
- Run `/api-design-patterns` to define API contracts
- Run `/task-decomposition` to create implementation tasksdocs/adr/# ADR-NNN: [Decision Title]
## Status
Accepted | Proposed | Superseded
## Context
[Why this decision is needed]
## Decision
[What we decided]
## Consequences
[Positive and negative outcomes]| Option | Pros | Cons |
|---|---|---|
| WebSocket | True bidirectional, low latency | Complex connection management, harder to scale |
| Server-Sent Events (SSE) | Simple, HTTP-based, auto-reconnect | Unidirectional (server→client only), limited browser connections |
| Polling | Simplest implementation, works everywhere | Higher latency, unnecessary server load |
ConnectionManageruseWebSocketreferences/architecture-decision-record-template.md