Skip to content

v3.4.1

Latest

Choose a tag to compare

@zfhassaan zfhassaan released this 02 Jun 04:35
· 6 commits to master since this release
1b8a552

A purely additive patch on top of 3.4.0 that introduces two new
capability layers — a gRPC-lite typed RPC stack and the
Laravel Messaging Platform (service discovery, sagas-as-a-facade,
typed-message dispatch, dead-letter management, declarative retry,
monitoring dashboard, causation IDs, MessageStore, and an async
Laravel-event bridge).

No migration required. Every new feature is opt-in; existing
publish/consume code, handler signatures, configuration layouts, and
the public surface from 3.4.0 continue to work unchanged.

Compatibility

  • PHP: 7.3 through 8.5 (PHP 8+ required to use #[Retry] attributes).
  • Laravel: 8.x through 13.x (Lumen 8.x+).
  • PHPUnit: ^9.6 on PHP 7.3/7.4; ^10.5|^11.5|^12.0 on PHP 8.0+.
  • Every new file under src/ parses as PHP 7.3 — verified by
    scripts/check-php73-compat.php (curated) and
    scripts/check-php73-compat-all-src.php (entire tree) using
    nikic/php-parser.

gRPC-lite RPC

A typed, service-oriented RPC layer that feels like gRPC but rides on
RabbitMQ. Built as a thin abstraction over the existing
Request/Consumer::reply() RPC primitive — no new dependencies, no
new transports.

  1. Service contract & DTOs

    • Bschmitt\Amqp\Rpc\RpcService — abstract contract with
      queue(), methods() (mapping request DTO → handler method), and
      optional name() / exchange() / routingKey() overrides.
    • Bschmitt\Amqp\Rpc\RpcRequest / Bschmitt\Amqp\Rpc\RpcResponse
      DTOs with a make(array $payload) factory built on the existing
      TypedMessage reflection hydration.
    • RpcRequest::responseClass() lets a request declare its typed
      reply so Rpc::call() hydrates the response automatically.
  2. Rpc facade & dispatcher

    • Bschmitt\Amqp\Rpc\RpcDispatcher coordinates symmetric
      call() / serve() / register() flow with x-rpc-service and
      x-rpc-request headers for routing and tracing.
    • Bschmitt\Amqp\Facades\Rpc auto-registered via composer.json
      (Rpc::call(UserService::class, GetUserRequest::make([...]))).
    • Amqp::rpcDispatcher() accessor for non-facade contexts.
    • Container-resolvable handler FQCNs (Rpc::register(UserService::class, UserServiceHandler::class)->serve(UserService::class)).
  3. Error & timeout typing

    • Bschmitt\Amqp\Rpc\RpcException — remote-handler errors carry
      the original exception class name and message.
    • Bschmitt\Amqp\Rpc\RpcTimeoutException — distinct type so callers
      can branch on "no reply" vs "remote error".
    • Server-side handler exceptions are caught, wrapped into an
      _rpc_error envelope, and surfaced to the client as the typed
      exceptions above.
  4. Configurable per-call

    • Global Rpc::defaultTimeout($seconds).
    • Per-call timeout and extra publish properties:
      Rpc::call(UserService::class, GetUserRequest::make([...]), 5, ['exchange' => 'rpc.svc']).

Laravel Messaging Platform

Higher-level building blocks that turn the package from "an AMQP
client" into a Laravel-first microservice toolkit. Every item below is
purely additive and ships with unit-test coverage.

  1. Service discovery (Rpc::service('payments'))

    • Bschmitt\Amqp\Rpc\ServiceRegistry — register short names → service
      FQCNs (Rpc::services()->register('payments', PaymentsService::class)).
    • autodiscover([...]) honours an opt-in public static function alias()
      method on RpcService subclasses.
    • Bschmitt\Amqp\Rpc\ServiceCaller — fluent caller with timeout()
      and withProperties() chaining;
      Rpc::service($alias|$fqcn)->call($request).
  2. Saga facade + compensate() syntax

    • Saga::make() static factory and a new top-level Saga facade
      (auto-registered via composer.json).
    • Fluent compensation:
      ->step('reserve', $reserve)->compensate($release).
    • Backwards-compatible: the old three-argument
      step($name, $action, $compensation) form still works.
  3. Message contract dispatch

    • TypedMessage::make(array $payload) and
      TypedMessage::dispatch(array $payload, array $properties = [])
      static helpers.
    • TypedMessage::dispatchLater(array $payload, int $delayMs) mirrors
      the delayed publisher.
    • Resolves the Amqp singleton from the Laravel container; throws a
      clear RuntimeException when called outside Laravel.
  4. Dead-letter management (Amqp::deadLetters())

    • Bschmitt\Amqp\Support\DeadLetterManager fluent API:
      for($queue)->count() / messages($limit) / replayTo($target, $limit)
      / purge().
    • Inspection uses the Management API; replay and purge use the AMQP
      channel directly so they work even when the management plugin is
      disabled.
  5. #[Retry] attribute + RetryStrategy

    • Bschmitt\Amqp\Attributes\Retry(attempts, strategy, delayMs, maxDelayMs, jitter) with PHP 8+ attribute target.
    • Bschmitt\Amqp\Support\RetryStrategy::{FIXED|EXPONENTIAL|LINEAR|NONE}
      string constants (PHP 7.3-safe — uses class constants, not enums).
    • RetryPolicy::fromAttribute($class, $method = null) reflection
      helper builds an existing RetryPolicy from the attribute.
    • PHP 7.x silently ignores the attribute marker (parsed as a
      comment), so the package still loads on older runtimes — only the
      reflection lookup requires PHP 8+.
  6. Monitoring dashboard + amqp:monitor

    • Bschmitt\Amqp\Support\MonitoringDashboard aggregates
      MetricsCollector (in-process counters) and Management API queue
      stats into a single JSON-safe snapshot.
    • Amqp::dashboard($queues)->snapshot() returns
      ['process' => ..., 'queues' => ..., 'overview' => ..., 'generated' => ...].
    • php artisan amqp:monitor --queue=orders [--queue=...] [--json] [--connection=]
      Artisan command (Bschmitt\Amqp\Console\Commands\AmqpMonitorCommand)
      for ops / CI / scrape targets.
  7. Causation ID propagation

    • CorrelationContext now tracks a causation id alongside the
      correlation id with setCausation() / getCausation() and a new
      CAUSATION_HEADER constant.
    • CorrelationContext::inheritFromMessage($incoming) captures the
      inbound message_id as the causation id of anything published
      next — letting downstream services trace
      "this happened because of that" through a chain.
    • applyToPublishProperties() adds an x-causation-id header
      alongside the existing correlation headers.
  8. MessageStore (Bschmitt\Amqp\Contracts\MessageStoreInterface)

    • Append-only log API:
      append() / find() / all($filters) / count($filters) / purge().
    • Bschmitt\Amqp\Support\InMemoryMessageStore default
      implementation (good for tests and small workloads).
    • Amqp::setMessageStore($store) / Amqp::messageStore() accessors;
      publish and consume both auto-record when a store is attached.
    • Foundation for durable replay / event-sourcing-style audit trails
      — implement the interface against Eloquent / Redis / files / S3
      for production use.
  9. Async Laravel events (ShouldPublishToAmqpInterface)

    • Marker interface for Laravel events that should auto-publish to
      RabbitMQ — mark the event, event(new OrderCreated(...)) becomes
      a publish.
    • Bschmitt\Amqp\Events\AmqpEventListener wildcard listener
      handles routing key, payload, and exchange resolution with
      overridable amqpRouting() / amqpPayload() / amqpExchange()
      hooks on the event.
    • Disabled by default; opt-in with
      amqp.broadcast_laravel_events => true.

New & Updated Public Surface

  • New classes:
    • Bschmitt\Amqp\Rpc\{RpcDispatcher, RpcService, RpcRequest, RpcResponse, RpcMessage, RpcException, RpcTimeoutException, ServiceCaller, ServiceRegistry}
    • Bschmitt\Amqp\Facades\{Rpc, Saga}
    • Bschmitt\Amqp\Attributes\Retry
    • Bschmitt\Amqp\Support\{RetryStrategy, DeadLetterManager, InMemoryMessageStore, MonitoringDashboard}
    • Bschmitt\Amqp\Contracts\{MessageStoreInterface, ShouldPublishToAmqpInterface}
    • Bschmitt\Amqp\Events\AmqpEventListener
    • Bschmitt\Amqp\Console\Commands\AmqpMonitorCommand
  • New Amqp facade methods: rpcDispatcher(), deadLetters(),
    dashboard($queues), setMessageStore(), messageStore().
  • Extended classes (backwards-compatible):
    Bschmitt\Amqp\Support\{Saga, TypedMessage, RetryPolicy, CorrelationContext},
    Bschmitt\Amqp\Rpc\RpcDispatcher, Bschmitt\Amqp\Providers\AmqpServiceProvider.

New & Updated Documentation

  • New pages:
    • docs/content/grpc-lite-rpc.md
    • docs/content/messaging-platform.md
  • New sidebar entries (docs/app.js) and feature cards
    (docs/index.html).
  • README.md gains a "gRPC-lite RPC" section and a
    "Laravel Messaging Platform" section with full usage examples for
    every item above.

Tests

  • 11 new unit-test files added under test/Unit/:
    Rpc/RpcDispatcherTest, Rpc/RpcMessageTest,
    Rpc/ServiceRegistryTest, Rpc/ServiceCallerTest,
    SagaFacadeTest, DeadLetterManagerTest,
    InMemoryMessageStoreTest, MonitoringDashboardTest,
    RetryAttributeTest, CausationContextTest,
    AmqpEventListenerTest.
  • New fixtures under test/Support/Fixtures/Rpc/:
    UserService, UserServiceHandler, GetUserRequest,
    GetUserResponse, CreateUserRequest.
  • Total unit suite: 444 tests / 1004 assertions (was 405 / 925 in
    the 3.4.0 baseline).
  • Full suite passes on PHP 7.3 through 8.5. Deprecation warnings on
    PHP 8.4+ continue to come exclusively from the vendored Mockery
    library and predate this release.

Migration

No migration required. Everything below is opt-in:

  • The new Rpc and Saga facades are auto-registered via
    composer.json aliases — they only become visible to user code when
    imported.
  • TypedMessage::make() / dispatch() / dispatchLater() are new
    static helpers; the existing instance-based publish path is
    untouched.
  • DeadLetterManager, MonitoringDashboard, and MessageStore are
    attached only when their factory methods are called or
    setMessageStore(...) is invoked.
  • The #[Retry] attribute is read only when application code asks
    for it via RetryPolicy::fromAttribute(...); existing RetryPolicy
    factories continue to work as before.
  • CorrelationContext keeps its previous API; the new
    setCausation() / getCausation() / CAUSATION_HEADER constants
    and x-causation-id header only appear when callers actively
    populate the causation slot (e.g. via inheritFromMessage()).
  • The async-Laravel-events bridge is only registered when
    amqp.broadcast_laravel_events is set to true in config/amqp.php.

What's Changed

Full Changelog: v3.4.0...v3.4.1