Skip to content

Architecture Patterns Standard ​

đź’ˇ Copyable AI Prompt Block (AGENTS.md)

markdown
<!-- START AGENT-STANDARD: ARCHITECTURE-PATTERNS -->
## Architecture & Layering Rules
- [ ] **Clean & Hexagonal Architecture (Ports & Adapters)**: Maintain a framework-agnostic core domain model at the center. Enforce strict inward dependency flow—core business logic MUST NOT depend on databases, HTTP frameworks, RPC libraries, or external infrastructure. Instantiate and inject adapters exclusively at the Composition Root (`main.ts` / container bootstrap).
- [ ] **Decoupled Storage & Transport Adapters**: Treat databases, ORM models, HTTP controllers, gRPC handlers, and message brokers as peripheral adapters interacting with business logic strictly via domain ports (interfaces). Adapters MUST catch low-level infrastructure exceptions and translate them into typed domain errors (preserving error cause for telemetry). Transport adapters MUST map domain errors to unified 5-key API JSON envelopes (`code`, `message`, `details`, `timestamp`, `request_id`) and mask raw stack traces. Network I/O inside DB transactions is strictly banned. Repository mappers MUST implement defensive schema mapping, and newly added migration columns MUST be nullable or have DB-level `DEFAULT` constraints to support zero-downtime migrations.
- [ ] **Component Architecture & Server/Client Boundary Isolation**: Decouple pure presentation UI components (~150-line limit, max 3 local state variables, named exports required) from hook/container logic and async data fetching. Treat URL search parameters as authoritative shareable UI state, and DO NOT duplicate hydrated server state into local `useState`. React Error Boundaries MUST forward uncaught exceptions with session ID and `trace_id` to telemetry platforms. Enforce framework-appropriate server code isolation (e.g., `import 'server-only'` for RSC, `.server.ts` conventions for Remix), enforce strict input validation (Zod), auth, anti-CSRF, rate limiting / IP throttling, client-generated idempotency keys, and BOLA/IDOR resource ownership checks on Server Actions, and mandate framework-specific public prefixes (`NEXT_PUBLIC_`, `VITE_`) for client environment variables with automated build-time enforcement.
- [ ] **Monorepo & Module Boundaries**: Define explicit package and module boundaries using subpath `exports` in `package.json` (declaring `"types"` first). Ensure shared foundational packages remain platform-agnostic, prevent cross-layer leaks, avoid monolithic barrel files (`index.ts`), and enforce automated CI checks against circular dependencies.
- [ ] **CQRS & Event-Driven Patterns**: Decouple read (query) models from write (command) models where high throughput or complex domain rules exist. Domain events MUST use explicit schema versioning and propagate full W3C Trace Context (`traceparent`, `tracestate`, `baggage`). Outbox writes MUST share entity database transactions, outbox workers MUST combine `SKIP LOCKED` with aggregate-level stream locking or hash partitioning to guarantee sequential ordering per aggregate ID, track explicit status lifecycles (`PENDING`, `PROCESSING`, `PUBLISHED`, `FAILED`) with lock timeout recovery, implement automated outbox retention/pruning, and consumers MUST enforce idempotency (in-transaction for DB mutations; two-phase reservation for external network I/O) with explicit TTL policies, bounded retries, and Dead-Letter Queues (DLQs) preserving original payload and trace metadata for replayability.
<!-- END AGENT-STANDARD: ARCHITECTURE-PATTERNS -->

Detailed Human Guide & Rationale ​

1. Clean & Hexagonal Architecture (Ports & Adapters) ​

  • Core Domain Isolation: The innermost core of the software contains business entities, value objects, domain events, and core domain rules. It MUST NOT contain imports from web frameworks (e.g., Express, FastAPI, Next.js), ORMs (e.g., Prisma, TypeORM, SQLAlchemy), or infrastructure SDKs.
  • Inward Dependency Rule: All source code dependencies must point inward toward the core domain model. Inner layers know nothing about outer layers (adapters, infrastructure, UI).
  • Domain Ports (Interfaces): Application workflows interact with external capabilities (storage, external APIs, message queues) exclusively via interfaces defined within the domain or application core.
  • Composition Root Wiring: Adapter instantiation and dependency injection MUST take place exclusively at the application Composition Root (e.g., main.ts / bootstrap entry points in the outermost layer), injecting concrete interface implementations into domain/application services via constructors.
  • Primary Sources & Rationale: Grounded in Alistair Cockburn's Ports and Adapters Architecture (2005) and Robert C. Martin's Clean Architecture (2017). Decoupling core business rules from external technology choices prevents framework lock-in, ensures testability without external infrastructure, and lowers long-term maintenance costs.

