Skip to content

Add the CQRS query cache, messaging brokers, webhooks and callbacks, and harden the resilience store - #16

Merged
ancongui merged 49 commits into
mainfrom
feat/ports-parity
Oct 13, 2026
Merged

ancongui merged 49 commits into
mainfrom
feat/ports-parity

Conversation

@ancongui

Copy link
Copy Markdown
Contributor

Brings LaraFly's remaining single-adapter ports up to PyFly parity and adds inbound webhooks and outbound callbacks.

What ships

  • CQRS query cache. firefly.cqrs.query.cache.provider=none|cache (default none).
    • #[CachedQuery] sets a TTL per query. The cache key is derived from the query's value object; a query that can't be keyed reliably is rejected when it is scanned.
    • #[InvalidatesQuery] evicts cached results when committed domain events or EDA envelopes arrive, using generation-based group eviction.
    • A cache failure never fails the query.
  • Messaging brokers. firefly.messaging.provider=kafka|rabbitmq reuse the eda client factories. Listeners get retry/back-off policies and Kafka DLT or RabbitMQ DLX routing, and firefly:messaging:consume runs them.
  • Resilience store. firefly.resilience.store.provider=cache|memory. A cache store without atomic locks now fails at boot. Before, it silently dropped the critical section; you can still accept that explicitly with firefly.resilience.store.allow-lockless=true.
  • Health indicators for cache stores, queue connections and Redis, each behind its own config key.
  • firefly/webhooks. An inbound endpoint per provider:
    • Signature validators: HMAC, Stripe, GitHub, Twilio, NoOp.
    • A replay window and an idempotency store (memory|cache|database).
    • Accepted webhooks are published to firefly/eda; rejections are returned as problem+json.
    • firefly:webhooks:prune cleans up stored records.
  • firefly/callbacks. Outbound delivery driven by #[EventListener]:
    • A subscription registry (memory|database) and an execution log (memory|database).
    • HMAC-SHA256 signing, retries with back-off, and a circuit breaker per endpoint.
    • firefly:callbacks:{list,replay}.

Security fixes made during review

  • Twilio: the signed string is chosen from the URL, so a JSON body can no longer be swapped while the URL-only signature still validates.
  • Callback replay:
    • It never delivers to a deactivated subscription unless you pass --include-inactive.
    • It always resolves the destination from the subscription's current URL and runs the https and allowed-host check on it.

Verification

Includes origin/main (W1 and W2).

Run Result
composer check exit 0, 6515 passed
Integration on PostgreSQL 16, MySQL 8.4, MariaDB 11, SQLite, RabbitMQ, Kafka and Redis 219 passed, 0 failed
SQL Server lane (local container) 70 passed, 0 failed

Andres Contreras added 30 commits October 12, 2026 18:19
…andler manifest and derive cache keys from query value objects
…ic state and never key a Traversable by its empty view
…der and refuse to boot on a cache store without atomic locks
…ckless in the skeleton and the resilience module
…ys in the skeleton and the actuator module
…REFLY_HEALTH_*_ENABLED variables in the skeleton
…essaging:consume in the skeleton and the messaging module
…h retries, back-off and dead-letter topics
…listener.dead-letter.enabled in the skeleton
…ng with retries, back-off and a dead-letter exchange
…alth indicators so its backed-off panel holds only the seeded rows
…r dead-letter topic so the direct exchange never drops a dead letter as unroutable
…real RabbitMQ, dead-letter exchange included
…provider settings behind firefly.webhooks.enabled
Andres Contreras added 19 commits October 12, 2026 20:24
…yed JSON signature cannot carry a forged body
… so TrimStrings and ConvertEmptyStringsToNull cannot break the signature
…ortable receipts table proven on every database
…and publishes webhooks to the event bus, refusing with problem+json
…database, one row per subscription and event
… retries, back-off and a circuit breaker per endpoint
…nce store, health indicators, webhooks and callbacks
…liverer, so replay no longer reaches a deactivated endpoint without --include-inactive
…and run the destination refusal on it, recording a changed endpoint
…without --include-inactive and sends to the current URL
…Dsn, integration lanes) into feat/ports-parity

Conflicts kept both sides: the [Unreleased] changelog entries under their headings, the module tables, nav
rows and capability notes, and the messaging config keys, README and auto-configuration docblock. The lumen
outbox harness takes main's version. The query-cache and outbox lines both leave the CQRS known-latent list.

Counts recomputed from the tree: 34 component directories, 37 module guides, 22 of the 34 packages with a
#[Configuration] class, and eleven firefly:* commands contributed by capability packages; docs/cli.md gains
the four W4 commands it was missing.

W4's integration tests move onto Firefly\Testing\Integration\IntegrationDsn: the webhooks and callbacks
database tests run per IntegrationDsn::lanes() (sqlite, pgsql, mysql, mariadb, sqlsrv) and skip by
skipReason(), replacing their private DSN parsers; the Kafka, RabbitMQ and Redis tests read their variables
through it.
@ancongui
ancongui merged commit a345e88 into main Oct 13, 2026
12 checks passed
@ancongui ancongui mentioned this pull request Oct 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant