Reputation: 103
I'm building an application with micro services approach. For communication between services I use Symfony Messenger with RMQ transport. Basically everything works fine, but all my services has to be in same namespace. Once I tried to separate them into their own namespaces like App\Mail
, App\Auth
and so on messenger was complaining about lack of Event classes because whole namesapce is provided within header of message sent to RMQ. Is there any way I could map events from two different namespaces?
For instance Auth
app dispatches event UserRegistered
so message has type of App\Auth\Event\UserRegistered
. I want to handle that event in my Mail app but messenger can't consume it because my Event and Handler are under App\Mail
namespace, so it can't find App\Auth\Event\UserRegistered
class in "Mail" app.
Example error I'm getting:
In Serializer.php line 85:
Could not decode message: Could not denormalize object of type App\Event\UserRequestedPasswordReset, no supporting normalizer found.
In this exact example I'm sending event UserRequestedPasswordReset from app that is under App
namespace, and I'm trying to consume it with application under App\Mail
namespace.
I wasn't able to find anything helpful in documentation or over the internet. I was trying to alias App\Event\UserRequestedPasswordReset
to App\Mail\Event\UserRequestedPasswordReset
in container but no luck. I'm guessing that it's something doable with Denormalizers, but also couldn't find anything helpful over internet.
Communication itself is working, messages are sent to RMQ and received in other services. My setup for RMQ is: I have multiple queues, one for each service. I have fanout exchange with those queues binded. Whenever I'm producing event I'm publishing it to exchange to populate it to all queues, so interested services can handle them.
Example messenger configuration in one of my services. Besides event I'm using messenger to handle CQRS commands and queries, so I'm using three different buses.
messenger:
default_bus: messenger.bus.commands
buses:
messenger.bus.commands:
middleware:
# - validation
# - doctrine_transaction
messenger.bus.queries:
middleware:
# - validation
messenger.bus.events:
default_middleware: allow_no_handlers
middleware:
# - validation
transports:
events:
dsn: "%env(MESSENGER_AMQP_DSN)%"
options:
exchange:
name: ecommerce_events
type: fanout
queue:
name: ecommerce_auth
routing:
'App\Event\UserCreated': events
'App\Event\UserModified': events
'App\Event\UserChangedPassword': events
'App\Event\UserRequestedPasswordReset': events
I would like to keep my applications in different namespaces and still be able to handle events from other services
Upvotes: 2
Views: 2517
Reputation: 103
So after digging into topic I was able to find solution.
I just needed to create custom serializer and then during encoding I was stripping off the namespace and then during decoding I was providing map for type to actual event class. Here is my code
class EventsSerializer extends Serializer
{
public function encode(Envelope $envelope): array
{
$data = parent::encode($envelope);
$data['headers']['type'] = $this->parseType($data['headers']['type']);
return $data;
}
private function parseType(string $type)
{
return end(explode('\\', $type));
}
public function decode(array $encodedEnvelope): Envelope
{
$translatedType = $this->translateType($encodedEnvelope['headers']['type']);
$encodedEnvelope['headers']['type'] = $translatedType;
return parent::decode($encodedEnvelope);
}
private function translateType($type)
{
$map = [
'UserCreated' => UserCreated::class,
'UserRequestedPasswordReset' => UserRequestedPasswordReset::class
];
return $map[$type] ?? $type;
}
}
In messenger configuration:
framework:
messenger:
serializer:
default_serializer: AppBundle\Serializer\EventsSerializer
Please bare in mind that this is more like proof of concept and it probably can be enhanced, but it's working.
Upvotes: 4