5.4 KiB
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.
⸻
- 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
⸻
- 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
⸻
- 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
⸻
- 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
⸻
- 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
⸻
- 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.
⸻
- 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
⸻
- 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
⸻
- 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
⸻
- 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.
⸻
- Dependency injection
DI is configured only in apps.
Example:
provide: IPageViewRepository useClass: PageViewTypeOrmRepository
Switching implementations (e.g. InMemory vs TypeORM) happens outside the core.
⸻
- Testing strategy
Domain tests • Test entities and value objects directly • No adapters • No frameworks
Use case tests • Core + in-memory adapters • No NestJS
API tests • NestJS TestingModule • Explicit adapter overrides
⸻
- Testing helpers
Testing helpers live in /testing.
Contexts • One context per bounded context • Provide repositories + use cases
Factories • Create valid domain objects • Express intent, not randomness
Builders • Build complex aggregates fluently
Fakes • Replace external systems (payments, email, etc.)
⸻
- Read models and projections
Not every query needs a domain entity.
Rules: • Reports, analytics, dashboards may use DTOs or projections • No forced mapping into domain entities • Domain entities are for behavior, not reporting
⸻
- Golden rules • Domain entities never depend on infrastructure • ORM entities never contain behavior • Repositories are anti-corruption layers • Adapters are replaceable • Apps wire everything together • Tests never leak into production code
⸻
- Summary
This architecture ensures: • Long-term maintainability • Framework independence • Safe refactoring • Clear ownership of responsibilities
If a class violates a rule, it is in the wrong layer.