testes/Modulos Angular/projects/idt_app/docs/backend-integration/CQRS_PATTERN.md

5.3 KiB

🎯 CQRS Pattern Implementation - PraFrota Frontend

Overview

This document outlines how the PraFrota frontend implements the Command Query Responsibility Segregation (CQRS) pattern to align with our backend architecture.

🏗️ Service Layer Structure

Query Services

@Injectable({
  providedIn: 'root'
})
export class VehicleQueryService {
  constructor(private http: HttpClient) {}

  // Query Methods (Read Operations)
  getVehicles(filters: VehicleFilters): Observable<ResponsePaginated<Vehicle>> {
    return this.http.get<ResponsePaginated<Vehicle>>('/api/vehicle', { params: filters });
  }

  getVehicleById(id: string): Observable<Response<Vehicle>> {
    return this.http.get<Response<Vehicle>>(`/api/vehicle/${id}`);
  }

  searchVehicles(query: string): Observable<Response<Vehicle[]>> {
    return this.http.get<Response<Vehicle[]>>('/api/vehicle/search', { params: { query } });
  }
}

Command Services

@Injectable({
  providedIn: 'root'
})
export class VehicleCommandService {
  constructor(private http: HttpClient) {}

  // Command Methods (Write Operations)
  createVehicle(vehicle: CreateVehicleDto): Observable<Response<Vehicle>> {
    return this.http.post<Response<Vehicle>>('/api/vehicle', vehicle);
  }

  updateVehicle(id: string, vehicle: UpdateVehicleDto): Observable<Response<Vehicle>> {
    return this.http.put<Response<Vehicle>>(`/api/vehicle/${id}`, vehicle);
  }

  deleteVehicle(id: string): Observable<Response<void>> {
    return this.http.delete<Response<void>>(`/api/vehicle/${id}`);
  }
}

📦 Service Adapters

Service adapters bridge between our CQRS services and the BaseDomainComponent:

@Injectable({
  providedIn: 'root'
})
export class VehicleServiceAdapter implements IDomainServiceAdapter<Vehicle> {
  constructor(
    private queryService: VehicleQueryService,
    private commandService: VehicleCommandService
  ) {}

  // Query Operations
  getList(filters: any): Observable<ResponsePaginated<Vehicle>> {
    return this.queryService.getVehicles(filters);
  }

  getById(id: string): Observable<Response<Vehicle>> {
    return this.queryService.getVehicleById(id);
  }

  // Command Operations
  create(data: CreateVehicleDto): Observable<Response<Vehicle>> {
    return this.commandService.createVehicle(data);
  }

  update(id: string, data: UpdateVehicleDto): Observable<Response<Vehicle>> {
    return this.commandService.updateVehicle(id, data);
  }

  delete(id: string): Observable<Response<void>> {
    return this.commandService.deleteVehicle(id);
  }
}

🔄 Component Integration

BaseDomainComponent Usage

@Component({
  selector: 'app-vehicle',
  standalone: true,
  imports: [CommonModule, TabSystemComponent],
  templateUrl: './vehicle.component.html'
})
export class VehicleComponent extends BaseDomainComponent<Vehicle> {
  constructor(
    private queryService: VehicleQueryService,
    private commandService: VehicleCommandService,
    titleService: TitleService,
    headerActionsService: HeaderActionsService,
    cdr: ChangeDetectorRef
  ) {
    super(
      titleService,
      headerActionsService,
      cdr,
      new VehicleServiceAdapter(queryService, commandService)
    );
  }
}

📊 State Management

Query State

interface QueryState<T> {
  data: T[];
  loading: boolean;
  error: any;
  filters: any;
  pagination: {
    currentPage: number;
    pageSize: number;
    totalCount: number;
  };
}

Command State

interface CommandState<T> {
  data: T | null;
  loading: boolean;
  error: any;
  success: boolean;
}

🔐 Error Handling

Query Error Handling

@Injectable({
  providedIn: 'root'
})
export class QueryErrorHandler {
  handleError(error: HttpErrorResponse): Observable<never> {
    // Handle query-specific errors
    if (error.status === 404) {
      // Handle not found
    }
    return throwError(() => error);
  }
}

Command Error Handling

@Injectable({
  providedIn: 'root'
})
export class CommandErrorHandler {
  handleError(error: HttpErrorResponse): Observable<never> {
    // Handle command-specific errors
    if (error.status === 409) {
      // Handle conflicts
    }
    return throwError(() => error);
  }
}

📝 Best Practices

  1. Service Organization:

    • Separate query and command services
    • Use service adapters for component integration
    • Implement proper error handling
  2. State Management:

    • Maintain separate states for queries and commands
    • Use proper loading states
    • Handle errors appropriately
  3. Type Safety:

    • Use proper TypeScript interfaces
    • Implement proper DTOs
    • Use proper response types
  4. Performance:

    • Implement proper caching
    • Use proper pagination
    • Optimize queries

🔄 Implementation Checklist

When implementing CQRS in a new domain:

  1. Create Query Service
  2. Create Command Service
  3. Create Service Adapter
  4. Implement proper error handling
  5. Add proper loading states
  6. Implement proper caching
  7. Add proper validation
  8. Test all scenarios

📚 Additional Resources

  • Backend CQRS Documentation: /docs/mcp/CQRS_PATTERN.md
  • Frontend Architecture: /docs/ARCHITECTURE.md
  • Service Adapter Pattern: /docs/SERVICE_ADAPTER_PATTERN.md