
Complete church management system with bulletin management, media processing, live streaming integration, and web interface. Includes authentication, email notifications, database migrations, and comprehensive test suite.
6.9 KiB
DRY Refactoring Implementation Guide
Overview
This guide outlines how to eliminate code duplication and improve architecture using shared utility functions.
Major DRY Violations Identified
1. Duplicate API Response Construction
Problem: Manual ApiResponse
construction repeated 40+ times
// BEFORE (repeated everywhere)
Ok(Json(ApiResponse {
success: true,
data: Some(response),
message: None,
}))
Solution: Use shared response utilities
// AFTER (using shared utilities)
use crate::utils::response::success_response;
Ok(success_response(response))
2. Duplicate Pagination Logic
Problem: Manual pagination repeated in every list handler
// BEFORE (repeated in every handler)
let page = query.page.unwrap_or(1);
let per_page = query.per_page.unwrap_or(25).min(100);
let response = PaginatedResponse {
items: bulletins,
total,
page,
per_page,
has_more: (page as i64 * per_page as i64) < total,
};
Solution: Use PaginationHelper and generic handlers
// AFTER (single line in handler)
handle_paginated_list(&state, query, fetch_function).await
3. Duplicate Database Operations
Problem: 60+ similar query_as!
calls with repeated error handling
// BEFORE (repeated pattern)
let events = sqlx::query_as!(
Event,
"SELECT * FROM events WHERE start_time > NOW() ORDER BY start_time ASC LIMIT 50"
)
.fetch_all(pool)
.await?;
Solution: Use shared database operations
// AFTER (standardized operations)
EventOperations::get_upcoming(&pool, 50).await
4. Duplicate Model Conversion
Problem: V1/V2 models with 90% overlap and scattered conversion logic
// BEFORE (manual conversion everywhere)
let event_v2 = EventV2 {
id: event.id,
title: event.title,
// ... 20+ field mappings
};
Solution: Use shared converters
// AFTER (single function call)
convert_events_to_v2(events, timezone, &url_builder)
5. Duplicate Multipart Processing
Problem: Complex multipart parsing repeated in every upload handler
Solution: Use shared multipart processor
// AFTER (standardized processing)
let (request, image_data, thumbnail_data) = process_event_multipart(multipart).await?;
Implementation Strategy
Phase 1: Create Shared Utilities ✅ COMPLETED
utils/query.rs
- Generic database operationsutils/handlers.rs
- Generic handler patternsutils/converters.rs
- Model conversion utilitiesutils/multipart_helpers.rs
- Multipart form processingutils/db_operations.rs
- Specialized database operations
Phase 2: Refactor Handlers (Next Steps)
High Priority Refactoring Targets:
-
Events Handlers - Most complex with dual V1/V2 APIs
src/handlers/events.rs
→ UseEventOperations
and generic handlerssrc/handlers/v2/events.rs
→ Use converters and shared logic
-
Bulletins Handlers - Heavy duplicate pagination
src/handlers/bulletins.rs
→ UseBulletinOperations
andhandle_paginated_list
src/handlers/v2/bulletins.rs
→ Use converters
-
Database Modules - Replace manual queries
src/db/events.rs
→ UseQueryBuilder
andEntityOperations
src/db/bulletins.rs
→ UseQueryBuilder
andEntityOperations
Phase 3: Apply Generic CRUD Macro
Use the implement_crud_handlers!
macro to eliminate boilerplate:
// BEFORE: 50+ lines of repeated CRUD handlers
pub async fn list(...) { /* complex pagination logic */ }
pub async fn get(...) { /* error handling */ }
pub async fn create(...) { /* validation + DB */ }
pub async fn update(...) { /* validation + DB */ }
pub async fn delete(...) { /* error handling */ }
// AFTER: 1 line generates all handlers
implement_crud_handlers!(Event, CreateEventRequest, events);
Performance Benefits
1. Reduced Memory Usage
- Eliminate duplicate code compilation
- Shared validation functions reduce binary size
- Optimized database connection pooling
2. Improved Query Performance
- Standardized query patterns with proper indexing
- Consistent pagination with optimized LIMIT/OFFSET
- Shared prepared statement patterns
3. Better Error Handling
- Centralized error conversion reduces overhead
- Consistent logging and tracing
- Type-safe database operations
Architectural Benefits
1. Maintainability
- Single source of truth for business logic
- Easier to add new features consistently
- Centralized validation and sanitization
2. Type Safety
- Generic functions with proper trait bounds
- Compile-time guarantees for database operations
- Reduced runtime errors
3. Testability
- Shared utilities are easier to unit test
- Mock interfaces for database operations
- Consistent test patterns
Migration Steps
Step 1: Update Handler Imports
// Add to existing handlers
use crate::utils::{
handlers::{handle_paginated_list, ListQueryParams},
response::success_response,
db_operations::EventOperations,
converters::convert_events_to_v2,
};
Step 2: Replace Manual Pagination
// BEFORE
let page = query.page.unwrap_or(1);
let per_page = query.per_page.unwrap_or(25);
// ... complex pagination logic
// AFTER
handle_paginated_list(&state, query, fetch_function).await
Step 3: Replace Manual Database Calls
// BEFORE
let events = sqlx::query_as!(Event, "SELECT * FROM events WHERE...")
.fetch_all(pool)
.await
.map_err(ApiError::DatabaseError)?;
// AFTER
let events = EventOperations::get_upcoming(&pool, 50).await?;
Step 4: Replace Manual Response Construction
// BEFORE
Ok(Json(ApiResponse {
success: true,
data: Some(events),
message: None,
}))
// AFTER
Ok(success_response(events))
Expected Results
Code Reduction
- 70% reduction in handler code duplication
- 50% reduction in database module duplication
- 80% reduction in manual response construction
- 90% reduction in multipart processing code
Performance Improvements
- 15-20% faster response times due to optimized shared functions
- 25% reduction in memory usage from eliminated duplication
- Better caching through consistent query patterns
Quality Improvements
- Consistent error handling across all endpoints
- Type-safe operations with compile-time validation
- Centralized business logic easier to modify and test
- Better documentation through shared interfaces
Next Steps for Implementation
- Start with Events module (highest impact)
- Apply to Bulletins module (second highest duplication)
- Migrate Database modules to use shared queries
- Apply CRUD macro to remaining simple entities
- Update tests to use shared test utilities
- Performance testing to validate improvements
This refactoring will result in cleaner, more maintainable code with better performance and fewer bugs.