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

Was this helpful?

Export as PDF

Last updated 9 months ago

Was this helpful?

In this section we will discuss using Commands, Events and Queries. We will start with Commands and Command Handlers. Even so we will be discussing Commands the functionality which we will tackle, applies to Queries and Events also. Understanding this part will give us understanding of the whole.

Handling Commands

External Command Handlers are Services available in Dependency Container, which are defined to handle Commands. We call them External to differentiate from Aggregate Command Handlers, which will be described in later part of the section. In Ecotone we enable Command Handlers using attributes. By marking given method with #[CommandHandler] we state it should be used as a Command Handler.

In case of Ecotone the class itself is not a Command Handler, it's a method that is considered to be Command Handler. This way we may join multiple Command Handlers under same class without introducing new classes if that's not needed.

Command Handlers are methods where we would typically places our business logic. In above example using #[CommandHandler] we stated that createTicket method will be handling CreateTicketCommand. The first parameter of Command Handler method is indicator of the Command Class we want to handle, so in this case it will be CreateTicketCommand. Now whenever we will send this CreateTicketCommand using Command Bus, it will be delivered to createTicket method.

If you are using autowire functionality, then all your classes are registered using class names. In other case, if your class name is not corresponding to their name in Dependency Container, we may use ClassReference:

Sending Commands

We send Command using Command Bus. After Ecotone is installed all Buses are available out of the box in Dependency Container, this way we may start using them directly after installation. In order to use the Command we first need to define it:

All Messages (Command/Queries/Events) just like Message Handlers (Command/Query/Event Handlers) are simple Plain old PHP Objects, which means they do not extend or implement any framework specific classes. This way we keep our business code clean and easy to understand.

To send an Command we will be using send method on CommandBus. Command will be delivered to corresponding Command Handler.

Sending Commands with Metadata

We may send Command with Metadata (Message Headers) via Command Bus. This way we may provide additional information that should not be part of the Command or details which will be reused across Command Handlers without copy/pasting it to each of the related Command classes.

#[Header] provides information that we would like to fetch metadata which is under executorId. This way Ecotone knows what to pass to the Command Handler.

Injecting Services into Command Handler

If we need additional Services (which are available in Dependency Container) to perform our business logic, we may pass them to the Command Handler using #[Reference] attribute:

In case Service is defined under custom id in DI, we may pass the reference name to the attribute:

Sending Commands via Routing

Routing without Command Classes

There may be cases where creating Command classes is unnecessary boilerplate, in those situations, we may simplify the code and make use scalars, arrays or non-command classes directly.

Ecotone provides flexibility which allows to create Command classes when there are actually needed. In other cases we may use routing functionality together with simple types in order to fulfill our business logic.

Returning Data from Command Handler

It happens that after performing action, we would like to return some value. This may happen for scenarios that require immediate response, for taking an payment may generate redirect URL for the end user.

The returned data will be available as result of the Command Bus.

Take under consideration that returning works for synchronous Command Handlers. in case of asynchronous scenarios this will not be possible.

If we use Command Handler, Ecotone will ensure our metadata will be serialized and deserialized correctly.

In Ecotone we may register Command Handlers under routing instead of a class name. This is especially useful if we will register to tell Ecotone how to deserialize given Command. This way we may simplify higher level code like Controllers or Console Line Commands by avoid transformation logic.

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.

class TicketService
{
    #[CommandHandler] 
    public function createTicket(CreateTicketCommand $command) : void
    {
        // handle create ticket command
    }
}
#[ClassReference("ticketService")]
class TicketService
class readonly CreateTicketCommand
{
    public function __construct(
        public string $priority,
        public string $description
    ){}
}
class TicketController
{
   // Command Bus will be auto registered in Depedency Container.
   public function __construct(private CommandBus $commandBus) {}
   
   public function createTicketAction(Request $request) : Response
   {
      $this->commandBus->send(
         new CreateTicketCommand(
            $request->get("priority"),
            $request->get("description"),            
         )
      );
      
      return new Response();
   }
}
$messagingSystem->getCommandBus()->send(
    new CreateTicketCommand(
        $priority,
        $description,            
     )
);
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]
);
class TicketService
{   
    #[CommandHandler]
    public function closeTicket(
        CloseTicketCommand $command, 
        // by adding Header attribute we state what metadata we want to fetch
        #[Header("executorId")] string $executorId
    ): void
    {          
//        handle closing ticket with executor from metadata
    }   
}
class TicketService
{   
    #[CommandHandler]
    public function closeTicket(
        CloseTicketCommand $command, 
        #[Reference] AuthorizationService $authorizationService
    ): void
    {          
//        handle closing ticket with executor from metadata
    }   
}
#[Reference("authorizationService")] AuthorizationService $authorizationService
class TicketController
{
   public function __construct(private CommandBus $commandBus) {}
   
   public function createTicketAction(Request $request) : Response
   {
      $commandBus->sendWithRouting(
         "createTicket", 
         $request->getContent(),
         "application/json" // we tell what format is used in the request content
      );
      
      return new Response();
   }
}
$messagingSystem->getCommandBus()->sendWithRouting(
   "createTicket", 
   $data,
   "application/json"
);
class TicketService
{   
    // Ecotone will do deserialization for the Command
    #[CommandHandler("createTicket")]
    public function createTicket(CreateTicketCommand $command): void
    {
//        handle creating ticket
    }   
}
class TicketController
{
   private CommandBus $commandBus;

   public function __construct(CommandBus $commandBus)
   {
       $this->commandBus = $commandBus;   
   }
   
   public function closeTicketAction(Request $request) : Response
   {
      $commandBus->sendWithRouting(
         "closeTicket", 
         Uuid::fromString($request->get("ticketId"))
      );
      
      return new Response();
   }
}
$messagingSystem->getCommandBus()->sendWithRouting(
   "closeTicket", 
   Uuid::fromString($ticketId)
);
class TicketService
{   
    #[CommandHandler("closeTicket")]
    public function closeTicket(UuidInterface $ticketId): void
    {
//        handle closing ticket
    }   
}
class PaymentService
{   
    #[CommandHandler]
    public function closeTicket(MakePayment $command): Url
    {
//        handle making payment

        return $paymentUrl;
    }   
}
$redirectUrl = $this->commandBus->send($command);
  1. Modelling
  2. Message Bus and CQRS

CQRS Introduction - Commands

Commands CQRS PHP

PreviousMessage Bus and CQRSNextQuery Handling
  • Handling Commands
  • Sending Commands
  • Sending Commands with Metadata
  • Injecting Services into Command Handler
  • Sending Commands via Routing
  • Routing without Command Classes
  • Returning Data from Command Handler
Asynchronous
Converters
cross application communication