87 lines
3.2 KiB
Markdown
87 lines
3.2 KiB
Markdown
# Displays & Formatters
|
|
|
|
## Definition
|
|
|
|
A **Display** encapsulates **reusable, UI-only display logic**.
|
|
|
|
In this codebase, we distinguish between **Formatters** (Stateless Logic) and **Display Objects** (Rich Value Objects).
|
|
|
|
### 1. Formatters (The "Mouth")
|
|
Formatters are pure, stateless utilities. They are the "Experts" on how to transform a raw value into a primitive string/number.
|
|
- **Usage:** Used by `ViewDataBuilders` (Server) and `ViewModels` (Client).
|
|
- **Output:** MUST return primitive values only (`string`, `number`, `boolean`, `null`).
|
|
- **Uncle Bob says:** "Data structures (ViewData) should not have behavior. Keep logic in stateless utilities."
|
|
|
|
### 2. Display Objects (The "Rich API")
|
|
Display Objects are logic-rich **Value Objects** that live only on the client. They wrap data and provide multiple ways to look at it.
|
|
- **Usage:** Used by `ViewModels` (Client) to provide a rich API to the UI.
|
|
- **Output:** Can return complex objects or variants.
|
|
- **Uncle Bob says:** "Objects expose behavior, not data. Use them to hide the complexity of the UI."
|
|
|
|
---
|
|
|
|
## Responsibilities
|
|
|
|
A Display/Formatter MAY:
|
|
- format values (money, dates, durations)
|
|
- handle deterministic localization (mapping stable codes to labels)
|
|
- encapsulate UI display conventions
|
|
|
|
A Display/Formatter MUST:
|
|
- be deterministic
|
|
- be side-effect free
|
|
- be implemented as a **class** with static methods (Formatters) or as immutable classes (Display Objects)
|
|
|
|
---
|
|
|
|
## Restrictions
|
|
|
|
A Display/Formatter MUST NOT:
|
|
- contain business logic (e.g., "Team is full if count > 10")
|
|
- enforce domain invariants
|
|
- perform validation
|
|
- **be serialized** (only their primitive outputs are stored in `ViewData`)
|
|
- call `Intl.*` or `toLocale*` (unless explicitly marked for client-only ViewModels)
|
|
|
|
---
|
|
|
|
## Relationship to ViewData and ViewModels
|
|
|
|
### The "Primitive Compact" (Server-Side)
|
|
`ViewDataBuilders` MUST use **Formatters** to produce flat, serializable `ViewData`.
|
|
- **Rule:** `ViewData` properties assigned from a Display/Formatter MUST be primitives.
|
|
- **Reason:** Ensures `ViewData` remains a "dumb" JSON structure for SSR.
|
|
|
|
### The "Rich API" (Client-Side)
|
|
`ViewModels` MAY use **Display Objects** to provide interactive formatting.
|
|
- **Rule:** `ViewModels` can return `Display Object` instances to the UI.
|
|
- **Reason:** Allows the UI to access multiple variants (e.g., `date.short`, `date.relative`) without re-fetching data.
|
|
|
|
---
|
|
|
|
## Summary of the Flow
|
|
|
|
```mermaid
|
|
graph TD
|
|
DTO[Raw DTO] -->|ViewDataBuilder| VD[ViewData]
|
|
subgraph "Server: The Formatter Compact"
|
|
VD -->|Uses| F[Stateless Formatter]
|
|
F -->|Returns| S[Primitive string/number]
|
|
end
|
|
|
|
VD -->|SSR Boundary| T[Template]
|
|
|
|
subgraph "Client: The DisplayObject Richness"
|
|
T -->|Props| CW[ClientWrapper]
|
|
CW -->|new| VM[ViewModel]
|
|
VM -->|Wraps| DO[Rich Display Object]
|
|
DO -->|Provides| R[Rich API: .time, .relative, .date]
|
|
end
|
|
```
|
|
|
|
## Final Rule: Where does logic live?
|
|
|
|
1. **Is it a business rule?** (e.g., "Can join?") → **ViewModel**.
|
|
2. **Is it a formatting rule?** (e.g., "How to show date?") → **Formatter/Display**.
|
|
3. **Is it for SEO/SSR?** → **ViewDataBuilder** (using a Formatter).
|
|
4. **Is it for interaction?** → **ViewModel** (using a Display Object). |