# Clean Architecture plan for GridPilot (NestJS‑focused) This document defines the **target architecture** and **rules** for GridPilot. It is written as a **developer-facing contract**: what goes where, what is allowed, and what is forbidden. --- ## 1. Architectural goals * Strict Clean Architecture (dependency rule enforced) * Domain-first design (DDD-inspired) * Frameworks are delivery mechanisms, not architecture * Business logic is isolated from persistence, UI, and infrastructure * Explicit composition roots * High testability without mocking the domain --- ## 2. High-level structure ``` /apps /api # NestJS API (delivery mechanism) /website # Next.js website (delivery mechanism) /electron # Electron app (delivery mechanism) /core # Business rules (framework-agnostic) /analytics /automation /identity /media /notifications /racing /social /shared /adapters # Technical implementations (details) /persistence /typeorm /inmemory /auth /media /notifications /logging /testing # Test-only code (never shipped) /contexts /factories /builders /fakes /fixtures ``` --- ## 3. Dependency rule (non-negotiable) Dependencies **must only point inward**: ``` apps → adapters → core ``` Forbidden: * `core` importing from `adapters` * `core` importing from `apps` * domain entities importing ORM, NestJS, or framework code --- ## 4. Core rules The Core contains **only business rules**. ### Core MAY contain: * Domain entities * Value objects * Domain services * Domain events * Repository interfaces * Application use cases * Application-level ports ### Core MUST NOT contain: * ORM entities * Persistence implementations * In-memory repositories * NestJS decorators * TypeORM decorators * HTTP / GraphQL / IPC concerns * Faker, demo data, seeds --- ## 5. Domain entities Domain entities: * Represent business concepts * Enforce invariants * Contain behavior * Are immutable or controlled via methods Example characteristics: * Private constructors * Static factory methods * Explicit validation * Value objects for identity Domain entities **must never**: * Be decorated with `@Entity`, `@Column`, etc. * Be reused as persistence models * Know how they are stored --- ## 6. Persistence entities (ORM) Persistence entities live in adapters and are **data-only**. ``` /adapters/persistence/typeorm/ - PageViewOrmEntity.ts ``` Rules: * No business logic * No validation * No behavior * Flat data structures ORM entities are **not domain entities**. --- ## 7. Mapping (anti-corruption layer) Mapping between domain and persistence is explicit and isolated. ``` /adapters/persistence/typeorm/ - PageViewMapper.ts ``` Rules: * Domain ↔ ORM mapping only happens in adapters * Mappers are pure functions * Boilerplate is acceptable and expected --- ## 8. Repositories ### Core ``` /core//domain/repositories - IPageViewRepository.ts ``` Only interfaces. ### Adapters ``` /adapters/persistence/typeorm/ - PageViewTypeOrmRepository.ts /adapters/persistence/inmemory/ - InMemoryPageViewRepository.ts ``` Rules: * Repositories translate between domain entities and storage models * Repositories implement core interfaces * Repositories hide all persistence details from the core --- ## 9. In-memory repositories In-memory repositories are **test adapters**, not core infrastructure. Rules: * Never placed in `/core` * Allowed only in `/adapters/persistence/inmemory` * Used for unit tests and integration tests * Must behave like real repositories --- ## 10. NestJS API (`/apps/api`) The NestJS API is a **delivery mechanism**. ### Responsibilities: * Controllers * DTOs * Request validation * Dependency injection * Adapter selection (prod vs test) ### Forbidden: * Business logic * Domain rules * Persistence logic NestJS modules are **composition roots**. --- ## 11. Dependency injection DI is configured **only in apps**. Example: ``` provide: IPageViewRepository useClass: PageViewTypeOrmRepository ``` Switching implementations (e.g. InMemory vs TypeORM) happens **outside the core**. --- ## 12. Testing strategy Tests are **clients of the system**, not part of the architecture. ### Core principles * `/tests` contains test cases * `/testing` contains **test support only** (helpers, factories, fixtures) * Production code must never import from `/testing` --- ## 13. Test support structure (`/testing`) The `/testing` folder is a **test-support library**, not a layer. It follows a single, strict structure: ``` /testing /fixtures # Static reference data (NO logic, NO faker) /factories # Domain object factories (faker allowed ONLY here) /fakes # Fake implementations of external ports /helpers # Generic test utilities (time, ids, assertions) index.ts ``` --- ## 14. Rules for test support The project uses **factories only**. There is **no separate concept of fixtures**. All test data is produced via factories. This avoids duplication, drift, and ambiguity. --- ### Factories (the single source of test data) Factories are the **only approved way** to create test data. Rules: * Factories create **valid domain objects** or **valid DTOs** (boundary only) * One factory per concept * Stateless * One export per file * File name === exported symbol * Faker / randomness allowed here Factories must **NOT**: * Use repositories * Perform IO * Contain assertions * Contain domain logic --- ### Fakes Fakes replace **external systems only**. Rules: * Implement application ports * Explicit behavior * One export per file * No domain logic --- ### Helpers Helpers are **pure utilities**. Rules: * No domain knowledge * No adapters * No randomness --- ### Factories * Create **valid domain objects** * Stateless * Deterministic unless randomness is required * Faker is allowed **only here** Factories **must NOT**: * Use repositories * Use DTOs * Perform IO --- ### Fakes * Implement application ports * Replace external systems (payments, email, auth, etc.) * Explicit, controllable behavior --- ### Helpers * Pure utilities * No domain logic * No adapters --- ## 15. DTO usage in tests (strict rule) DTOs are **boundary objects**, not domain concepts. Rules: * Domain tests: **never** use DTOs * Domain factories: **never** use DTOs * Use case tests: DTOs allowed **only if the use case explicitly accepts a DTO** * API / controller tests: DTOs are expected and allowed If DTOs are needed in tests, they must be created explicitly, e.g.: ``` /testing/dto-factories ``` This makes boundary tests obvious and prevents accidental coupling. --- ## 16. Concrete pseudo examples (authoritative) The following examples show the **only intended usage**, aligned with **Screaming Architecture** and **one-export-per-file**. Naming and placement are part of the rule. --- ### Naming rules (strict) * One file = one export * File name === exported symbol * PascalCase only * No suffixes like `.factory`, `.fixture`, `.fake` Correct: ``` PageViewFactory.ts PageViewFixture.ts FakePaymentGateway.ts ``` --- ### Example: Domain entity ```ts // core/analytics/domain/entities/PageView.ts export class PageView { static create(props) {} isMeaningfulView(): boolean {} } ``` --- ### Example: Fixture (static data) ```ts // testing/analytics/fixtures/PageViewFixture.ts export const PageViewFixture = { id: 'pv-1', entityType: 'league', entityId: 'l-1', visitorType: 'guest', sessionId: 's-1', }; ``` Rules demonstrated: * Plain object * One export * No logic * No faker --- ### Example: Factory (domain object creation) ```ts // testing/analytics/factories/PageViewFactory.ts export class PageViewFactory { static create(overrides = {}) { return PageView.create({ id: randomId(), entityType: 'league', entityId: randomId(), visitorType: 'guest', sessionId: randomId(), ...overrides, }); } } ``` Rules demonstrated: * Produces domain entity * Faker / randomness allowed here * One export per file * No DTOs * No repositories --- ### Example: Fake (external port) ```ts // testing/racing/fakes/FakePaymentGateway.ts export class FakePaymentGateway implements PaymentGatewayPort { charge() { return { success: true }; } } ``` Rules demonstrated: * Implements a port * Simulates external system * One export per file * No domain logic --- ### Example: Domain test ```ts // tests/analytics/PageView.spec.ts const pageView = PageViewFactory.create({ durationMs: 6000 }); expect(pageView.isMeaningfulView()).toBe(true); ``` Rules demonstrated: * Talks domain language * No DTOs * No framework --- ### Example: Use case test (DTO allowed only if required) ```ts // tests/analytics/RecordPageView.spec.ts const command = RecordPageViewCommandDTO.create({ ... }); useCase.execute(command); ``` Rules demonstrated: * DTO only used because it is the explicit input * Boundary is under test --- ### Example: API test ```ts // tests/api/analytics.e2e.spec.ts POST /api/page-views body = RecordPageViewCommandDTO ``` Rules demonstrated: * DTOs expected * HTTP boundary * Full stack --- ## 17. Migration vs Bootstrap (production data) Certain data exists **not because of business rules**, but because the application must be operable. This is **not a testing concern** and **not a domain concern**. --- ### Migrations **Purpose:** * Create or evolve the database schema Characteristics: * Tables, columns, indices, constraints * Versioned * Deterministic * Idempotent per version Rules: * No domain logic * No factories * No faker * No test helpers * No business decisions Location: ``` /adapters/persistence/migrations ``` --- ### Bootstrap (initial application data) **Purpose:** * Ensure the system is usable after first start Examples: * Initial admin user * System roles * Required system configuration entries Characteristics: * Runs at application startup * Idempotent * Environment-driven Rules: * Must NOT live in core * Must NOT live in testing * Must NOT live in repositories * Must use application use cases Location: ``` /adapters/bootstrap ``` Example (pseudo): ```ts class EnsureInitialAdminUser { run(): Promise {} } ``` The application (e.g. NestJS) decides **when** this runs. Adapters decide **how** it runs. --- ### Hard separation | Concern | Purpose | Location | | --------- | ------------------- | ------------------------------- | | Migration | Schema | adapters/persistence/migrations | | Bootstrap | Required start data | adapters/bootstrap | | Factory | Test data | testing | --- ## 18. Final enforcement rules * Domain knows nothing about migrations or bootstrap * Repositories never auto-create data * Factories are test-only * Bootstrap is explicit and idempotent * Migrations and bootstrap are never mixed This section is authoritative.