5.3 KiB
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
-
Service Organization:
- Separate query and command services
- Use service adapters for component integration
- Implement proper error handling
-
State Management:
- Maintain separate states for queries and commands
- Use proper loading states
- Handle errors appropriately
-
Type Safety:
- Use proper TypeScript interfaces
- Implement proper DTOs
- Use proper response types
-
Performance:
- Implement proper caching
- Use proper pagination
- Optimize queries
🔄 Implementation Checklist
When implementing CQRS in a new domain:
- Create Query Service
- Create Command Service
- Create Service Adapter
- Implement proper error handling
- Add proper loading states
- Implement proper caching
- Add proper validation
- 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