Add feature flag to switch between HTTP and gRPC transports
Adds ApiModeConfig class with support for selecting API transport mode: - ApiMode enum (http, grpc) - Static configurations: development (HTTP), developmentGrpc, production, productionGrpc - fallbackToHttpOnError option for graceful degradation Creates unified deliveryRoutesProvider and deliveriesProvider that: - Respect apiModeConfigProvider for transport selection - Automatically delegate to gRPC or HTTP providers - Fall back to HTTP on gRPC failures when configured To enable gRPC, override apiModeConfigProvider with ApiModeConfig.developmentGrpc in the ProviderScope. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
4a9377e0a9
commit
a60f92c56d
@ -11,6 +11,100 @@ import '../models/user_profile.dart';
|
|||||||
import '../models/delivery_route.dart';
|
import '../models/delivery_route.dart';
|
||||||
import '../models/delivery.dart';
|
import '../models/delivery.dart';
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// API Mode Configuration - Feature Flag for HTTP/gRPC Transport
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/// Enum representing the available API transport modes.
|
||||||
|
enum ApiMode {
|
||||||
|
/// Use HTTP/REST-based CQRS API client
|
||||||
|
http,
|
||||||
|
|
||||||
|
/// Use gRPC-based CQRS API client
|
||||||
|
grpc,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Configuration for API transport mode selection.
|
||||||
|
///
|
||||||
|
/// This class allows switching between HTTP and gRPC transports
|
||||||
|
/// for API calls. Following the pattern from [ApiClientConfig].
|
||||||
|
///
|
||||||
|
/// Example usage:
|
||||||
|
/// ```dart
|
||||||
|
/// // To switch to gRPC, override the apiModeConfigProvider:
|
||||||
|
/// ProviderScope(
|
||||||
|
/// overrides: [
|
||||||
|
/// apiModeConfigProvider.overrideWithValue(ApiModeConfig.developmentGrpc),
|
||||||
|
/// ],
|
||||||
|
/// child: MyApp(),
|
||||||
|
/// )
|
||||||
|
/// ```
|
||||||
|
class ApiModeConfig {
|
||||||
|
/// The transport mode to use for API calls.
|
||||||
|
final ApiMode mode;
|
||||||
|
|
||||||
|
/// Whether to fall back to HTTP on gRPC failures.
|
||||||
|
/// Only applicable when [mode] is [ApiMode.grpc].
|
||||||
|
final bool fallbackToHttpOnError;
|
||||||
|
|
||||||
|
const ApiModeConfig({
|
||||||
|
required this.mode,
|
||||||
|
this.fallbackToHttpOnError = true,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Development configuration - defaults to HTTP for stability.
|
||||||
|
/// Use this for safe development when gRPC backend may be unavailable.
|
||||||
|
static const ApiModeConfig development = ApiModeConfig(
|
||||||
|
mode: ApiMode.http,
|
||||||
|
fallbackToHttpOnError: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
/// gRPC-first development configuration for testing gRPC integration.
|
||||||
|
/// Use this when actively developing/testing gRPC functionality.
|
||||||
|
static const ApiModeConfig developmentGrpc = ApiModeConfig(
|
||||||
|
mode: ApiMode.grpc,
|
||||||
|
fallbackToHttpOnError: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Production configuration - uses HTTP until gRPC is verified stable.
|
||||||
|
static const ApiModeConfig production = ApiModeConfig(
|
||||||
|
mode: ApiMode.http,
|
||||||
|
fallbackToHttpOnError: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Production gRPC configuration for when gRPC is production-ready.
|
||||||
|
static const ApiModeConfig productionGrpc = ApiModeConfig(
|
||||||
|
mode: ApiMode.grpc,
|
||||||
|
fallbackToHttpOnError: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Whether the current mode is gRPC.
|
||||||
|
bool get isGrpc => mode == ApiMode.grpc;
|
||||||
|
|
||||||
|
/// Whether the current mode is HTTP.
|
||||||
|
bool get isHttp => mode == ApiMode.http;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Provider for API mode configuration.
|
||||||
|
///
|
||||||
|
/// Override this provider to switch between HTTP and gRPC:
|
||||||
|
/// ```dart
|
||||||
|
/// ProviderScope(
|
||||||
|
/// overrides: [
|
||||||
|
/// apiModeConfigProvider.overrideWithValue(ApiModeConfig.developmentGrpc),
|
||||||
|
/// ],
|
||||||
|
/// child: MyApp(),
|
||||||
|
/// )
|
||||||
|
/// ```
|
||||||
|
final apiModeConfigProvider = Provider<ApiModeConfig>((ref) {
|
||||||
|
// Default to HTTP for safety during transition
|
||||||
|
return ApiModeConfig.development;
|
||||||
|
});
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Core Service Providers
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
final authServiceProvider = Provider<AuthService>((ref) {
|
final authServiceProvider = Provider<AuthService>((ref) {
|
||||||
return AuthService(config: AuthConfig.development);
|
return AuthService(config: AuthConfig.development);
|
||||||
});
|
});
|
||||||
@ -65,7 +159,9 @@ final authTokenProvider = FutureProvider<String?>((ref) async {
|
|||||||
return await authService.getToken();
|
return await authService.getToken();
|
||||||
});
|
});
|
||||||
|
|
||||||
final deliveryRoutesProvider = FutureProvider<List<DeliveryRoute>>((ref) async {
|
/// Internal HTTP-based delivery routes provider.
|
||||||
|
/// Use [deliveryRoutesProvider] instead, which respects the API mode configuration.
|
||||||
|
final _httpDeliveryRoutesProvider = FutureProvider<List<DeliveryRoute>>((ref) async {
|
||||||
final authService = ref.watch(authServiceProvider);
|
final authService = ref.watch(authServiceProvider);
|
||||||
final isAuthenticated = await authService.isAuthenticated();
|
final isAuthenticated = await authService.isAuthenticated();
|
||||||
|
|
||||||
@ -95,6 +191,39 @@ final deliveryRoutesProvider = FutureProvider<List<DeliveryRoute>>((ref) async {
|
|||||||
return result.whenSuccess((routes) => routes) ?? [];
|
return result.whenSuccess((routes) => routes) ?? [];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// Unified delivery routes provider that respects the API mode configuration.
|
||||||
|
///
|
||||||
|
/// Automatically switches between HTTP and gRPC based on [apiModeConfigProvider].
|
||||||
|
/// When gRPC mode is enabled and [ApiModeConfig.fallbackToHttpOnError] is true,
|
||||||
|
/// falls back to HTTP on gRPC failures.
|
||||||
|
///
|
||||||
|
/// Example usage:
|
||||||
|
/// ```dart
|
||||||
|
/// final routes = ref.watch(deliveryRoutesProvider);
|
||||||
|
/// routes.when(
|
||||||
|
/// data: (data) => displayRoutes(data),
|
||||||
|
/// loading: () => showLoading(),
|
||||||
|
/// error: (error, stack) => showError(error),
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
|
final deliveryRoutesProvider = FutureProvider<List<DeliveryRoute>>((ref) async {
|
||||||
|
final apiModeConfig = ref.watch(apiModeConfigProvider);
|
||||||
|
|
||||||
|
if (apiModeConfig.isGrpc) {
|
||||||
|
try {
|
||||||
|
return await ref.watch(grpcDeliveryRoutesProvider.future);
|
||||||
|
} catch (e) {
|
||||||
|
if (apiModeConfig.fallbackToHttpOnError) {
|
||||||
|
debugPrint('gRPC failed, falling back to HTTP: $e');
|
||||||
|
return await ref.watch(_httpDeliveryRoutesProvider.future);
|
||||||
|
}
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return await ref.watch(_httpDeliveryRoutesProvider.future);
|
||||||
|
});
|
||||||
|
|
||||||
/// Provider for delivery routes using gRPC.
|
/// Provider for delivery routes using gRPC.
|
||||||
///
|
///
|
||||||
/// This is the gRPC-based alternative to [deliveryRoutesProvider].
|
/// This is the gRPC-based alternative to [deliveryRoutesProvider].
|
||||||
@ -173,7 +302,9 @@ final grpcDeliveriesProvider = FutureProvider.family<List<Delivery>, int>((ref,
|
|||||||
return [...deliveries, Delivery.createWarehouseDelivery()];
|
return [...deliveries, Delivery.createWarehouseDelivery()];
|
||||||
});
|
});
|
||||||
|
|
||||||
final deliveriesProvider = FutureProvider.family<List<Delivery>, int>((ref, routeFragmentId) async {
|
/// Internal HTTP-based deliveries provider.
|
||||||
|
/// Use [deliveriesProvider] instead, which respects the API mode configuration.
|
||||||
|
final _httpDeliveriesProvider = FutureProvider.family<List<Delivery>, int>((ref, routeFragmentId) async {
|
||||||
final authService = ref.watch(authServiceProvider);
|
final authService = ref.watch(authServiceProvider);
|
||||||
final isAuthenticated = await authService.isAuthenticated();
|
final isAuthenticated = await authService.isAuthenticated();
|
||||||
|
|
||||||
@ -213,6 +344,41 @@ final deliveriesProvider = FutureProvider.family<List<Delivery>, int>((ref, rout
|
|||||||
return [...deliveries, Delivery.createWarehouseDelivery()];
|
return [...deliveries, Delivery.createWarehouseDelivery()];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// Unified deliveries provider that respects the API mode configuration.
|
||||||
|
///
|
||||||
|
/// Automatically switches between HTTP and gRPC based on [apiModeConfigProvider].
|
||||||
|
/// When gRPC mode is enabled and [ApiModeConfig.fallbackToHttpOnError] is true,
|
||||||
|
/// falls back to HTTP on gRPC failures.
|
||||||
|
///
|
||||||
|
/// Takes a [routeFragmentId] parameter to fetch deliveries for a specific route.
|
||||||
|
///
|
||||||
|
/// Example usage:
|
||||||
|
/// ```dart
|
||||||
|
/// final deliveries = ref.watch(deliveriesProvider(routeFragmentId));
|
||||||
|
/// deliveries.when(
|
||||||
|
/// data: (data) => displayDeliveries(data),
|
||||||
|
/// loading: () => showLoading(),
|
||||||
|
/// error: (error, stack) => showError(error),
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
|
final deliveriesProvider = FutureProvider.family<List<Delivery>, int>((ref, routeFragmentId) async {
|
||||||
|
final apiModeConfig = ref.watch(apiModeConfigProvider);
|
||||||
|
|
||||||
|
if (apiModeConfig.isGrpc) {
|
||||||
|
try {
|
||||||
|
return await ref.watch(grpcDeliveriesProvider(routeFragmentId).future);
|
||||||
|
} catch (e) {
|
||||||
|
if (apiModeConfig.fallbackToHttpOnError) {
|
||||||
|
debugPrint('gRPC failed for route $routeFragmentId, falling back to HTTP: $e');
|
||||||
|
return await ref.watch(_httpDeliveriesProvider(routeFragmentId).future);
|
||||||
|
}
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return await ref.watch(_httpDeliveriesProvider(routeFragmentId).future);
|
||||||
|
});
|
||||||
|
|
||||||
/// Provider to get all deliveries from all routes
|
/// Provider to get all deliveries from all routes
|
||||||
final allDeliveriesProvider = FutureProvider<List<Delivery>>((ref) async {
|
final allDeliveriesProvider = FutureProvider<List<Delivery>>((ref) async {
|
||||||
final routes = await ref.read(deliveryRoutesProvider.future);
|
final routes = await ref.read(deliveryRoutesProvider.future);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user