mirror of
https://github.com/johndoe6345789/low-code-react-app-b.git
synced 2026-04-25 06:04:54 +00:00
368 lines
9.7 KiB
Markdown
368 lines
9.7 KiB
Markdown
# Storage Fallback Implementation
|
|
|
|
## Summary
|
|
|
|
Enhanced the storage adapter system to **automatically fallback to IndexedDB** when Flask backend fetch operations fail. This ensures the application remains functional even when the backend is temporarily unavailable or experiencing issues.
|
|
|
|
## Problem
|
|
|
|
Previously, when the Flask backend was configured but became unavailable during runtime:
|
|
- All storage operations would throw errors
|
|
- The application would fail to save/load data
|
|
- User experience would be degraded
|
|
- No automatic recovery mechanism existed
|
|
|
|
## Solution
|
|
|
|
Implemented a **transparent fallback mechanism** that:
|
|
1. Attempts operations on the Flask backend first (when configured)
|
|
2. Automatically retries failed operations using IndexedDB
|
|
3. Logs a warning on the first fallback (prevents console spam)
|
|
4. Ensures data persistence even during backend outages
|
|
5. Works silently without user intervention
|
|
|
|
## Changes Made
|
|
|
|
### 1. **Enhanced AutoStorageAdapter** (`src/lib/storage-adapter.ts`)
|
|
|
|
#### Added Fallback Infrastructure
|
|
```typescript
|
|
class AutoStorageAdapter implements StorageAdapter {
|
|
private adapter: StorageAdapter | null = null
|
|
private fallbackAdapter: IndexedDBAdapter | null = null // NEW
|
|
private backendType: 'flask' | 'indexeddb' | null = null
|
|
private hasWarnedAboutFallback = false // NEW
|
|
// ...
|
|
}
|
|
```
|
|
|
|
#### Fallback Initialization
|
|
```typescript
|
|
if (this.backendType === 'flask' && FLASK_BACKEND_URL) {
|
|
this.adapter = new FlaskBackendAdapter(FLASK_BACKEND_URL)
|
|
this.fallbackAdapter = new IndexedDBAdapter() // NEW: Always create fallback
|
|
console.log(`[StorageAdapter] Initialized with Flask backend: ${FLASK_BACKEND_URL} (with IndexedDB fallback)`)
|
|
}
|
|
```
|
|
|
|
#### Smart Execution Wrapper
|
|
```typescript
|
|
private async executeWithFallback<T>(
|
|
operation: () => Promise<T>,
|
|
fallbackOperation?: () => Promise<T>
|
|
): Promise<T> {
|
|
try {
|
|
return await operation() // Try primary backend
|
|
} catch (error) {
|
|
// If Flask failed and we have fallback, use it
|
|
if (this.backendType === 'flask' && this.fallbackAdapter && fallbackOperation) {
|
|
if (!this.hasWarnedAboutFallback) {
|
|
console.warn('[StorageAdapter] Flask backend operation failed, falling back to IndexedDB:', error)
|
|
this.hasWarnedAboutFallback = true // Only warn once
|
|
}
|
|
try {
|
|
return await fallbackOperation() // Retry with IndexedDB
|
|
} catch (fallbackError) {
|
|
console.error('[StorageAdapter] Fallback to IndexedDB also failed:', fallbackError)
|
|
throw fallbackError
|
|
}
|
|
}
|
|
throw error
|
|
}
|
|
}
|
|
```
|
|
|
|
#### Updated Storage Methods
|
|
All storage methods now use `executeWithFallback`:
|
|
|
|
```typescript
|
|
async get<T>(key: string): Promise<T | undefined> {
|
|
await this.initialize()
|
|
return this.executeWithFallback(
|
|
() => this.adapter!.get<T>(key),
|
|
this.fallbackAdapter ? () => this.fallbackAdapter!.get<T>(key) : undefined
|
|
)
|
|
}
|
|
|
|
async set<T>(key: string, value: T): Promise<void> {
|
|
await this.initialize()
|
|
return this.executeWithFallback(
|
|
() => this.adapter!.set(key, value),
|
|
this.fallbackAdapter ? () => this.fallbackAdapter!.set(key, value) : undefined
|
|
)
|
|
}
|
|
|
|
// Similar changes for: delete(), keys(), clear()
|
|
```
|
|
|
|
### 2. **Updated Documentation** (`FLASK_BACKEND_AUTO_DETECTION.md`)
|
|
|
|
Added new "Runtime Fallback" flow to explain automatic retry behavior.
|
|
|
|
## How It Works
|
|
|
|
### Scenario 1: Flask Backend Available
|
|
```
|
|
User calls: storage.set('key', 'value')
|
|
↓
|
|
AutoStorageAdapter.set()
|
|
↓
|
|
executeWithFallback()
|
|
↓
|
|
Try: FlaskBackendAdapter.set() → ✓ Success
|
|
↓
|
|
Return
|
|
```
|
|
|
|
### Scenario 2: Flask Backend Fails (Network Error)
|
|
```
|
|
User calls: storage.get('key')
|
|
↓
|
|
AutoStorageAdapter.get()
|
|
↓
|
|
executeWithFallback()
|
|
↓
|
|
Try: FlaskBackendAdapter.get() → ✗ Network Error
|
|
↓
|
|
Catch error → Check if fallback available
|
|
↓
|
|
Log warning (first time only)
|
|
↓
|
|
Try: IndexedDBAdapter.get() → ✓ Success
|
|
↓
|
|
Return data from IndexedDB
|
|
```
|
|
|
|
### Scenario 3: Flask Backend Timeout
|
|
```
|
|
User calls: storage.keys()
|
|
↓
|
|
AutoStorageAdapter.keys()
|
|
↓
|
|
executeWithFallback()
|
|
↓
|
|
Try: FlaskBackendAdapter.keys() → ✗ Timeout after 3s
|
|
↓
|
|
Catch timeout error
|
|
↓
|
|
Try: IndexedDBAdapter.keys() → ✓ Success
|
|
↓
|
|
Return keys from IndexedDB
|
|
```
|
|
|
|
### Scenario 4: IndexedDB Only (No Flask)
|
|
```
|
|
User calls: storage.set('key', 'value')
|
|
↓
|
|
AutoStorageAdapter.set()
|
|
↓
|
|
executeWithFallback()
|
|
↓
|
|
Try: IndexedDBAdapter.set() → ✓ Success
|
|
↓
|
|
Return (no fallback needed)
|
|
```
|
|
|
|
## Benefits
|
|
|
|
### 1. **Resilience**
|
|
- Application continues working during backend outages
|
|
- No data loss when network is unstable
|
|
- Graceful degradation of service
|
|
|
|
### 2. **User Experience**
|
|
- No visible errors to end users
|
|
- Seamless operation regardless of backend status
|
|
- Data always persists (either backend or browser)
|
|
|
|
### 3. **Development**
|
|
- Easier local development (backend optional)
|
|
- Reduced error handling complexity in application code
|
|
- Automatic recovery without code changes
|
|
|
|
### 4. **Production**
|
|
- Zero-downtime deployments possible
|
|
- Backend maintenance doesn't break frontend
|
|
- Network issues handled transparently
|
|
|
|
## Console Output Examples
|
|
|
|
### First Fallback Event
|
|
```
|
|
[StorageAdapter] Initialized with Flask backend: http://backend:5001 (with IndexedDB fallback)
|
|
[StorageAdapter] Flask backend operation failed, falling back to IndexedDB: TypeError: Failed to fetch
|
|
```
|
|
|
|
### Subsequent Fallback Events (No Spam)
|
|
```
|
|
(silent - already warned once)
|
|
```
|
|
|
|
### Normal Operation
|
|
```
|
|
[StorageAdapter] Initialized with Flask backend: http://backend:5001 (with IndexedDB fallback)
|
|
(operations succeed silently)
|
|
```
|
|
|
|
## Error Handling
|
|
|
|
### Fetch Failures That Trigger Fallback
|
|
- Network errors (`Failed to fetch`)
|
|
- Timeout errors (> 3 seconds)
|
|
- HTTP errors (500, 502, 503, 504)
|
|
- CORS errors
|
|
- DNS resolution failures
|
|
|
|
### Operations That Don't Fallback
|
|
- Already using IndexedDB as primary backend
|
|
- IndexedDB operation also fails (throws error up)
|
|
|
|
## Testing
|
|
|
|
### Test Fallback Behavior
|
|
|
|
1. **Start app with Flask backend configured:**
|
|
```bash
|
|
echo "VITE_USE_FLASK_BACKEND=true" > .env
|
|
echo "VITE_FLASK_BACKEND_URL=http://localhost:5001" >> .env
|
|
npm run dev
|
|
```
|
|
|
|
2. **Stop Flask backend:**
|
|
```bash
|
|
# In backend terminal: Ctrl+C
|
|
```
|
|
|
|
3. **Try storage operations:**
|
|
```typescript
|
|
// Should fallback to IndexedDB automatically
|
|
await storage.set('test', { value: 123 })
|
|
const result = await storage.get('test')
|
|
console.log(result) // { value: 123 } - from IndexedDB
|
|
```
|
|
|
|
4. **Check console:**
|
|
```
|
|
[StorageAdapter] Flask backend operation failed, falling back to IndexedDB: TypeError: Failed to fetch
|
|
```
|
|
|
|
5. **Verify data in IndexedDB:**
|
|
- Open Chrome DevTools → Application → IndexedDB
|
|
- Check `codeforge-db` → `storage` → `test` key
|
|
|
|
### Test Normal Operation
|
|
|
|
1. **Start both frontend and backend:**
|
|
```bash
|
|
# Terminal 1
|
|
cd backend && python app.py
|
|
|
|
# Terminal 2
|
|
npm run dev
|
|
```
|
|
|
|
2. **Perform storage operations:**
|
|
```typescript
|
|
await storage.set('test', { value: 123 })
|
|
```
|
|
|
|
3. **Verify no fallback warnings:**
|
|
```
|
|
(console should be clean, no fallback messages)
|
|
```
|
|
|
|
4. **Verify data in Flask backend:**
|
|
```bash
|
|
curl http://localhost:5001/api/storage/test
|
|
# {"value": {"value": 123}}
|
|
```
|
|
|
|
## Migration Considerations
|
|
|
|
### Data Consistency During Fallback
|
|
|
|
When fallback occurs:
|
|
- **Reading**: IndexedDB may have stale data if Flask backend was updated
|
|
- **Writing**: Data written to IndexedDB won't sync to Flask automatically
|
|
|
|
### Best Practices
|
|
|
|
1. **For Critical Data:**
|
|
- Implement sync mechanism when backend recovers
|
|
- Use optimistic UI updates with background sync
|
|
- Show user notification when in fallback mode
|
|
|
|
2. **For Non-Critical Data:**
|
|
- Current implementation is sufficient
|
|
- Data persists locally until backend available
|
|
- Manual export/import available if needed
|
|
|
|
3. **For Production:**
|
|
- Monitor backend availability
|
|
- Set up alerts for fallback events
|
|
- Implement periodic health checks
|
|
|
|
## Future Enhancements
|
|
|
|
Potential improvements:
|
|
|
|
1. **Auto-Sync When Backend Recovers**
|
|
```typescript
|
|
// Periodically check backend health
|
|
// If recovered, sync IndexedDB → Flask
|
|
async autoSyncOnRecovery() {
|
|
if (this.hasUsedFallback && await this.checkBackendAvailable()) {
|
|
await this.syncIndexedDBToFlask()
|
|
}
|
|
}
|
|
```
|
|
|
|
2. **Conflict Resolution**
|
|
```typescript
|
|
// Handle cases where data changed in both backends
|
|
async resolveConflicts(key: string) {
|
|
const flaskValue = await flaskAdapter.get(key)
|
|
const indexedDBValue = await indexedDBAdapter.get(key)
|
|
// Implement merge strategy
|
|
}
|
|
```
|
|
|
|
3. **User Notification**
|
|
```typescript
|
|
// Show toast when fallback occurs
|
|
if (!this.hasWarnedAboutFallback) {
|
|
toast.warning('Backend unavailable. Using local storage.')
|
|
}
|
|
```
|
|
|
|
4. **Fallback Metrics**
|
|
```typescript
|
|
// Track fallback frequency
|
|
interface FallbackStats {
|
|
count: number
|
|
lastFallback: Date
|
|
operations: string[]
|
|
}
|
|
```
|
|
|
|
## Related Files
|
|
|
|
- `src/lib/storage-adapter.ts` - Implementation
|
|
- `FLASK_BACKEND_AUTO_DETECTION.md` - Overall architecture
|
|
- `STORAGE_DEFAULT_INDEXEDDB.md` - Default behavior
|
|
- `STORAGE.md` - Complete storage documentation
|
|
|
|
## Success Criteria
|
|
|
|
✅ Flask backend failures automatically fallback to IndexedDB
|
|
✅ No user-visible errors during backend outages
|
|
✅ Data persists during fallback periods
|
|
✅ Warning logged on first fallback only
|
|
✅ Zero code changes required in consuming code
|
|
✅ Backward compatible with existing deployments
|
|
✅ Works in all scenarios (dev, prod, offline)
|
|
|
|
## Conclusion
|
|
|
|
The storage adapter now provides **resilient, transparent fallback** from Flask backend to IndexedDB. This ensures the application remains functional regardless of backend availability, providing a better user experience and reducing operational complexity.
|