It happens, that some parts of the system or external integrations are unavailable or have long execution time.
Baker do not stand and waiting for the bread to be baked as in the meantime he can prepare new ones, the same is for the users in our systems.
- We can let the user to finish registration and send email asynchronously.
- We can generate the report asynchronously and send the download link after it finish.
- We can run external integrations asynchronously, so the unavailability of external service has no effect on the end user.
In the last blog post about Event Handling, we took a look how to separate flows from each other, using events.
It's important to know what flows we have in the system, as it's the flow that give us clues, if we should make it asynchronous.
User Registration Flow
Continuing on the example from post about Event Handling.
For registering user, Main Flow would be creating an user and saving it to the database.
The Sub Flows on other hand would be:
- Sending an welcome or confirmation email
- Creating audit logs about user registration
- Synchronizing registered user to external Service (External Integration)
Thanks that we clearly defined what flows we have in given scenario, we can decide which of them can should run asynchronously.
Let's define what problems can we solve by choosing to run the flows asynchronously:
1. Calling External Service can take more time.
The service that we integrate with, may having high traffic or some performance issues.
By calling this code asynchronously, we are letting the end user to finish the registration smoothly. Secondly it allows us to release resources like memory, as the request end up right away.
2. External Service may be down.
It can down because there was to much traffic, or a bug that made the API fail or simply the deployment process is putting it on the maintenance mode.
Making the code asynchronous allows us to finish the registration with success, no matter of what happens on the 3rd party side.
3. We may end up inconsistent state.
What, if the email was sent with success, however calling external service failed?
If we rollback database transaction, then we fail the registration, however user will be informed about success registration.
If we commit the database transaction, then the external service will not have information about the user.
Running the code asynchronously allows us to succeed the registration and call the external service later, when it will be up and running. So we always end up in consistent state.
Making The Flow Asynchronous
To make specific flow asynchronous, we mark Handler as Asynchronous.
You can be familiar with marking the Event as asynchronous. This is all or none approach.
Ecotone allows Handlers to be marked as asynchronous. This open the possibility for the developer to decide, what specific handlers should be running asynchronously for given event.
Annotation "Asynchronous" enables specific handler to be executed asynchronously using "asynchronous_messages" Message Channel.
Message Channel allows for abstracting away specific implementation of message delivery mechanism, so your code becomes clean of infrastructure concerns and you can switch it to different one without touching the business code.
Now we need to choose the asynchronous module.
In this particular scenario we will be using RabbitMQ Module.
Follow up installation process:
And let's register our Message Channel for RabbitMQ.
You can easily switch the implementation of Message Channel, or make use of different channels for different handlers.
Let's publish the event the same way as we did for synchronous code.
Now can run the Consumer to handle the event.
Consumer is automatically registered for us and it will consume message from "asynchronous_messages" Message Channel.
bin/console ecotone:run asynchronous_messages -vvv
artisan ecotone:run orders -vvv
Making the code asynchronously is straight forward in Ecotone.
This shows one of Ecotone's tenets. Your code will become clean of infrastructure concerns, so you can focus on business functionality and keep your code clean.
Check demo implementation here.
If you want to known more about asynchronous handling, you may read the documentation.