Riverpod Circuit Breaker
The Circuit Breaker pattern is used to stop making requests to a service that is likely to fail, preventing your application from wasting resources and potentially overwhelming the failing service.
Fasq provides a built-in circuit breaker implementation that integrates seamlessly with Riverpod.
How it Works
Section titled “How it Works”The circuit breaker has three states:
- Closed: Requests are allowed to pass through normally. If failures exceed a certain threshold, the circuit opens.
- Open: Requests are immediately rejected with a
CircuitBreakerOpenExceptionwithout reaching the network. After a timeout, the circuit enters the half-open state. - Half-Open: A limited number of requests are allowed to pass through. If they succeed, the circuit closes. If they fail, it opens again.
Global Setup
Section titled “Global Setup”By default, Fasq provides a fasqCircuitBreakerRegistryProvider that you can override to configure the global circuit breaker behavior.
ProviderScope( overrides: [ fasqCircuitBreakerRegistryProvider.overrideWithValue( CircuitBreakerRegistry(), ), ], child: MyApp(),)Configuring Queries
Section titled “Configuring Queries”You can enable and configure the circuit breaker for individual queries by passing CircuitBreakerOptions to the queryProvider.
final userProvider = queryProvider<User>( 'user', () => fetchUser(), options: QueryOptions( circuitBreaker: CircuitBreakerOptions( failureThreshold: 5, resetTimeout: Duration(seconds: 30), ), ),);Circuit Breaker Scope
Section titled “Circuit Breaker Scope”By default, circuit breakers are scoped to the unique query key. This means if one query fails, it won’t affect other unrelated queries.
However, you can group multiple related queries under a single circuit breaker (e.g., all queries that hit the same API domain) using circuitBreakerScope.
final postsProvider = queryProvider<List<Post>>( 'posts', () => fetchPosts(), options: QueryOptions( circuitBreakerScope: 'api.example.com', circuitBreaker: CircuitBreakerOptions(failureThreshold: 10), ),);
final commentsProvider = queryProvider<List<Comment>>( 'comments', () => fetchComments(), options: QueryOptions( circuitBreakerScope: 'api.example.com', // Shares the same breaker as 'posts' ),);When multiple queries share a scope, the CircuitBreakerOptions from the
first query that initializes the breaker will be used. Subsequent queries
sharing the same scope will use the existing breaker.
Handling Circuit Breaker Exceptions
Section titled “Handling Circuit Breaker Exceptions”When the circuit is open, subsequent requests will throw a CircuitBreakerOpenException. You should handle this in your UI to show a friendly message to the user.
final userAsync = ref.watch(userProvider);
return userAsync.when( data: (user) => UserProfile(user: user), loading: () => LoadingIndicator(), error: (error, stack) { if (error is CircuitBreakerOpenException) { return Text('Service is temporarily unavailable. Please try again later.'); } return Text('Error: $error'); },);Mutations
Section titled “Mutations”While MutationOptions does not explicitly take CircuitBreakerOptions yet, mutations will still honor the global circuit breaker state if they share a scope with a protected query, or if you manually access the CircuitBreakerRegistry through the QueryClient.
Direct circuit breaker support for mutations is planned for a future update.
Next Steps
Section titled “Next Steps”Now that you’ve secured your queries with circuit breakers, you might want to look at: