Skip to content

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.

The circuit breaker has three states:

  1. Closed: Requests are allowed to pass through normally. If failures exceed a certain threshold, the circuit opens.
  2. Open: Requests are immediately rejected with a CircuitBreakerOpenException without reaching the network. After a timeout, the circuit enters the half-open state.
  3. Half-Open: A limited number of requests are allowed to pass through. If they succeed, the circuit closes. If they fail, it opens again.

By default, Fasq provides a fasqCircuitBreakerRegistryProvider that you can override to configure the global circuit breaker behavior.

ProviderScope(
overrides: [
fasqCircuitBreakerRegistryProvider.overrideWithValue(
CircuitBreakerRegistry(),
),
],
child: MyApp(),
)

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),
),
),
);

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.

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');
},
);

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.

Now that you’ve secured your queries with circuit breakers, you might want to look at: