micro-module/fault-tolerance-bundle
A fault-tolerance bundle for PHP/Laravel apps that adds circuit breakers, retries, timeouts, and fallback handling to unreliable service calls. Configure policies per service and keep failures from cascading through your application.
This is a low-traffic package (0 stars, 0 dependents, single release in Sept 2025), so treat the source as the documentation. Budget 30–45 minutes to read it before wiring it into anything production-facing.
Install and inspect:
composer require micro-module/fault-tolerance-bundle
composer show micro-module/fault-tolerance-bundle # confirm version + autoload namespace
Where to look first, in order:
composer.json — check require for symfony/* vs illuminate/*. The name says bundle (Symfony terminology), so confirm whether a Laravel ServiceProvider is shipped or whether you must register services manually in config/app.php / bootstrap/providers.php.MicroModule\FaultTolerance\... or similar.*.yaml, *.php, or Resources/config/ directory. Copy it into config/fault-tolerance.php if the package supports publishing; otherwise wrap the defaults in your own config file.First use case: wrap a single outbound call to a flaky downstream microservice. Something like a retry (3 attempts, exponential backoff) with a hard timeout and a cached fallback response. Do not start by wrapping every HTTP client — pick one endpoint, observe behaviour in staging, then roll out. Verify the circuit breaker actually opens by pointing it at a black-hole host, not by trusting the README.
If no Laravel provider exists, create a thin wrapper service in your app that resolves the package classes from the container, so you can swap the implementation later without touching call sites.
Decorator around the HTTP client. The cleanest integration is a Laravel HTTP client macro or a small FaultTolerantClient class that wraps Http::...->send(). Keep the package's retry/breaker concerns out of your controllers and out of your domain services; put them in one adapter.
Retry for idempotent reads, breaker for everything. Retries on GET are safe; retries on POST need an idempotency key or you will double-charge customers. The circuit breaker is the pattern that protects you from a dead dependency, so apply it more broadly than retries.
Fallback as an explicit return type. If a fallback returns a cached/stale payload, make it the same DTO the happy path returns — otherwise callers end up with null checks scattered through the codebase. A Result-style wrapper ({ ok, data, degraded: true }) makes degraded mode visible in logs and metrics.
Queue jobs. Wrap the dispatch site, not the job body, if you want the breaker to shed load before jobs pile up in Redis. Wrapping inside handle() only retries work the queue already retries — you get multiplicative retries and a thundering herd on recovery. If you must wrap the job, set $tries = 1 on the job and let the package own the retry policy.
Shared breaker state via cache. Circuit breakers only work if every worker sees the same state. Configure the state store to Redis (or your shared cache), keyed by downstream service name plus environment. Array/file cache makes the breaker per-process and effectively decorative.
Composition order matters. Timeout outermost, then breaker, then retry, then the actual call. Reversing timeout and retry means a 3-retry budget can consume 3× your timeout. Reversing breaker and retry means retries bypass the open circuit.
Health checks and metrics. Emit a counter for breaker.opened, retry.attempt, fallback.used per downstream. A breaker that opens silently is worse than no breaker — you lose the signal that a dependency is failing.
Illuminate\Support\ServiceProvider, DI, config publishing, and php artisan config:cache semantics all need manual handling — and cached config means config changes require php artisan config:clear.composer.json, and read the diff before every bump. Do not use ^ constraints on a package with one release and no downstream users.connect_timeout and timeout are distinct. A package-level "timeout" may map to only one. Set both at the HTTP client level as a backstop.max_execution_time is not a timeout. It is a fatal error, not an exception — your fallback will not run. Keep the package timeout well under it, and under your queue worker's --timeout.RefreshDatabase does not touch Redis). Otherwise a test that trips a breaker poisons subsequent tests, and failures look random and order-dependent.sleep(), Carbon::setTestNow will not help and your test suite will get slow fast.How can I help you explore Laravel packages today?