119 lines
2.9 KiB
Markdown
119 lines
2.9 KiB
Markdown
Command Models
|
|
|
|
This document defines Command Models as a concept for frontend form handling.
|
|
|
|
**IMPORTANT**: Command Models are **optional UX helpers**. They are NOT enforced by ESLint rules and do not belong in the strict architecture contract.
|
|
|
|
## 1) Definition
|
|
|
|
Command Models (also called Form Models) are UX-only write models used to:
|
|
- Collect user input
|
|
- Track form state (dirty, touched, submitting)
|
|
- Perform basic UX validation
|
|
- Build Command DTOs for submission
|
|
|
|
**Command Models are NOT:**
|
|
- Domain models
|
|
- View models
|
|
- Security boundaries
|
|
- Required for the architecture
|
|
|
|
## 2) Purpose
|
|
|
|
Use Command Models when:
|
|
- Forms have complex state management
|
|
- Multiple fields need validation
|
|
- You want to centralize form logic
|
|
- Building DTOs is non-trivial
|
|
|
|
**Don't use Command Models when:**
|
|
- Forms are simple (use React state directly)
|
|
- You're building a quick prototype
|
|
- The form logic is trivial
|
|
|
|
## 3) Core Rules
|
|
|
|
If you use Command Models:
|
|
|
|
**They MUST:**
|
|
- Live in `components/` or `hooks/` (not `lib/`)
|
|
- Be write-only (never reused for reads)
|
|
- Be discarded after submission
|
|
- Only perform UX validation
|
|
|
|
**They MUST NOT:**
|
|
- Contain business logic
|
|
- Enforce domain rules
|
|
- Reference View Models or Domain Entities
|
|
- Be sent to the API directly (use `toCommand()`)
|
|
|
|
## 4) Example
|
|
|
|
```typescript
|
|
// In your component file or hooks/
|
|
export class SignupFormModel {
|
|
email = '';
|
|
password = '';
|
|
isSubmitting = false;
|
|
|
|
isValid(): boolean {
|
|
// UX validation only
|
|
return this.email.includes('@') && this.password.length >= 8;
|
|
}
|
|
|
|
toCommand(): SignupCommandDto {
|
|
return {
|
|
email: this.email,
|
|
password: this.password,
|
|
};
|
|
}
|
|
}
|
|
|
|
// Usage
|
|
export function SignupForm() {
|
|
const [form] = useState(() => new SignupFormModel());
|
|
|
|
async function handleSubmit() {
|
|
if (!form.isValid()) return;
|
|
|
|
form.isSubmitting = true;
|
|
const result = await signupMutation.mutateAsync(form.toCommand());
|
|
form.isSubmitting = false;
|
|
}
|
|
|
|
return (
|
|
<form onSubmit={handleSubmit}>
|
|
<input value={form.email} onChange={e => form.email = e.target.value} />
|
|
{/* ... */}
|
|
</form>
|
|
);
|
|
}
|
|
```
|
|
|
|
## 5) Key Principle
|
|
|
|
**Command Models are optional.** The backend must validate everything.
|
|
|
|
If you don't use Command Models, that's fine! Just:
|
|
- Use React state for form data
|
|
- Let the backend handle validation
|
|
- Return clear errors from mutations
|
|
|
|
## 6) Comparison
|
|
|
|
| Approach | When to Use | Where |
|
|
|----------|-------------|-------|
|
|
| **React State** | Simple forms, prototypes | Component |
|
|
| **Command Model** | Complex forms, multi-step | Component/Hook |
|
|
| **View Model** | Read-only UI state | `lib/view-models/` |
|
|
| **Service** | Business orchestration | `lib/services/` |
|
|
|
|
## 7) Summary
|
|
|
|
Command Models are **optional UX sugar**. They:
|
|
- Help organize complex forms
|
|
- Are NOT required by the architecture
|
|
- Don't need ESLint enforcement
|
|
- Should stay in `components/` or `hooks/`
|
|
|
|
Use them if they make your life easier. Skip them if they don't. |