Ecotone
SponsorBlogGithubSupport and ContactCommunity Channel
  • About
  • Installation
  • How to use
    • CQRS PHP
    • Event Handling PHP
    • Aggregates & Sagas
    • Scheduling in PHP
    • Asynchronous PHP
    • Event Sourcing PHP
    • Microservices PHP
    • Resiliency and Error Handling
    • Laravel Demos
    • Symfony Demos
      • Doctrine ORM
  • Tutorial
    • Before we start tutorial
    • Lesson 1: Messaging Concepts
    • Lesson 2: Tactical DDD
    • Lesson 3: Converters
    • Lesson 4: Metadata and Method Invocation
    • Lesson 5: Interceptors
    • Lesson 6: Asynchronous Handling
  • Enterprise
  • Modelling
    • Introduction
    • Message Bus and CQRS
      • CQRS Introduction - Commands
        • Query Handling
        • Event Handling
      • Aggregate Introduction
        • Aggregate Command Handlers
        • Aggregate Query Handlers
        • Aggregate Event Handlers
        • Advanced Aggregate creation
      • Repositories Introduction
      • Business Interface
        • Introduction
        • Business Repository
        • Database Business Interface
          • Converting Parameters
          • Converting Results
      • Saga Introduction
      • Identifier Mapping
    • Extending Messaging (Middlewares)
      • Message Headers
      • Interceptors (Middlewares)
        • Additional Scenarios
      • Intercepting Asynchronous Endpoints
      • Extending Message Buses (Gateways)
    • Event Sourcing
      • Installation
      • Event Sourcing Introduction
        • Working with Event Streams
        • Event Sourcing Aggregates
          • Working with Aggregates
          • Applying Events
          • Different ways to Record Events
        • Working with Metadata
        • Event versioning
        • Event Stream Persistence
          • Event Sourcing Repository
          • Making Stream immune to changes
          • Snapshoting
          • Persistence Strategies
          • Event Serialization and PII Data (GDPR)
      • Projection Introduction
        • Configuration
        • Choosing Event Streams for Projection
        • Executing and Managing
          • Running Projections
          • Projection CLI Actions
          • Access Event Store
        • Projections with State
        • Emitting events
    • Recovering, Tracing and Monitoring
      • Resiliency
        • Retries
        • Error Channel and Dead Letter
          • Dbal Dead Letter
        • Idempotent Consumer (Deduplication)
        • Resilient Sending
        • Outbox Pattern
        • Concurrency Handling
      • Message Handling Isolation
      • Ecotone Pulse (Service Dashboard)
    • Asynchronous Handling and Scheduling
      • Asynchronous Message Handlers
      • Asynchronous Message Bus (Gateways)
      • Delaying Messages
      • Time to Live
      • Message Priority
      • Scheduling
      • Dynamic Message Channels
    • Distributed Bus and Microservices
      • Distributed Bus
        • Distributed Bus with Service Map
          • Configuration
          • Custom Features
          • Non-Ecotone Application integration
          • Testing
        • AMQP Distributed Bus (RabbitMQ)
          • Configuration
        • Distributed Bus Interface
      • Message Consumer
      • Message Publisher
    • Business Workflows
      • The Basics - Stateless Workflows
      • Stateful Workflows - Saga
      • Handling Failures
    • Testing Support
      • Testing Messaging
      • Testing Aggregates and Sagas with Message Flows
      • Testing Event Sourcing Applications
      • Testing Asynchronous Messaging
  • Messaging and Ecotone In Depth
    • Overview
    • Multi-Tenancy Support
      • Getting Started
        • Any Framework Configuration
        • Symfony and Doctrine ORM
        • Laravel
      • Different Scenarios
        • Hooking into Tenant Switch
        • Shared and Multi Database Tenants
        • Accessing Current Tenant in Message Handler
        • Events and Tenant Propagation
        • Multi-Tenant aware Dead Letter
      • Advanced Queuing Strategies
    • Document Store
    • Console Commands
    • Messaging concepts
      • Message
      • Message Channel
      • Message Endpoints/Handlers
        • Internal Message Handler
        • Message Router
        • Splitter
      • Consumer
      • Messaging Gateway
      • Inbound/Outbound Channel Adapter
    • Method Invocation And Conversion
      • Method Invocation
      • Conversion
        • Payload Conversion
        • Headers Conversion
    • Service (Application) Configuration
    • Contributing to Ecotone
      • How Ecotone works under the hood
      • Ecotone Phases
      • Registering new Module Package
      • Demo Integration with SQS
        • Preparation
        • Inbound and Outbound Adapters and Message Channel
        • Message Consumer and Publisher
  • Modules
    • Overview
    • Symfony
      • Symfony Configuration
      • Symfony Database Connection (DBAL Module)
      • Doctrine ORM
      • Symfony Messenger Transport
    • Laravel
      • Laravel Configuration
      • Database Connection (DBAL Module)
      • Eloquent
      • Laravel Queues
      • Laravel Octane
    • Ecotone Lite
      • Logging
      • Database Connection (DBAL Module)
    • JMS Converter
    • OpenTelemetry (Tracing and Metrics)
      • Configuration
    • RabbitMQ Support
    • Kafka Support
      • Configuration
      • Message partitioning
      • Usage
    • DBAL Support
    • Amazon SQS Support
    • Redis Support
  • Other
    • Contact, Workshops and Support
