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
  • Uncovered Business Scenarios
  • Let the Exception propagate
  • Handling failures in the Message Handler
  • Recoverable Synchronous Errors
  • Recoverable Asynchronous Errors
  • Unrecoverable Asynchronous Errors
  • Customizing Global Error Handling
  • Customizing Error Handling on Message Handler Level

Was this helpful?

Export as PDF
  1. Modelling
  2. Business Workflows

Handling Failures

Handling Failures and Exceptions in Sagas and Process Managers

We may happen to face Errors on different stage of our Workflows. This may happen due to coding error and due to Network and Integration problems as well. Whatever the problem is, we want to be able to recover from that, or to be able to execute compensation action that will handle this failure.

Ecotone provides different ways to deal with the problems and depending on the context, we may want to choose different solution.

Uncovered Business Scenarios

It happens that uncovered business scenario are treated as failures. This may happen for example when Customer have made two payments for the same Order and our system basically throws an Exception.

Uncovered Business Scenarios are not failures. It's just gap in the knowledge about what we need to do. In those situations we just need confirm with our Product people how to deal with those situations and implement given behaviour.

When double payment happens, we could for example trigger an automatic refund, or store this information in order to provide manual refund. Even things which looks like external problems can actually be uncovered scenarios. For example failing on taking subscription payment, may actually reveal that we need to reattempt it after some time.

If our Architecture let us, it's good to treat exceptions as something exceptional, not as something to steer the Workflow. This way we can make it explicit in the code, what different scenarios we expect to happen.

Let the Exception propagate

The most basic solution we could apply is to let the Exception propagate and catch it in the place where we can make meaning out of it, for example Controller. This can make sense in Synchronous Scenarios, where we return to the Customer details about the problems based on Exception.

final readonly class OrderController
{
    public function __construct(private CommandBus $commandBus) {}
    
    public function placeOrder(Request $request): Response
    {
        $orderId = $request->get('orderId');
        $customerId = $request->get('customerId');
        $items = $request->get('items');

        try {
            $this->commandBus->send(PlaceOrder::create($orderId, $customerId, $items));   
        }catch (InvalidOrder $exception) {
            // Customize Response based on the Exception details
            return new Response($exception->getMessage(), 422);
        }

        return new Response('Order placed');
    }
}

and our Command Handler

class ProcessOrder
{
    #[CommandHandler(
        'verify.order',
        outputChannelName: 'place.order'
    )]
    public function verify(PlaceOrder $command): PlaceOrder
    {
        // verify the order
        
        if ($orderInvalid) {
            throw new InvalidOrder($orderInvalidDetails);
        }
    }
}

In above example Workflow will stop, as no Message will go to the next step "place.order". In the Controller then we can simply state, what was the problem.

Depending on the application architecture, we may actually validate the Order before it even enters the Workflow. This may happen for example with Symfony Forms, then we can consider Order to be valid when it enters the Workflow.

Handling failures in the Message Handler

We may find out, that it happens that when we decline Order due to validation errors, some Customer are cancelling the Order completely, as most likely they got irritated and decide to go somewhere else to buy the products. This as a result make the Business lose they money, which of course we want to avoid in the first place.

To solve that, we could accept all the Orders just as they are, and when given Order is considered to be invalid we still accept it. For example, if we lack of given Product, we can contact Customer to provide substitute and then if Customer agrees, change the Order and consider it valid.

class ProcessOrder
{
    #[CommandHandler(
        'verify.order',
        outputChannelName: 'place.order'
    )]
    public function verify(PlaceOrder $command): ?PlaceOrder
    {
        // verify the order
        
        if ($orderInvalid) {
            // Store Order for reviewal process
            $this->orderToReviewRepository->save($order);        
            
            // This will stop the flow from moving forward to outputChannel
            return null;
        }
    }
}

So now when the Order is considered invalid we store for internal reviewal process. We also return null from the method now. This will stop the Workflow from moving forward to the outputChannel.

This kind of explicit way of solving problems allows us to switch the code from synchronous to asynchronous easily. As now even if we would do Validation asynchronously Customer experience would stay the same.

Recoverable Synchronous Errors

In our case, if we would want to store our Order synchronously, or send an Message to the Broker, it may happen that we will face an error. This of course means that Customer will not completely his Order, which is far from ideal.

When failure happens Command Bus will automatically be triggered once more (depending on the configuration). This way we can self-heal application from transient error like network related problems.

Recoverable Asynchronous Errors

Unrecoverable Asynchronous Errors

In case External Service is down for longer period of time, we may actually not be able to self-heal. In case of coding errors (bugs) we may also end up in situation where not matter how many retries we would do, we still won't recover.

Instant, Delayed Retries and Dead Letter creates a solution where Messages goes in circle till the moment they are handled or deleted. This ensure no data is lost along the way, and we more often than not do not need to deal with failures as our Application can self-heal from those problems. And if unrecoverable error happens, we get ability to easily replay the Message to resume the Workflow, after fix is applied.

Customizing Global Error Handling

Customizing Error Handling on Message Handler Level

We could also catch exception using Middleware like behaviour and provide custom logic that we would like to trigger. This can be easily built using Ecotone's Interceptors.

PreviousStateful Workflows - SagaNextTesting Support

Last updated 1 year ago

Was this helpful?

One of the problems that we need to accept is that . This means that that when we will want to store something in our Database, send an Message to the Message Broker or call External Service, network may simply fail and there are various reasons why it may happen.

To solve this we can make use of on the Command Bus.

So when Failure happens during Asynchronous handling of given Message, we've still can kick off to try to recover immediately. However we get a bit more options here now as we are no more in HTTP Context, we can now delay the Retry too. Delaying the retries may be especially useful when dealing with External Services, as it may happen that they will be down for some period of time, which instant retries will not solve. In those situations we still want to Application to seal-heal, so we don't need to bother with those situations and for this we can use .

For this situation, Ecotone provides , which allows us to store the the Message and replay after the problem is fixed.

If we already have some solution to handle Asynchronous Errors in our Application, we can take over the process using Error Channel. Error Chanel is a Message Channel where all unhandled Asynchronous Errors go. You can read more about in .

You can read more about in .

Network is not reliable
Dead Letter Storage
related documentation
related documentation
Instant Retries
instant retries
Delayed Retries
When failure happens, Command Bus is triggered again
Retrying the Command Handler with delay
Store in Dead Letter when urecoverable and replay when needed