Skip to content

Commit 7314547

Browse files
dgafkagitbook-bot
authored andcommitted
GITBOOK-830: No subject
1 parent 198a021 commit 7314547

26 files changed

+914
-334
lines changed

.gitbook/assets/async.png

58.8 KB
Loading

.gitbook/assets/cancel_order.png

42.7 KB
Loading

.gitbook/assets/dead-letter.png

43.9 KB
Loading

.gitbook/assets/instant-retries.png

51.8 KB
Loading

.gitbook/assets/order_was_placed.png

84.4 KB
Loading

.gitbook/assets/place-order.png

34.6 KB
Loading
21.1 KB
Loading

.gitbook/assets/process_order.png

49.6 KB
Loading

.gitbook/assets/synchronize (1).png

49.1 KB
Loading

.gitbook/assets/synchronize.png

49.1 KB
Loading

.gitbook/assets/verify_order.png

49.7 KB
Loading

SUMMARY.md

+6-1
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
* [Ecotone Pulse (Service Dashboard)](modelling/recovering-tracing-and-monitoring/ecotone-pulse-service-dashboard.md)
7474
* [Asynchronous Handling and Scheduling](modelling/asynchronous-handling/README.md)
7575
* [Delaying Messages](modelling/asynchronous-handling/delaying-messages.md)
76+
* [Time to Live](modelling/asynchronous-handling/time-to-live.md)
7677
* [Message Priority](modelling/asynchronous-handling/message-priority.md)
7778
* [Scheduling](modelling/asynchronous-handling/scheduling.md)
7879
* [Distributed Bus and MicroServices](modelling/microservices-php/README.md)
@@ -88,6 +89,10 @@
8889
## Messaging and Ecotone In Depth <a href="#messaging" id="messaging"></a>
8990

9091
* [Overview](messaging/overview.md)
92+
* [Workflows](messaging/workflows/README.md)
93+
* [The Basics - Stateless Workflows](messaging/workflows/the-basics-stateless-workflows.md)
94+
* [Stateful Workflows - Saga](messaging/workflows/stateful-workflows-saga.md)
95+
* [Handling Failures](messaging/workflows/handling-failures.md)
9196
* [Multi-Tenancy Support](messaging/multi-tenancy-support/README.md)
9297
* [Getting Started](messaging/multi-tenancy-support/getting-started/README.md)
9398
* [Any Framework Configuration](messaging/multi-tenancy-support/getting-started/any-framework-configuration.md)
@@ -105,7 +110,7 @@
105110
* [Message](messaging/messaging-concepts/message.md)
106111
* [Message Channel](messaging/messaging-concepts/message-channel.md)
107112
* [Message Endpoints/Handlers](messaging/messaging-concepts/message-endpoint/README.md)
108-
* [Service Activator](messaging/messaging-concepts/message-endpoint/service-activator.md)
113+
* [Internal Message Handler](messaging/messaging-concepts/message-endpoint/service-activator.md)
109114
* [Message Router](messaging/messaging-concepts/message-endpoint/message-routing.md)
110115
* [Splitter](messaging/messaging-concepts/message-endpoint/splitter.md)
111116
* [Consumer](messaging/messaging-concepts/consumer.md)

messaging/messaging-concepts/message-endpoint/message-routing.md

