Files
gridpilot.gg/docs/architecture/website/WEBSITE_CONTRACT_WRITE_FLOW.md
2026-01-13 01:36:27 +01:00

3.8 KiB

Write Flow Update (Mutation Pattern)

This document updates the write flow section of WEBSITE_CONTRACT.md to use the Mutation pattern.

Write Flow (Strict)

All writes MUST enter through Next.js Server Actions.

Forbidden

  • client components performing write HTTP requests
  • client components calling API clients for mutations

Allowed

  • client submits intent (FormData, button action)
  • server action performs UX validation
  • server action calls a Mutation (not Services directly)
  • Mutation creates infrastructure and calls Service
  • Service orchestrates API calls and business logic

Server Actions must use Mutations

// ❌ WRONG - Direct service usage
'use server';
import { AdminService } from '@/lib/services/admin/AdminService';

export async function updateUserStatus(userId: string, status: string) {
  const service = new AdminService(...);
  await service.updateUserStatus(userId, status); // ❌ Should use mutation
}
// ✅ CORRECT - Mutation usage
'use server';
import { AdminUserMutation } from '@/lib/mutations/admin/AdminUserMutation';
import { revalidatePath } from 'next/cache';

export async function updateUserStatus(userId: string, status: string) {
  const mutation = new AdminUserMutation();
  const result = await mutation.updateUserStatus(userId, status);
  
  if (result.isErr()) {
    console.error('updateUserStatus failed:', result.getError());
    throw new Error('Failed to update user status');
  }
  
  revalidatePath('/admin/users');
}

Mutation Pattern

// lib/mutations/admin/AdminUserMutation.ts
import { Result, ResultFactory } from '@/lib/contracts/Result';

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<Result<void, string>> {
    try {
      await this.service.updateUserStatus(userId, status);
      return ResultFactory.ok(undefined);
    } catch (error) {
      return ResultFactory.error('UPDATE_USER_STATUS_FAILED');
    }
  }
}

Flow

  1. Server Action (thin wrapper) - handles framework concerns (revalidation, redirects)
  2. Mutation (framework-agnostic) - creates infrastructure, calls service
  3. Service (business logic) - orchestrates API calls
  4. API Client (infrastructure) - makes HTTP requests
  5. Result - type-safe error handling

Rationale

  • Framework independence: Mutations can be tested without Next.js
  • Consistency: Mirrors PageQuery pattern for reads/writes
  • Type-safe errors: Result pattern eliminates exceptions
  • Migration ease: Can switch frameworks without rewriting business logic
  • Testability: Can unit test mutations in isolation
  • Reusability: Can be called from other contexts (cron jobs, etc.)

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 Result<ApiDto, string> Result<void, string>
Revalidation N/A Server Action handles it

See Also