setup
This commit is contained in:
361
docs/architecture/DATA_FLOW.md
Normal file
361
docs/architecture/DATA_FLOW.md
Normal file
@@ -0,0 +1,361 @@
|
||||
Frontend data shapes: View Models, Presenters, and API Client (Strict)
|
||||
|
||||
This document defines the exact placement and responsibilities for frontend data shapes.
|
||||
It is designed to leave no room for interpretation.
|
||||
|
||||
⸻
|
||||
|
||||
1. Definitions
|
||||
|
||||
API DTOs
|
||||
|
||||
Transport shapes owned by the API boundary (HTTP). They are not used directly by UI components.
|
||||
|
||||
View Models
|
||||
|
||||
UI-owned shapes. They represent exactly what the UI needs and nothing else.
|
||||
|
||||
Website Presenters
|
||||
|
||||
Pure mappers that convert API DTOs (or core use-case outputs) into View Models.
|
||||
|
||||
API Client
|
||||
|
||||
A thin HTTP wrapper that returns API DTOs only and performs no business logic.
|
||||
|
||||
⸻
|
||||
|
||||
2. Directory layout (exact)
|
||||
|
||||
apps/website
|
||||
├── app/ # Next.js routes/pages
|
||||
├── components/ # React components (UI only)
|
||||
├── lib/
|
||||
│ ├── api/ # API client (HTTP only)
|
||||
│ ├── dtos/ # API DTO types (transport shapes)
|
||||
│ ├── view-models/ # View Models (UI-owned shapes)
|
||||
│ ├── presenters/ # Presenters: DTO -> ViewModel mapping
|
||||
│ ├── services/ # UI orchestration (calls api + presenters)
|
||||
│ └── index.ts
|
||||
|
||||
No additional folders for these concerns are allowed.
|
||||
|
||||
⸻
|
||||
|
||||
3. View Models (placement and rules)
|
||||
|
||||
Where they live
|
||||
|
||||
View Models MUST live in:
|
||||
|
||||
apps/website/lib/view-models
|
||||
|
||||
What they may contain
|
||||
• UI-ready primitives (strings, numbers, booleans)
|
||||
• UI-specific derived fields (e.g., isOwner, badgeLabel, formattedDate)
|
||||
• UI-specific structures (e.g., grouped arrays, flattened objects)
|
||||
|
||||
What they must NOT contain
|
||||
• Domain entities or value objects
|
||||
• API transport metadata
|
||||
• Validation logic
|
||||
• Network or persistence concerns
|
||||
|
||||
Rule
|
||||
|
||||
Components consume only View Models.
|
||||
|
||||
⸻
|
||||
|
||||
4. API DTOs in the website (placement and rules)
|
||||
|
||||
Clarification
|
||||
|
||||
The website does have DTOs, but only API DTOs.
|
||||
|
||||
These DTOs exist exclusively to type HTTP communication with the backend API.
|
||||
They are not UI models.
|
||||
|
||||
⸻
|
||||
|
||||
Where they live
|
||||
|
||||
Website-side API DTO types MUST live in:
|
||||
|
||||
apps/website/lib/dtos
|
||||
|
||||
What they represent
|
||||
• Exact transport shapes sent/received via HTTP
|
||||
• Backend API contracts
|
||||
• No UI assumptions
|
||||
|
||||
Who may use them
|
||||
• API client
|
||||
• Website presenters
|
||||
|
||||
Who must NOT use them
|
||||
• React components
|
||||
• Pages
|
||||
• UI logic
|
||||
|
||||
Rule
|
||||
|
||||
API DTOs stop at the presenter boundary.
|
||||
|
||||
Components must never consume API DTOs directly.
|
||||
|
||||
⸻
|
||||
|
||||
5. Presenters (website) (placement and rules)
|
||||
|
||||
Where they live
|
||||
|
||||
Website presenters MUST live in:
|
||||
|
||||
apps/website/lib/presenters
|
||||
|
||||
What they do
|
||||
• Convert API DTOs into View Models
|
||||
• Perform UI-friendly formatting and structuring
|
||||
• Are pure and deterministic
|
||||
|
||||
What they must NOT do
|
||||
• Make API calls
|
||||
• Read from localStorage/cookies directly
|
||||
• Contain business rules or decisions
|
||||
• Perform side effects
|
||||
|
||||
Rule
|
||||
|
||||
Presenters output View Models. Presenters never output API DTOs.
|
||||
|
||||
⸻
|
||||
|
||||
6. Do website presenters use View Models?
|
||||
|
||||
Yes. Strictly:
|
||||
|
||||
Website presenters MUST output View Models and MUST NOT output API DTOs.
|
||||
|
||||
Flow is always:
|
||||
|
||||
API DTO -> Presenter -> View Model -> Component
|
||||
|
||||
|
||||
⸻
|
||||
|
||||
7. API client (website) (placement and rules)
|
||||
|
||||
Where it lives
|
||||
|
||||
The API client MUST live in:
|
||||
|
||||
apps/website/lib/api
|
||||
|
||||
What it does
|
||||
• Sends HTTP requests
|
||||
• Returns API DTOs
|
||||
• Performs authentication header/cookie handling only at transport level
|
||||
• Does not map to View Models
|
||||
|
||||
What it must NOT do
|
||||
• Format or reshape responses for UI
|
||||
• Contain business rules
|
||||
• Contain decision logic
|
||||
|
||||
Rule
|
||||
|
||||
The API client has no knowledge of View Models.
|
||||
|
||||
⸻
|
||||
|
||||
8. Website service layer (strict orchestration)
|
||||
|
||||
Where it lives
|
||||
|
||||
Website orchestration MUST live in:
|
||||
|
||||
apps/website/lib/services
|
||||
|
||||
What it does
|
||||
• Calls the API client
|
||||
• Calls presenters to map DTO -> View Model
|
||||
• Returns View Models to pages/components
|
||||
|
||||
What it must NOT do
|
||||
• Contain domain logic
|
||||
• Modify core invariants
|
||||
• Return API DTOs
|
||||
|
||||
Rule
|
||||
|
||||
Services are the only layer allowed to call both api/ and presenters/.
|
||||
|
||||
Components must not call the API client directly.
|
||||
|
||||
⸻
|
||||
|
||||
9. Allowed dependency directions (frontend)
|
||||
|
||||
Within apps/website:
|
||||
|
||||
components -> services -> (api + presenters) -> (dtos + view-models)
|
||||
|
||||
Strict rules:
|
||||
• components may import only view-models and services
|
||||
• presenters may import dtos and view-models only
|
||||
• api may import dtos only
|
||||
• services may import api, presenters, view-models
|
||||
|
||||
Forbidden:
|
||||
• components importing api
|
||||
• components importing dtos
|
||||
• presenters importing api
|
||||
• api importing view-models
|
||||
• any website code importing core domain entities
|
||||
|
||||
⸻
|
||||
|
||||
10. Naming rules (strict)
|
||||
• View Models end with ViewModel
|
||||
• API DTOs end with Dto
|
||||
• Presenters end with Presenter
|
||||
• Services end with Service
|
||||
• One export per file
|
||||
• File name equals exported symbol (PascalCase)
|
||||
|
||||
⸻
|
||||
|
||||
11. Final "no ambiguity" summary
|
||||
• View Models live in apps/website/lib/view-models
|
||||
• API DTOs live in apps/website/lib/dtos
|
||||
• Presenters live in apps/website/lib/presenters and map DTO -> ViewModel
|
||||
• API client lives in apps/website/lib/api and returns DTOs only
|
||||
• Services live in apps/website/lib/services and return View Models only
|
||||
• Components consume View Models only and never touch API DTOs or API clients
|
||||
|
||||
⸻
|
||||
|
||||
12. Clean Architecture Flow Diagram
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[UI Components] --> B[Services]
|
||||
B --> C[API Client]
|
||||
B --> D[Presenters]
|
||||
C --> E[API DTOs]
|
||||
D --> E
|
||||
D --> F[View Models]
|
||||
A --> F
|
||||
|
||||
style A fill:#e1f5fe
|
||||
style B fill:#f3e5f5
|
||||
style C fill:#fff3e0
|
||||
style D fill:#e8f5e8
|
||||
style E fill:#ffebee
|
||||
style F fill:#e3f2fd
|
||||
```
|
||||
|
||||
**Flow Explanation:**
|
||||
- UI Components consume only View Models
|
||||
- Services orchestrate API calls and presenter mappings
|
||||
- API Client returns raw API DTOs
|
||||
- Presenters transform API DTOs into UI-ready View Models
|
||||
- Strict dependency direction: UI → Services → (API + Presenters) → (DTOs + ViewModels)
|
||||
|
||||
⸻
|
||||
|
||||
13. Enforcement Guidelines
|
||||
|
||||
**ESLint Rules:**
|
||||
- Direct imports from `apiClient` are forbidden - use services instead
|
||||
- Direct imports from `dtos` in UI components are forbidden - use ViewModels instead
|
||||
- Direct imports from `api/*` in UI components are forbidden - use services instead
|
||||
|
||||
**TypeScript Path Mappings:**
|
||||
- Use `@/lib/dtos` for API DTO imports
|
||||
- Use `@/lib/view-models` for View Model imports
|
||||
- Use `@/lib/presenters` for Presenter imports
|
||||
- Use `@/lib/services` for Service imports
|
||||
- Use `@/lib/api` for API client imports
|
||||
|
||||
**Import Restrictions:**
|
||||
- Components may import only view-models and services
|
||||
- Presenters may import dtos and view-models only
|
||||
- API may import dtos only
|
||||
- Services may import api, presenters, view-models
|
||||
- Forbidden: components importing api, components importing dtos, presenters importing api, api importing view-models
|
||||
|
||||
**Verification Commands:**
|
||||
```bash
|
||||
npm run build # Ensure TypeScript compiles
|
||||
npm run lint # Ensure ESLint rules pass
|
||||
npm run test # Ensure all tests pass
|
||||
```
|
||||
|
||||
⸻
|
||||
|
||||
14. Architecture Examples
|
||||
|
||||
**Before (Violates Rules):**
|
||||
```typescript
|
||||
// In a page component - BAD
|
||||
import { apiClient } from '@/lib/apiClient';
|
||||
import type { RaceResultDto } from '@/lib/dtos/RaceResultDto';
|
||||
|
||||
const RacePage = () => {
|
||||
const [data, setData] = useState<RaceResultDto[]>();
|
||||
// Direct API call and DTO usage in UI
|
||||
useEffect(() => {
|
||||
apiClient.getRaceResults().then(setData);
|
||||
}, []);
|
||||
return <div>{data?.map(d => d.position)}</div>;
|
||||
};
|
||||
```
|
||||
|
||||
**After (Clean Architecture):**
|
||||
```typescript
|
||||
// In a page component - GOOD
|
||||
import { RaceResultsService } from '@/lib/services/RaceResultsService';
|
||||
import type { RaceResultViewModel } from '@/lib/view-models/RaceResultViewModel';
|
||||
|
||||
const RacePage = () => {
|
||||
const [data, setData] = useState<RaceResultViewModel[]>();
|
||||
useEffect(() => {
|
||||
RaceResultsService.getResults().then(setData);
|
||||
}, []);
|
||||
return <div>{data?.map(d => d.formattedPosition)}</div>;
|
||||
};
|
||||
```
|
||||
|
||||
**Service Implementation:**
|
||||
```typescript
|
||||
// apps/website/lib/services/RaceResultsService.ts
|
||||
import { apiClient } from '@/lib/api';
|
||||
import { RaceResultsPresenter } from '@/lib/presenters/RaceResultsPresenter';
|
||||
|
||||
export class RaceResultsService {
|
||||
static async getResults(): Promise<RaceResultViewModel[]> {
|
||||
const dtos = await apiClient.getRaceResults();
|
||||
return RaceResultsPresenter.present(dtos);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Presenter Implementation:**
|
||||
```typescript
|
||||
// apps/website/lib/presenters/RaceResultsPresenter.ts
|
||||
import type { RaceResultDto } from '@/lib/dtos/RaceResultDto';
|
||||
import type { RaceResultViewModel } from '@/lib/view-models/RaceResultViewModel';
|
||||
|
||||
export class RaceResultsPresenter {
|
||||
static present(dtos: RaceResultDto[]): RaceResultViewModel[] {
|
||||
return dtos.map(dto => ({
|
||||
id: dto.id,
|
||||
formattedPosition: `${dto.position}${dto.position === 1 ? 'st' : dto.position === 2 ? 'nd' : dto.position === 3 ? 'rd' : 'th'}`,
|
||||
driverName: dto.driverName,
|
||||
// ... other UI-specific formatting
|
||||
}));
|
||||
}
|
||||
}
|
||||
```
|
||||
82
docs/architecture/DISPLAY_OBJECTS.md
Normal file
82
docs/architecture/DISPLAY_OBJECTS.md
Normal file
@@ -0,0 +1,82 @@
|
||||
# Display Objects
|
||||
|
||||
## Definition
|
||||
|
||||
A **Display Object** encapsulates **reusable, UI-only display logic**.
|
||||
|
||||
It answers the question:
|
||||
|
||||
> “How should this specific piece of information be shown?”
|
||||
|
||||
Display Objects are **not screen-specific**.
|
||||
They exist to avoid duplicating presentation logic across View Models.
|
||||
|
||||
---
|
||||
|
||||
## Responsibilities
|
||||
|
||||
A Display Object MAY:
|
||||
|
||||
- format values (money, dates, durations)
|
||||
- handle localization and language-specific rules
|
||||
- map codes to labels
|
||||
- encapsulate UI display conventions
|
||||
- be reused across multiple View Models
|
||||
|
||||
A Display Object MUST:
|
||||
|
||||
- be deterministic
|
||||
- be side-effect free
|
||||
- operate only on presentation data
|
||||
|
||||
---
|
||||
|
||||
## Restrictions
|
||||
|
||||
A Display Object MUST NOT:
|
||||
|
||||
- contain business logic
|
||||
- enforce domain invariants
|
||||
- perform validation
|
||||
- influence system behavior
|
||||
- be sent back to the server
|
||||
- depend on backend or infrastructure concerns
|
||||
|
||||
If a rule affects system correctness or persistence,
|
||||
it does not belong in a Display Object.
|
||||
|
||||
---
|
||||
|
||||
## Ownership & Placement
|
||||
|
||||
- Display Objects belong to the **presentation layer**
|
||||
- They are frontend-only
|
||||
- They are not shared with the backend or core
|
||||
|
||||
---
|
||||
|
||||
## Relationship to View Models
|
||||
|
||||
- View Models MAY use Display Objects
|
||||
- Display Objects MUST NOT depend on View Models
|
||||
- Display Objects represent **parts**
|
||||
- View Models represent **screens**
|
||||
|
||||
---
|
||||
|
||||
## Testing
|
||||
|
||||
Display Objects SHOULD be tested because they often contain:
|
||||
|
||||
- locale-specific behavior
|
||||
- formatting rules
|
||||
- edge cases visible to users
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
- Display Objects encapsulate **how something looks**
|
||||
- View Models encapsulate **what a screen needs**
|
||||
- Both are presentation concerns
|
||||
- Neither contains business truth
|
||||
85
docs/architecture/VIEW_MODELS.md
Normal file
85
docs/architecture/VIEW_MODELS.md
Normal file
@@ -0,0 +1,85 @@
|
||||
+# View Models
|
||||
|
||||
## Definition
|
||||
|
||||
A **View Model** represents a **fully prepared UI state**.
|
||||
|
||||
It answers the question:
|
||||
|
||||
> “What does the UI need in order to render this screen without thinking?”
|
||||
|
||||
View Models are **UI-owned** classes.
|
||||
They do not represent business truth and do not enforce domain rules.
|
||||
|
||||
---
|
||||
|
||||
## Responsibilities
|
||||
|
||||
A View Model MAY:
|
||||
|
||||
- accept an API DTO as input
|
||||
- derive UI-specific fields
|
||||
- combine or reshape data for rendering
|
||||
- perform formatting (dates, numbers, labels)
|
||||
- handle localization and presentation logic
|
||||
- use Display Objects for reusable UI concerns
|
||||
|
||||
A View Model MUST:
|
||||
|
||||
- be fully usable by the UI without further computation
|
||||
- expose only data and UI-oriented helpers
|
||||
- be created in a consistent, explicit way
|
||||
|
||||
---
|
||||
|
||||
## Restrictions
|
||||
|
||||
A View Model MUST NOT:
|
||||
|
||||
- contain business logic
|
||||
- validate domain rules
|
||||
- enforce permissions or authorization
|
||||
- contain domain entities or value objects
|
||||
- perform side effects
|
||||
- be sent back to the server
|
||||
|
||||
If a View Model decides whether something is *allowed* or *correct*,
|
||||
that logic belongs in the Core, not here.
|
||||
|
||||
---
|
||||
|
||||
## Ownership & Placement
|
||||
|
||||
- View Models belong to the **frontend**
|
||||
- They live close to the UI, not in shared or core layers
|
||||
- They are not shared with the backend
|
||||
|
||||
---
|
||||
|
||||
## Creation Rules
|
||||
|
||||
- View Models are created from API DTOs
|
||||
- UI components must never construct View Models themselves
|
||||
- Construction happens in services or presentation layers
|
||||
- The UI only consumes View Models, never DTOs
|
||||
|
||||
---
|
||||
|
||||
## Testing
|
||||
|
||||
View Models SHOULD be tested when they contain:
|
||||
|
||||
- formatting logic
|
||||
- localization behavior
|
||||
- non-trivial derived fields
|
||||
|
||||
View Models do NOT need tests if they only expose data without logic.
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
- View Models describe **UI state**
|
||||
- They are **presentation-focused**, not business-focused
|
||||
- They reduce complexity in components
|
||||
- They form a stable contract for the UI
|
||||
Reference in New Issue
Block a user