website refactor
This commit is contained in:
151
docs/architecture/website/MUTATIONS.md
Normal file
151
docs/architecture/website/MUTATIONS.md
Normal file
@@ -0,0 +1,151 @@
|
||||
# Mutations (Strict)
|
||||
|
||||
This document defines the **Mutation** pattern for `apps/website`.
|
||||
|
||||
Mutations exist to provide framework-agnostic write operations.
|
||||
|
||||
## 1) Definition
|
||||
|
||||
A **Mutation** is a framework-agnostic operation that orchestrates writes.
|
||||
|
||||
Mutations are the write equivalent of PageQueries.
|
||||
|
||||
## 2) Relationship to Next.js Server Actions
|
||||
|
||||
**Server Actions are the entry point**, but they should be thin wrappers:
|
||||
|
||||
```typescript
|
||||
// app/admin/actions.ts (Next.js framework code)
|
||||
'use server';
|
||||
|
||||
import { AdminUserMutation } from '@/lib/mutations/admin/AdminUserMutation';
|
||||
import { revalidatePath } from 'next/cache';
|
||||
|
||||
export async function updateUserStatus(userId: string, status: string): Promise<void> {
|
||||
try {
|
||||
const mutation = new AdminUserMutation();
|
||||
await mutation.updateUserStatus(userId, status);
|
||||
revalidatePath('/admin/users');
|
||||
} catch (error) {
|
||||
console.error('updateUserStatus failed:', error);
|
||||
throw new Error('Failed to update user status');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 3) Mutation Structure
|
||||
|
||||
```typescript
|
||||
// lib/mutations/admin/AdminUserMutation.ts
|
||||
export class AdminUserMutation {
|
||||
private service: AdminService;
|
||||
|
||||
constructor() {
|
||||
// Manual DI for serverless
|
||||
const logger = new ConsoleLogger();
|
||||
const errorReporter = new EnhancedErrorReporter(logger, {
|
||||
showUserNotifications: true,
|
||||
logToConsole: true,
|
||||
reportToExternal: process.env.NODE_ENV === 'production',
|
||||
});
|
||||
const baseUrl = process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:3001';
|
||||
const apiClient = new AdminApiClient(baseUrl, errorReporter, logger);
|
||||
this.service = new AdminService(apiClient);
|
||||
}
|
||||
|
||||
async updateUserStatus(userId: string, status: string): Promise<void> {
|
||||
await this.service.updateUserStatus(userId, status);
|
||||
}
|
||||
|
||||
async deleteUser(userId: string): Promise<void> {
|
||||
await this.service.deleteUser(userId);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 4) Why This Pattern?
|
||||
|
||||
**Benefits:**
|
||||
|
||||
1. **Framework independence** - Mutations can be tested without Next.js
|
||||
2. **Consistent pattern** - Mirrors PageQueries for reads/writes
|
||||
3. **Easy migration** - Can switch frameworks without rewriting business logic
|
||||
4. **Testable** - Can unit test mutations in isolation
|
||||
5. **Reusable** - Can be called from other contexts (cron jobs, etc.)
|
||||
|
||||
## 5) Naming Convention
|
||||
|
||||
- Mutations: `*Mutation.ts`
|
||||
- Server Actions: `actions.ts` (thin wrappers)
|
||||
|
||||
## 6) File Structure
|
||||
|
||||
```
|
||||
lib/
|
||||
mutations/
|
||||
admin/
|
||||
AdminUserMutation.ts
|
||||
AdminLeagueMutation.ts
|
||||
league/
|
||||
LeagueJoinMutation.ts
|
||||
team/
|
||||
TeamUpdateMutation.ts
|
||||
```
|
||||
|
||||
## 7) Non-negotiable Rules
|
||||
|
||||
1. **Server Actions are thin wrappers** - They only handle framework concerns (revalidation, redirects)
|
||||
2. **Mutations handle infrastructure** - They create services, handle errors
|
||||
3. **Services handle business logic** - They orchestrate API calls
|
||||
4. **Mutations are framework-agnostic** - No Next.js imports except in tests
|
||||
5. **Mutations must be deterministic** - Same inputs = same outputs
|
||||
|
||||
## 8) Comparison with PageQueries
|
||||
|
||||
| Aspect | PageQuery | Mutation |
|
||||
|--------|-----------|----------|
|
||||
| Purpose | Read data | Write data |
|
||||
| Location | `lib/page-queries/` | `lib/mutations/` |
|
||||
| Framework | Called from RSC | Called from Server Actions |
|
||||
| Infrastructure | Manual DI | Manual DI |
|
||||
| Returns | Page DTO | void or result |
|
||||
| Revalidation | N/A | Server Action handles it |
|
||||
|
||||
## 9) Example Flow
|
||||
|
||||
**Read:**
|
||||
```
|
||||
RSC page.tsx
|
||||
↓
|
||||
PageQuery.execute()
|
||||
↓
|
||||
Service
|
||||
↓
|
||||
API Client
|
||||
↓
|
||||
Page DTO
|
||||
```
|
||||
|
||||
**Write:**
|
||||
```
|
||||
Client Component
|
||||
↓
|
||||
Server Action
|
||||
↓
|
||||
Mutation.execute()
|
||||
↓
|
||||
Service
|
||||
↓
|
||||
API Client
|
||||
↓
|
||||
Revalidation
|
||||
```
|
||||
|
||||
## 10) Enforcement
|
||||
|
||||
ESLint rules should enforce:
|
||||
- Server Actions must call Mutations (not Services directly)
|
||||
- Mutations must not import Next.js (except in tests)
|
||||
- Mutations must use services
|
||||
|
||||
See `docs/architecture/website/WEBSITE_GUARDRAILS.md` for details.
|
||||
Reference in New Issue
Block a user