view models
This commit is contained in:
156
docs/architecture/FORM_MODELS.md
Normal file
156
docs/architecture/FORM_MODELS.md
Normal 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.
|
||||
156
docs/architecture/FORM_SUBMISSION.md
Normal file
156
docs/architecture/FORM_SUBMISSION.md
Normal 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.
|
||||
Reference in New Issue
Block a user