view models

This commit is contained in:
2025-12-18 00:08:47 +01:00
parent f7a56a92ce
commit 7c449af311
56 changed files with 2594 additions and 206 deletions

View File

@@ -0,0 +1,156 @@
Form Models
This document defines Form Models as a first-class concept in the frontend architecture.
Form Models are UX-only write models used to collect, validate, and prepare user input
before it is sent to the backend as a Command DTO.
Form Models are not View Models and not Domain Models.
Purpose
A Form Model answers the question:
“What does the UI need in order to safely submit user input?”
Form Models exist to:
• centralize form state
• reduce logic inside components
• provide consistent client-side validation
• build Command DTOs explicitly
Core Rules
Form Models:
• exist only in the frontend
• are write-only (never reused for reads)
• are created per form
• are discarded after submission
Form Models MUST NOT:
• contain business logic
• enforce domain rules
• reference View Models
• reference Domain Entities or Value Objects
• be sent to the API directly
Relationship to Other Models
API DTO (read) → ViewModel → UI
UI Input → FormModel → Command DTO → API
• View Models are read-only
• Form Models are write-only
• No model is reused across read/write boundaries
Typical Responsibilities
A Form Model MAY:
• store field values
• track dirty / touched state
• perform basic UX validation
• expose isValid, canSubmit
• build a Command DTO
A Form Model MUST NOT:
• decide if an action is allowed
• perform authorization checks
• validate cross-aggregate rules
Validation Guidelines
Client-side validation is UX validation, not business validation.
Allowed validation examples:
• required fields
• min / max length
• email format
• numeric ranges
Forbidden validation examples:
• “user is not allowed”
• “league already exists”
• “quota exceeded”
Server validation is the source of truth.
Example: Simple Form Model (with class-validator)
import { IsEmail, IsNotEmpty, MinLength } from 'class-validator';
export class SignupFormModel {
@IsEmail()
email = '';
@IsNotEmpty()
@MinLength(8)
password = '';
isSubmitting = false;
reset(): void {
this.email = '';
this.password = '';
}
toCommand(): SignupCommandDto {
return {
email: this.email,
password: this.password,
};
}
}
Usage in UI Component
const form = useFormModel(SignupFormModel);
async function onSubmit() {
if (!form.isValid()) return;
form.isSubmitting = true;
await authService.signup(form.toCommand());
}
The component:
• binds inputs to the Form Model
• reacts to validation state
• never builds DTOs manually
Testing
Form Models SHOULD be tested when they contain:
• validation rules
• non-trivial state transitions
• command construction logic
Form Models do NOT need tests if they only hold fields without logic.
Summary
• Form Models are UX helpers for writes
• They protect components from complexity
• They never replace backend validation
• They never leak into read flows
Form Models help users.
Use Cases protect the system.

View File

@@ -0,0 +1,156 @@
Form Submission Flow (UI → System)
This document defines the only valid data flow when a user submits a form.
It applies to all write operations (create, update, delete).
There are no exceptions.
Core Principle
Read and Write paths are different.
What is displayed is never sent back.
High-Level Flow
UI → Command DTO → API → Core Use Case → Persistence
• View Models are read-only
• Display Objects are read-only
• Commands are write-only
1. UI (Component)
Responsibility
• Collect user input
• Manage UX state (loading, disabled, local errors)
Rules
• Only primitives are handled (string, number, boolean)
• No DTO reuse
• No ViewModel reuse
• No domain objects
The UI does not decide whether an action is allowed.
2. Form Model (Optional)
Responsibility
• Local form state
• Client-side validation (required, min/max length)
• Field-level errors
Rules
• UX-only validation
• No business rules
• Never shared with API or Core
3. Command DTO (Frontend)
Responsibility
• Express intent
• Represent a write operation
Rules
• Created fresh on submit
• Never derived from a ViewModel
• Never reused from read DTOs
• Write-only
4. Frontend Service
Responsibility
• Orchestrate the write action
• Call the API Client
• Propagate success or failure
Rules
• No business logic
• No validation
• No UI decisions
• No ViewModel creation for writes (except explicit success summaries)
5. API Client (Frontend)
Responsibility
• Perform HTTP request
• Handle transport-level failures
Rules
• Stateless
• No retries unless explicitly designed
• Throws technical errors only
6. API Layer (Backend)
Responsibility
• HTTP boundary
• Transport validation (schema / class-validator)
• Map API DTO → Core Command
Rules
• No business logic
• No persistence
• No UI concerns
7. Core Use Case
Responsibility
• Enforce business rules
• Validate domain invariants
• Change system state
Rules
• Single source of truth
• No UI logic
• No HTTP knowledge
Response Handling
Success
• API returns a Result DTO (IDs, status)
• Frontend reacts by:
• navigation
• reload via GET
• toast / confirmation
Failure
• Business errors → user-visible message
• Technical errors → error boundary / monitoring
Forbidden Patterns
• ViewModel → Command
• DisplayObject → API
• DTO roundtrip
• Domain Object in UI
• Reusing read models for writes
Summary
• Read Flow: DTO → ViewModel → UI
• Write Flow: UI → Command DTO → Core
What is shown is never sent back.