2. Decoupled Storage & Transport Adapters ​

  • Peripheral Adapters: Databases (SQL/NoSQL), web frameworks (HTTP/REST, gRPC, GraphQL), and message queues (Kafka, RabbitMQ) exist as outer adapters that implement or invoke domain ports.
  • Data Mapping & Defensive Schema Boundaries: Persistence entities (e.g., SQL tables, ORM models) must be mapped to pure domain models at repository boundaries. Repository mappers MUST handle optional or migrating schema fields defensively (supplying fallback defaults for newly added columns and treating deprecated fields as optional). During the Expand phase of Expand-Migrate-Contract zero-downtime database migrations, newly added columns MUST be nullable or define database-level DEFAULT constraints to prevent legacy application pods running during rolling deployments from failing on INSERT operations.
  • Prohibition of Network I/O in DB Transactions: Database transactions MUST be kept minimal in duration and MUST NEVER perform external HTTP/RPC requests or out-of-band network I/O inside the active transaction scope.
  • Infrastructure Error Translation & Cause Preservation: Adapters MUST catch driver, network, and database exceptions at the infrastructure boundary and translate them into typed domain errors (e.g., EntityNotFoundError, DomainConflictError) or Result monads, preserving the low-level exception as an internal cause (e.g., Error.cause) for internal logging and telemetry.
  • Transport Separation & Envelope Sanitization: Transport handlers (controllers/resolvers) handle request parsing, protocol validation, and response serialization, immediately delegating business execution to domain use cases or application services. Transport adapters MUST map typed domain errors into standardized API response envelopes (5-key JSON envelope containing code, message, details, timestamp, request_id as specified in API Design Standard) with appropriate protocol status codes, and strictly mask raw internal system errors as sanitized 500 responses.
  • Primary Sources & Rationale: Grounded in Eric Evans' Domain-Driven Design (Repository & Service patterns). Isolating storage and transport representations guarantees that changes in database schema or API protocols do not break core business domain rules.

3. Component Architecture & Server/Client Boundary Isolation ​

  • Presentation vs Container Logic: Frontend components must separate pure UI rendering from state management, async data fetching, and business orchestration. Render components accept typed, immutable props; container components or custom hooks encapsulate side effects and data fetching. Presentation components and custom hooks MUST use named exports exclusively (default exports are forbidden), SHOULD target ~150 lines maximum, and maintain no more than 3 local state hooks. Shareable UI state MUST use URL search parameters as authoritative state, and server data hydrated into async client caches MUST NOT be duplicated into local component useState.
  • Server/Client Code Guardrails: In modern fullstack architectures, server-only modules (such as database credentials, private API keys, or server-side SDKs) MUST be guarded with framework-appropriate isolation—using build-time assertions (e.g., import 'server-only') in React Server Components (Next.js App Router) or module extension conventions (e.g., .server.ts) in Remix / React Router—and placed in dedicated server directories (@project/server).
  • Server Action Security & Validation: All server-client communication must occur via typed API contracts or server actions. Server actions MUST be treated as public HTTP endpoints and enforce strict input schema validation (e.g., Zod), authentication/authorization checks, resource-level tenant/user ownership validation (BOLA/IDOR protection), rate limiting / IP throttling, client-generated idempotency keys for non-idempotent mutations, and Origin/Host header verification with anti-CSRF token protection before executing domain logic.
  • Environment Variable Scoping & Build Guards: Client components must only access environment variables explicitly prefixed with public framework identifiers (e.g., NEXT_PUBLIC_, VITE_). Un-prefixed environment variables must be inaccessible in client bundles to prevent secret leakage. Linter or compiler build-time checks MUST fail the build if un-prefixed environment variables are referenced inside client-scoped modules.
  • Client Error Boundary Telemetry: React Error Boundaries MUST capture uncaught component exceptions and forward structured telemetry events (tagged with user session ID and trace_id correlation headers) to centralized observability platforms (e.g., Sentry/Datadog) to prevent silent production client failures.
  • Bundle Protection: Client components must never import server-side modules directly.
  • Primary Sources & Rationale: Grounded in React component design patterns (Container/Presenter) and modern Next.js/React Server Components specs. Preventing server-side code from leaking into client bundles avoids critical security credential disclosures and prevents bloated bundle sizes.

