ASA Architecture — Domain Slices & Boundaries
Architecture · ARCH-01, ARCH-02, ARCH-03, ARCH-04, ARCH-05, ARCH-06 · Priority: P0
Why It Matters
AI tools generate code without architectural awareness. Every prompt adds files wherever seems convenient — business logic in pages, shared utilities mixed with domain code, imports crossing boundaries freely. After 50-100 prompts, one change breaks three unrelated features because everything is connected to everything.
The ASA architecture pattern prevents this by enforcing clear boundaries: each feature lives in its own domain slice, pages are thin routing wrappers, and cross-domain coupling is blocked.
Priority: P0 — Without architecture enforcement, AI-generated code becomes unmaintainable. Each new prompt increases the probability of cascading failures.
Affected Stack: Next.js (App Router), React
ARCH-01 — Business logic in domains/
The Problem
AI tools put business logic wherever the current prompt lands — in page files, API routes, shared utilities, or root-level files. This makes the code impossible to reason about: where does billing logic live? Everywhere.
// ❌ Business logic scattered across the app
app/dashboard/page.tsx → 200 LOC with API calls, state management, business rules
shared/utils.ts → billing calculations mixed with string helpers
app/api/webhook/route.ts → auth checks mixed with billing fulfillment
The Fix
All business logic belongs in src/domains/{feature}/:
// ✅ ASA architecture — business logic in domain slices
domains/
billing/
lib/checkout.ts → checkout logic
lib/webhook-handler.ts → webhook processing
ui/PricingCard.tsx → billing UI components
auth/
lib/session.ts → session management
lib/middleware.ts → auth middleware
ARCH-02 — domains/ directory exists
The Problem
Without a domains/ directory, there is no designated place for business logic. Code spreads across lib/, utils/, helpers/, components/, and page files — with no convention to follow.
The Fix
Create src/domains/ as the single home for all business logic. Each domain gets its own subdirectory with lib/ for logic and ui/ for components.
ARCH-03 — No cross-domain imports
The Problem
When domains/billing/ imports from domains/auth/, changing auth can break billing. This invisible coupling is the root cause of "I changed one thing and broke everything."
// ❌ Cross-domain import creates hidden coupling
// In domains/billing/lib/checkout.ts
import { getSession } from '@/domains/auth/lib/session';
The Fix
Domains import only from shared/. If two domains need the same functionality, it lives in shared/:
// ✅ Both domains import from shared
// In domains/billing/lib/checkout.ts
import { getSession } from '@/shared/auth/session';
ARCH-04 — Thin pages (< 80 LOC)
The Problem
AI tools put everything into page files — data fetching, state management, business logic, UI rendering. A 300-line page file is a maintenance nightmare: changing any part risks breaking every other part.
The Fix
Pages are thin routing wrappers. Max 80 lines. They import from domains and compose — never implement:
// ✅ Thin page — routing + layout only
import { DashboardView } from '@/domains/dashboard/ui/DashboardView';
export default function DashboardPage() {
return <DashboardView />;
}
ARCH-05 — shared/ has no business logic
The Problem
shared/ becomes a dumping ground for code that "doesn't belong anywhere." Over time, billing calculations, auth checks, and admin logic accumulate in shared/utils/ — creating a hidden dependency hub that couples everything.
The Fix
shared/ contains only cross-cutting infrastructure: database clients, type definitions, UI primitives, layout components. Any logic specific to billing, auth, or admin belongs in its domain slice.
ARCH-06 — File size limit (> 500 LOC)
The Problem
AI tools add code to the most convenient location — typically the file that already contains related logic. After 50-100 prompts, a single file grows to 500, 800, even 1000+ lines. At that point, the AI itself can't reason about the file anymore — it loses context, introduces contradictions, and breaks existing logic.
This is one of the primary causes of the "AI wall" — the point where the AI tool stops being able to make productive changes.
The Fix
Split oversized files into smaller, focused modules. Each file should have a single responsibility:
// ❌ Before: one 800-line god component
// domains/billing/checkout.ts (800 lines)
// - checkout logic
// - webhook handling
// - validation
// - email notifications
// ✅ After: four focused files
// domains/billing/checkout/handler.ts (~150 lines) — checkout flow
// domains/billing/webhook/handler.ts (~120 lines) — webhook processing
// domains/billing/checkout/schemas.ts (~60 lines) — validation
// domains/billing/checkout/notifications.ts (~80 lines) — emails
Hard limit: Files over 500 LOC fail the check. Aim for under 300 LOC per file.
Related Checks
- Exposed Debug & Admin Routes — ADM-08
- Server-Side Auth for Protected Routes — ADM-01, ADM-02
Is your app safe? Run Free Scan →