Powered by GitBook
On this page
  • Handling Events
  • Multiple Subscriptions
  • Subscribe to Interface or Abstract Class
  • Subscribing by Union Classes
  • Subscribing to All Events
  • Subscribing to Events by Routing
  • Subscribing to Events by Routing and Class Name
  • Sending Events with Metadata
  • Metadata Propagation

Was this helpful?

Export as PDF
  1. Modelling
  2. Message Bus and CQRS
  3. CQRS Introduction - Commands

Event Handling

Event CQRS PHP

PreviousQuery HandlingNextAggregate Introduction

Last updated 1 month ago

Was this helpful?

Be sure to read before diving in this chapter.

Handling Events

External Event Handlers are Services available in your dependency container, which are defined to handle Events.

class TicketService
{
    #[EventHandler] 
    public function when(TicketWasCreated $event): void
    {
        // handle event
    }
}

Events are Plain Old PHP Objects:

class readonly TicketWasCreated
{
    public function __construct(
        public string $ticketId
    ) {}
}

The difference between Events and Command is in intention. Commands are meant to trigger an given action and events are information that given action was performed successfully.

class TicketService
{
    #[CommandHandler] 
    public function createTicket(
        CreateTicketCommand $command,
        EventBus $eventBus
    ) : void
    {
        // handle create ticket command
        
        $eventBus->publish(new TicketWasCreated($ticketId));
    }
}

EventBus is available in your Dependency Container by default, just like Command and Query buses. You may use Ecotone's feature to inject it directly into your Command Handler's method.

Just like EventBus is injected directly into your Command Handler, you may inject any other Service. This way you may make is clear what object your Command Handler needs in order to perform his action.

Multiple Subscriptions

Unlike Command Handlers which points to specific Command Handler, Event Handlers can have multiple subscribing Event Handlers.

class TicketService
{
    #[EventHandler] 
    public function when(TicketWasCreated $event): void
    {
        // handle event
    }
}

class NotificationService
{
    #[EventHandler] 
    public function when(TicketWasCreated $event): void
    {
        // handle event
    }
}

Subscribe to Interface or Abstract Class

If your Event Handler is interested in all Events around specific business concept, you may subscribe to Interface or Abstract Class.

interface TicketEvent
{
}
class readonly TicketWasCreated implements TicketEvent
{
    public function __construct(
        public string $ticketId
    ) {}
}

class readonly TicketWasCancelled implements TicketEvent
{
    public function __construct(
        public string $ticketId
    ) {}
}

And then instead of subscribing to TicketWasCreated or TicketWasCancelled, we will subscribe to TicketEvent.

#[EventHandler]
public function notify(TicketEvent $event) : void
{
   // do something with $event
}

