Skip to main content

MessageRouter

Pluggable inbound routing interface used by ReceiverActor to resolve each incoming Messenger envelope to the Nexus ActorRef that should receive its message.

What it does​

MessageRouter is a single-method interface. ReceiverActor calls route() for every envelope it drains from the transport. Returning null marks the message unroutable and triggers the UnroutablePolicy configured on ReceiverActorConfig (reject or dead-letters).

Two concrete implementations ship with the package:

MapMessageRouter​

Exact PHP message class → ActorRef lookup. This is the right choice for most applications where message types are known at startup.

/**
* @param Route<object> ...$routes
*/
public function __construct(Route ...$routes)

Routes are built through the typed Route::to() boundary, which checks at analysis time that each target ref handles its routed message class. route() returns the registered ref for $message::class or null if the class is not in the map. The lookup is an O(1) array fetch.

StampMessageRouter​

Cluster seam: resolves the TargetActorPathStamp on the envelope against a path-keyed registry. Use this when a remote producer stamps the target actor path (e.g., when routing across cluster nodes). Messages without the stamp, or with a path not in the registry, are unroutable.

/**
* @param array<string, ActorRef<object>> $registry keyed by actor-path string
* @param TargetAuthorizer|null $authorizer authorizes producer → target routing per envelope
*/
public function __construct(private array $registry, private ?TargetAuthorizer $authorizer = null)

route() reads $envelope->last(TargetActorPathStamp::class) and looks up $registry[$stamp->path].

Authorizing producer → target routes​

Because the target is selected by a producer-controlled stamp, a producer with publish rights could otherwise invoke any registered target and consume its capacity (SEC-012). Pass a TargetAuthorizer to gate this: a resolved target is only returned if the authorizer permits the envelope's producer to reach it. A denied envelope is unroutable — the ReceiverActor rejects or dead-letters it per its policy — so an unauthorized producer never reaches the target actor.

MapTargetAuthorizer is a static allowlist mapping a producer identity (read from the ProducerIdentityStamp, wire header X-Nexus-Producer-Identity) to the exact target paths it may reach. It fails closed: no identity stamp, an unknown identity, or a target outside that identity's list is denied (and logged when a PSR-3 logger is supplied).

$router = new StampMessageRouter(
['/user/orders' => $ordersRef, '/user/payments' => $paymentsRef],
new MapTargetAuthorizer([
'orders-svc' => ['/user/orders'],
'billing-svc' => ['/user/payments'],
]),
);
Trust boundary

A ProducerIdentityStamp proves origin only as far as the producer is trusted. Across mutually untrusted producers the identity must be established or validated at a trusted boundary — an authenticated transport, a broker ACL that stamps identity, or a signed envelope — otherwise a producer can assert any identity. The authorizer enforces the ACL; the trust in the identity comes from the boundary that set it. Pair it with broker-side ACLs when producers are not mutually trusted.

Omitting the authorizer preserves the previous behavior (any producer may reach any registered target), so this is a backward-compatible, opt-in tightening.

Interface​

interface MessageRouter
{
/**
* @return ActorRef<object>|null null means unroutable
*/
public function route(object $message, Envelope $envelope): ?ActorRef;
}

Example​

src/bootstrap.php
use Monadial\Nexus\Messenger\Routing\MapMessageRouter;
use Monadial\Nexus\Messenger\Routing\Route;
use Monadial\Nexus\Messenger\Routing\StampMessageRouter;

// Type-based routing (most common)
$router = new MapMessageRouter(
Route::to(OrderPlaced::class, $ordersActor),
Route::to(PaymentMade::class, $paymentsActor),
);

// Path-stamp routing (cluster seam)
$router = new StampMessageRouter([
'/user/orders' => $ordersActor,
'/user/payments' => $paymentsActor,
]);

// Custom router: route on any envelope property
$router = new class implements MessageRouter {
public function route(object $message, Envelope $envelope): ?ActorRef {
// inspect stamps, message fields, etc.
return $message instanceof PriorityMessage
? $this->priorityRef
: $this->defaultRef;
}
};

Full API reference​

MessageRouter interface · MapMessageRouter · StampMessageRouter

See also​