wip
This commit is contained in:
278
plans/2025-12-15T15:42:00Z_clean-architecture-migration.md
Normal file
278
plans/2025-12-15T15:42:00Z_clean-architecture-migration.md
Normal file
@@ -0,0 +1,278 @@
|
|||||||
|
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/<context>
|
||||||
|
- 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/<context>
|
||||||
|
- PageViewMapper.ts
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
• Domain ↔ ORM mapping only happens in adapters
|
||||||
|
• Mappers are pure functions
|
||||||
|
• Boilerplate is acceptable and expected
|
||||||
|
|
||||||
|
⸻
|
||||||
|
|
||||||
|
8. Repositories
|
||||||
|
|
||||||
|
Core
|
||||||
|
|
||||||
|
/core/<context>/domain/repositories
|
||||||
|
- IPageViewRepository.ts
|
||||||
|
|
||||||
|
Only interfaces.
|
||||||
|
|
||||||
|
Adapters
|
||||||
|
|
||||||
|
/adapters/persistence/typeorm/<context>
|
||||||
|
- PageViewTypeOrmRepository.ts
|
||||||
|
|
||||||
|
/adapters/persistence/inmemory/<context>
|
||||||
|
- 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
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
⸻
|
||||||
|
|
||||||
|
13. 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.)
|
||||||
|
|
||||||
|
⸻
|
||||||
|
|
||||||
|
14. 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
|
||||||
|
|
||||||
|
⸻
|
||||||
|
|
||||||
|
15. 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
|
||||||
|
|
||||||
|
⸻
|
||||||
|
|
||||||
|
16. 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.
|
||||||
Reference in New Issue
Block a user