+19-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ Router must return name of the channel, where the message should be routed too.
1111
```php
1212
class OrderRouter
1313
{
14-
#[Router("order")]
14+
#[Router("make.order")]
1515
public function orderSpecificType(string $orderType) : string
1616
{
1717
return $orderType === 'coffee' ? "orderInCoffeeShop" : "orderInGeneralShop";
@@ -25,6 +25,24 @@ class OrderRouter
2525
* `inputChannnelName` - Required option, defines to which channel endpoint should be connected
2626
* `isResolutionRequired` - If true, will throw exception if there was no channel name returned
2727

28+
## Routing to multiple Message Channels
29+
30+
```php
31+
class OrderRouter
32+
{
33+
#[Router("order.bought")]
34+
public function distribute(string $order) : array
35+
{
36+
// list of Channel names to distribute Message too
37+
return [
38+
'audit.store',
39+
'notification.send',
40+
'order.close'
41+
];
42+
}
43+
}
44+
```
45+
2846
## What can be Router used for? &#x20;
2947

3048
Router is powerful concept that is backing up Query/Command and Event Bus implementations. \

messaging/messaging-concepts/message-endpoint/service-activator.md

+3-4
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,16 @@
22
description: Service Activator PHP
33
---
44

5-
# Service Activator
5+
# Internal Message Handler
66

7-
The Service Activator connecting any service available in Depedency Container to an input channel so that it may play the role of a [Endpoint](./). If the service produces output, it may also be connected to an output channel. \
8-
Alternatively, an output producing service may be located at the end of a processing pipeline or message flow in which case, the inbound Message's "replyChannel" header can be used. This is the default behavior if no output channel is defined.
7+
The Internal Handler connecting any service available in Depedency Container to an input channel so that it may play the role of a [Endpoint](./). If the service produces output, it may also be connected to an output channel.&#x20;
98

109
### How to register
1110

1211
```php
1312
class Shop
1413
{
15-
#[ServiceActivator("buyProduct")]
14+
#[InternalHandler("buyProduct")]
1615
public function buyProduct(int $productId) : void
1716
{
1817
echo "Product with id {$productId} was bought";

messaging/workflows/README.md

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Workflows
2+
+162
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
---
2+
description: Handling Failures and Exceptions in Sagas and Process Managers
3+
---
4+
5+
# Handling Failures
6+
7+
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. \
8+
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. &#x20;
9+
10+
Ecotone provides different ways to deal with the problems and depending on the context, we may want to choose different solution.
11+
12+
## Uncovered Business Scenarios
13+
14+
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.&#x20;
15+
16+
{% hint style="success" %}
17+
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.
18+
{% endhint %}
19+
20+
When double payment happens, we could for example trigger an automatic refund, or store this information in order to provide manual refund. \
21+
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.&#x20;
22+
23+
{% hint style="info" %}
24+
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.
25+
{% endhint %}
26+
27+
## Let the Exception propagate
28+
29+
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.&#x20;
30+
31+
```php
32+
final readonly class OrderController
33+
{
34+
public function __construct(private CommandBus $commandBus) {}
35+
36+
public function placeOrder(Request $request): Response
37+
{
38+
$orderId = $request->get('orderId');
39+
$customerId = $request->get('customerId');
40+
$items = $request->get('items');
41+
42+
try {
43+
$this->commandBus->send(PlaceOrder::create($orderId, $customerId, $items));
44+
}catch (InvalidOrder $exception) {
45+
// Customize Response based on the Exception details
46+
return new Response($exception->getMessage(), 422);
47+
}
48+
49+
return new Response('Order placed');
50+
}
51+
}
52+
```
53+
54+
and our Command Handler
55+
56+
```php
57+
class ProcessOrder
58+
{
59+
#[CommandHandler(
60+
'verify.order',
61+
outputChannelName: 'place.order'
62+
)]
63+
public function verify(PlaceOrder $command): PlaceOrder
64+
{
65+
// verify the order
66+
67+
if ($orderInvalid) {
68+
throw new InvalidOrder($orderInvalidDetails);
69+
}
70+
}
71+
}
72+
```
73+
74+
In above example Workflow will stop, as no Message will go to the next step **"place.order".** \
75+
In the Controller then we can simply state, what was the problem.
76+
77+
{% hint style="info" %}
78+
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.
79+
{% endhint %}
80+
81+
## Handling failures in the Message Handler
82+
83+
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.&#x20;
84+
85+
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.
86+
87+
```php
88+
class ProcessOrder
89+
{
90+
#[CommandHandler(
91+
'verify.order',
92+
outputChannelName: 'place.order'
93+
)]
94+
public function verify(PlaceOrder $command): ?PlaceOrder
95+
{
96+
// verify the order
97+
98+
if ($orderInvalid) {
99+
// Store Order for reviewal process
100+
$this->orderToReviewRepository->save($order);
101+
102+
// This will stop the flow from moving forward to outputChannel
103+
return null;
104+
}
105+
}
106+
}
107+
```
108+
109+
So now when the Order is considered invalid we store for internal reviewal process. \
110+
We also return null from the method now. This will stop the Workflow from moving forward to the **outputChannel**.
111+
112+
{% hint style="success" %}
113+
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.
114+
{% endhint %}
115+
116+
## Recoverable Synchronous Errors
117+
118+
One of the problems that we need to accept is that [Network is not reliable](https://en.wikipedia.org/wiki/Fallacies\_of\_distributed\_computing). 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.
119+
120+
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.&#x20;
121+
122+
To solve this we can make use of [Instant Retries](../../modelling/recovering-tracing-and-monitoring/resiliency/retries.md#instant-retries) on the Command Bus.&#x20;
123+
124+
<figure><img src="../../.gitbook/assets/instant-retries.png" alt=""><figcaption><p>When failure happens, Command Bus is triggered again</p></figcaption></figure>
125+
126+
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.
127+
128+
## Recoverable Asynchronous Errors
129+
130+
So when Failure happens during Asynchronous handling of given Message, we've still can kick off [instant retries](../../modelling/recovering-tracing-and-monitoring/resiliency/retries.md#asynchronous-instant-retries) to try to recover immediately. \
131+
However we get a bit more options here now as we are no more in HTTP Context, we can now delay the Retry too. \
132+
\
133+
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. \
134+
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 [Delayed Retries](../../modelling/recovering-tracing-and-monitoring/resiliency/retries.md#delayed-retries).
135+
136+
<figure><img src="../../.gitbook/assets/synchronize (1).png" alt=""><figcaption><p>Retrying the Command Handler with delay</p></figcaption></figure>
137+
138+
## Unrecoverable Asynchronous Errors
139+
140+
In case External Service is down for longer period of time, we may actually not be able to self-heal. \
141+
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.
142+
143+
For this situation, Ecotone provides [Dead Letter Storage](../../modelling/recovering-tracing-and-monitoring/resiliency/error-channel-and-dead-letter.md), which allows us to store the the Message and replay after the problem is fixed.&#x20;
144+
145+
<figure><img src="../../.gitbook/assets/dead-letter.png" alt=""><figcaption><p>Store in Dead Letter when urecoverable and replay when needed</p></figcaption></figure>
146+
147+
{% hint style="success" %}
148+
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.\
149+
And if unrecoverable error happens, we get ability to easily replay the Message to resume the Workflow, after fix is applied.
150+
{% endhint %}
151+
152+
## Customizing Global Error Handling
153+
154+
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. \
155+
\
156+
You can read more about in [related documentation](../../modelling/recovering-tracing-and-monitoring/resiliency/error-channel-and-dead-letter.md#error-channel).
157+
158+
## Customizing Error Handling on Message Handler Level
159+
160+
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.
161+
162+
You can read more about in [related documentation](../../modelling/extending-messaging-middlewares/interceptors.md).

0 commit comments

Comments
 (0)