4. Monorepo & Module Boundaries ​

  • Explicit Public APIs & Subpath Exports: Monorepo packages (e.g., @project/types, @project/shared-schemas, @project/api-client, @project/server) must define clear entry points using package.json exports subpath maps rather than monolithic barrel files (index.ts) to enable efficient tree-shaking. When defining subpath exports in package.json, the "types" condition MUST always be declared first before "import", "require", or "default":
    json
    "exports": {
      ".": {
        "types": "./dist/index.d.ts",
        "import": "./dist/index.js",
        "default": "./dist/index.js"
      }
    }
  • Restricted Imports & Circular Dependency Guards: Deep internal imports (e.g., import { db } from '@project/server/src/internal/db') across package or layer boundaries are strictly forbidden. Enforce strict import boundaries and automated circular dependency detection in CI via linter rules (e.g., ESLint import/no-restricted-paths, import/no-cycle, or madge/dpdm).
  • Unidirectional Dependency Graph: Shared foundational packages (types, shared-schemas) must sit at the bottom of the dependency graph, cannot import from top-level application packages, and MUST remain platform-agnostic (free of Node.js-specific modules like fs, path, or server ORMs) to ensure safe usage in client browser bundles.
  • Primary Sources & Rationale: Grounded in software modularity principles and monorepo management best practices (Nx/Turborepo architecture). Explicit boundaries prevent spaghetti dependencies, ensure independent build caching, and allow teams to refactor internal implementations safely.

5. CQRS & Event-Driven Patterns ​

  • Command Query Responsibility Segregation (CQRS): In high-throughput or complex domain contexts, separate write paths (Commands) from read paths (Queries). Write models strictly enforce business invariants and domain validation. Read models utilize optimized projections or read replicas tailored for fast query performance.
  • Domain Event Decoupling & Schema Versioning: State-changing operations in the core domain publish domain events (e.g., OrderPlaced, UserRegistered). Domain event payloads MUST follow explicit schema versioning (e.g., CloudEvents envelope or version fields) and remain strictly additive and backward/forward compatible.
  • Distributed Tracing Header Propagation: Domain events and outbox payloads MUST include full W3C Trace Context (traceparent, tracestate, baggage) and correlation ID headers in event metadata. Event consumers MUST extract and bind these trace identifiers into their execution context prior to processing events.
  • Consumer Idempotency: All asynchronous domain event consumers MUST implement idempotency checks (e.g., deduplication stores or idempotency keys) to safely execute side effects under at-least-once message delivery guarantees. For consumers performing internal database state mutations, the idempotency check and record insertion MUST execute within the exact same database transaction as the domain state mutation. For consumers performing external HTTP/RPC side-effects (e.g., email or third-party webhooks), idempotency MUST be managed via a two-phase status reservation or outbox pattern to strictly prevent holding open active database transactions during external network I/O. Idempotency records MUST specify an explicit TTL (Time-To-Live, e.g., 7–30 days depending on domain retention) and automated cleanup to prevent unbounded table growth.
  • Transactional Outbox & Worker Concurrency: Use transactional outbox patterns—persisting outbound event records within the exact same database transaction as business entities—to guarantee dual-write consistency. Outbox relay workers MUST combine SELECT ... FOR UPDATE SKIP LOCKED with aggregate-level stream locking or hash-based aggregate partitioning across worker instances to guarantee that events for a single aggregate ID are never processed out of order. Outbox record state MUST explicitly track lifecycle status (PENDING, PROCESSING, PUBLISHED, FAILED) with lock timeouts to recover orphaned jobs if a worker crashes during dispatch. In addition, systems MUST implement an automated outbox message retention and cleanup strategy (e.g., async batch deletion of published rows or time-based partitioning) to prevent table bloat.
  • Eventual Consistency & DLQs: Event consumers MUST configure bounded exponential backoff retries and route unprocessable "poison-pill" messages to a Dead-Letter Queue (DLQ) with active alerting to prevent partition blocking. Poison-pill messages routed to Dead-Letter Queues MUST preserve original message payloads, failure cause stack traces, retry attempt headers, and W3C trace context identifiers to support manual operational inspection and deterministic event replayability. Applications MUST account for eventual consistency lag via optimistic UI updates or poll/subscription mechanisms.
  • Primary Sources & Rationale: Grounded in Martin Fowler's CQRS pattern (2011) and Greg Young's Event Sourcing & CQRS architecture. Decoupling reads from writes and isolating side effects via events improves system scalability, fault tolerance, and domain clarity.