106 lines
3.7 KiB
Markdown
106 lines
3.7 KiB
Markdown
# ViewData (Website Templates)
|
|
|
|
ViewData is the **only** allowed input type for Templates in `apps/website`.
|
|
|
|
Authoritative contract: [`WEBSITE_CONTRACT.md`](docs/architecture/website/WEBSITE_CONTRACT.md:1).
|
|
|
|
## 1) Definition
|
|
|
|
ViewData is a JSON-serializable, template-ready data structure:
|
|
|
|
- primitives (strings/numbers/booleans)
|
|
- arrays and plain objects
|
|
- `null` for missing values
|
|
|
|
**Uncle Bob says**: "Data structures should not have behavior." ViewData is a dumb container.
|
|
|
|
## 2) What ViewData is NOT
|
|
|
|
ViewData is not:
|
|
|
|
- an API Transport DTO (raw transport)
|
|
- a ViewModel (client-only class)
|
|
- a Display Object instance (rich API)
|
|
|
|
## 3) Construction rules
|
|
|
|
ViewData is created by **ViewData Builders**:
|
|
|
|
### Server Components (RSC)
|
|
```typescript
|
|
const apiDto = await PageQuery.execute();
|
|
const viewData = ViewDataBuilder.build(apiDto);
|
|
return <Template viewData={viewData} />;
|
|
```
|
|
|
|
### Client Components
|
|
```typescript
|
|
'use client';
|
|
|
|
const [viewModel, setViewModel] = useState<ViewModel | null>(null);
|
|
|
|
useEffect(() => {
|
|
const apiDto = await apiClient.get();
|
|
const viewData = ViewDataBuilder.build(apiDto);
|
|
const vm = ViewModelBuilder.build(viewData);
|
|
setViewModel(vm);
|
|
}, []);
|
|
|
|
// Template receives ViewData directly
|
|
return viewModel ? <Template viewData={viewData} /> : null;
|
|
```
|
|
|
|
Templates MUST NOT compute derived values.
|
|
|
|
ViewData Builders MUST NOT call the API.
|
|
|
|
**Important:** ViewModels are built from ViewData, not directly from DTOs. This ensures ViewModels are decoupled from the API transport layer.
|
|
|
|
## 4) Formatting and SEO
|
|
|
|
ViewData is responsible for providing **fully formatted strings** to Templates for Server-Side Rendering (SSR).
|
|
|
|
- **SEO Requirement:** All data required for search engines (prices, dates, counts, labels) MUST be formatted in the `ViewData Builder` on the server using **Formatters**.
|
|
- **Template Simplicity:** Templates should simply render the strings provided in `ViewData` without further processing.
|
|
|
|
## 5) Determinism rules
|
|
|
|
Any formatting used to produce ViewData MUST be deterministic.
|
|
|
|
Forbidden anywhere in formatting code paths:
|
|
|
|
- `Intl.*`
|
|
- `Date.toLocaleString()` / `Date.toLocaleDateString()` / `Date.toLocaleTimeString()`
|
|
|
|
Reason: SSR and browser outputs can differ.
|
|
|
|
Localization MUST NOT depend on runtime locale APIs.
|
|
If localized strings are required, they MUST be provided as deterministic inputs (for example via API-provided labels or a deterministic code-to-label map) and passed through ViewData Builders into ViewData.
|
|
|
|
## 6) Relationship to ViewModels
|
|
|
|
ViewData serves as the stable, serializable contract between the server and client. It is:
|
|
- The input for Templates (both SSR and Client)
|
|
- The input for ViewModelBuilders (Client-side state initialization)
|
|
|
|
ViewModels are built from ViewData, not from DTOs. This ensures:
|
|
- ViewModels remain decoupled from API transport concerns
|
|
- ViewModels can be initialized from any source that provides ViewData
|
|
- The ViewModel layer is purely for client-side interactive state
|
|
|
|
**Important:** ViewData MUST NOT contain ViewModel instances. ViewModels are classes with logic; ViewData is plain JSON.
|
|
|
|
## 7) Relationship to Formatters & Display Objects
|
|
|
|
Formatters are used to implement formatting/mapping, but their instances MUST NOT be stored inside ViewData.
|
|
|
|
Only primitive outputs produced by Formatters may appear in ViewData.
|
|
|
|
### The "Redundancy" Question
|
|
DTOs, ViewData, and ViewModels might share similar fields, but they are NOT redundant:
|
|
1. **DTO:** Raw API contract (Backend owned).
|
|
2. **ViewData:** Page-specific "bag of data" (Server-to-Client transport).
|
|
3. **ViewModel:** Interactive UI logic and computed properties (Client owned).
|
|
|
|
Using all three ensures that a change in the API doesn't break the Template, and a change in UI logic doesn't require a server-side deployment.
|