mykholy/laravel-flowguard
Laravel FlowGuard adds configurable flow-control guards to your Laravel application, letting you define rules that inspect and constrain requests before they reach your handlers. Lightweight, easy to install, and simple to tune for your app's needs.
FlowGuard bundles four reliability primitives for Laravel: idempotent operations, a webhook inbox, a transactional outbox, and reconciliation tooling. The 1.0.2 release is documentation and Composer-metadata only — runtime code, API signatures, dependencies and schema are unchanged, so upgrading from 1.0.x is a composer update with no data migration, no reinstall and no fingerprint rewrite.
Minimal path:
composer require mykholy/laravel-flowguard:^1.0
php artisan migrate
php artisan flowguard:doctor
Where to look first, in order:
php artisan flowguard:doctor — run this immediately after install and again in your deploy hook. It's the fastest way to confirm migrations landed, the configured connection/tables resolve, and required config is present before you blame your own code.config/flowguard.php — publish it and read it end to end. Table names, connection, retry/timeout knobs and retention live here; several "gotchas" below are config-shaped.First use case: wrap a single non-idempotent side effect (capture a payment, call a provider's create-order endpoint) in an idempotent operation keyed by a client-supplied key, then add a webhook handler that persists verified payloads into the inbox before queueing processing. That covers the two primitives that cause the most production incidents and validates your install end to end.
Idempotent operations around outbound calls. Derive the key from something stable and scoped — typically Idempotency-Key header + authenticated tenant + route — and resolve it on the server. The 1.0.2 docs specifically call out server-derived merchant context: never trust a client-supplied tenant/merchant id in the key, or a caller can collide keys across tenants. Store the operation record and its outcome, and on replay return the stored result rather than re-executing. Handle the in-flight case explicitly (a second request arriving while the first is mid-call should get a conflict/retry signal, not a second side effect).
Inbox = verify first, persist raw, then process. Webhook signature verification is explicitly host-owned. So the order is: verify the signature → hand the raw payload plus the provider's event id to the inbox (dedupe on that provider id) → process asynchronously via a queued job → mark processed only on success. Persist the raw body: you'll want it for audit, for replay after a bug fix, and for reconciliation evidence.
Outbox = write in the same transaction as the domain change. Insert the domain row and the outbox row inside one DB::transaction, then let a relay/worker publish downstream afterwards. Never call the external system inside the request transaction — that's the failure mode the outbox exists to remove. Because delivery is at least once, every downstream consumer needs its own dedupe on your message identity.
Reconciliation as a scheduled job, not an incident response. Compare internal state against the provider (or against inbox/outbox records) on a schedule in routes/console.php, and let it repair drift — missed webhooks, outbox rows that never got acknowledged, operations stuck in-flight. Treat its output as a health metric; a growing reconciliation backlog is your earliest signal that the relay or webhook path is degraded.
Fault tolerance config over bespoke retries. Set timeouts and retry policy in config and let the package own the retry semantics; adding a second retry loop in your job or HTTP client multiplies attempts and breaks the idempotency assumptions underneath.
Testing. Use RefreshDatabase, fake the provider's HTTP layer, and assert that two calls with the same key produce exactly one provider request and identical responses. Add a test that replays a webhook payload twice and asserts one processed inbox row.
migrate:rollback as a deploy safety net.flowguard:doctor belongs in CI and in the deploy hook. It catches config/connection drift after environment changes faster than a failed request will.composer update is sufficient.How can I help you explore Laravel packages today?