Subscribing by Union Classes

We can also subscribe to different Events using union type hint. This way we can ensure that only given set of events will be delivered to our Event Handler.

#[EventHandler]
public function notify(TicketWasCreated|TicketWasCancelled $event) : void
{
   // do something with $event
}

Subscribing to All Events

We may subscribe to all Events published within the application. To do it we type hint for generic object.

#[EventHandler]
public function log(object $event) : void
{
   // do something with $event
}

Subscribing to Events by Routing

Events can also be subscribed by Routing.

class TicketService
{
    #[EventHandler("ticket.was_created")] 
    public function when(TicketWasCreated $event): void
    {
        // handle event
    }
}

And then Event is published with routing key

class TicketService
{
    #[CommandHandler] 
    public function createTicket(
        CreateTicketCommand $command,
        EventBus $eventBus
    ) : void
    {
        // handle create ticket command
        
        $eventBus->publishWithRouting(
            "ticket.was_created",
            new TicketWasCreated($ticketId)
        );
    }
}

Subscribing to Events by Routing and Class Name

There may be situations when we will want to subscribe given method to either routing or class name. Ecotone those subscriptions separately to protect from unnecessary wiring, therefore to handle this case, we can simply add another Event Handler which is not based on routing key.

class TicketService
{
    #[EventHandler]
    #[EventHandler("ticket.was_created")] 
    public function when(TicketWasCreated $event): void
    {
        // handle event
    }
}

This way we explicitly state that we want to subscribe by class name and by routing key.

Sending Events with Metadata

Just like with Command Bus, we may pass metadata to the Event Bus:

class TicketService
{
    #[CommandHandler] 
    public function createTicket(
        CreateTicketCommand $command,
        EventBus $eventBus
    ) : void
    {
        // handle create ticket command
        
        $eventBus->publish(
            new TicketWasCreated($ticketId),
            metadata: [
                "executorId" => $command->executorId()
            ]
        );
    }
}
class TicketService
{
    #[EventHandler] 
    public function when(
        TicketWasCreated $event,
        // access metadata with given name
        #[Header("executorId")] string $executorId
    ): void
    {
        // handle event
    }
}

Metadata Propagation

By default Ecotone will ensure that your Metadata is propagated. This way you can simplify your code by avoiding passing around Headers and access them only in places where it matters for your business logic.

To better understand that, let's consider example in which we pass the metadata to the Command.

class TicketController
{
   public function __construct(private CommandBus $commandBus) {}
   
   public function closeTicketAction(Request $request, Security $security) : Response
   {
      $this->commandBus->nd(
         new CloseTicketCommand($request->get("ticketId")),
         ["executorId" => $security->getUser()->getId()]
      );
   }
}
$messagingSystem->getCommandBus()->send(
   new CloseTicketCommand($ticketId),
   ["executorId" => $executorId]
);

However in order to perform closing ticket logic, information about the executorId is not needed, so we don't access that.

class TicketService
{   
    #[CommandHandler]
    public function closeTicket(
        CloseTicketCommand $command, 
        EventBus $eventBus
    )
    {     
        // close the ticket
             
        // we simply publishing an Event, we don't pass any metadata here 
        $eventBus->publish(new TicketWasCreated($ticketId));
    }   
}

However Ecotone will ensure that your metadata is propagated from Handler to Handler. This means that the context is preserved and you will be able to access executorId in your Event Handler.

class AuditService
{
    #[EventHandler] 
    public function log(
        TicketWasCreated $event,
        // access metadata with given name
        #[Header("executorId")] string $executorId
    ): void
    {
        // handle event
    }
}

Each Event Handler can be defined as . If multiple Event Handlers are marked for asynchronous processing, each of them is handled in isolation. This ensures that in case of failure, we can safely retry, as only failed Event Handler will be performed again.

Ecotone is using message routing for . This way applications can stay decoupled from each other, as there is no need to share the classes between them.

If you make your Event Handler , Ecotone will ensure your metadata will be serialized and deserialized correctly.

CQRS Introduction
Asynchronous
cross application communication
Asynchronous