Starting with Microservices in PHP
This is follow up on “How To Integrate Microservices” post.
Feel encouraged to read the previous article first.
In this article, we will take theory and apply it in practice using PHP.
We will use Ecotone Framework and RabbitMQ, to integrate two Services together.
Implementing Messaging in PHP
Before we start, we need to enable RabbitMQ Module for used framework (Symfony/Laravel/Lite).
We will build two Services.
Service which will be consuming messages we will call “order_service” and Service that will publish them, “my_service”.
The naming is important and we need to set up Service Name in configuration.
In the “Order Service” we will enable Consumer to consumes messages coming from other Microservices.
In “My Service” we will enable Distributed Bus, that will allow us to send events and commands to other Microservices.
Sending Command to Order Service
Let’s start by defining Command Handler in Order Service.
The Command Handler is available under “placeOrder” routing key.
We may now send the command from My Service using Distributed Bus to place the order:
- Service Name that Command should be sent to
- Routing to Command Handler inside Order Service
- Data to be sent (payload of Command)
- And optional content type of the data
After executing this code, Command Message will be delivered to Order Service.
Order Service may consume it now by running:
(bin/console|artisan) ecotone:run order_service -vvv
Publishing Event
Suppose that we want to cancel all orders of the user in case his account was banned.
My Service will publish event about user being banned and Order Service will subscribe to this event.
The Event Handler will now subscribe to Event published with routing key “user.was_banned”.
- Name of the Event (routing key)
- Data to be sent (payload of Event)
- Optional content type of the payload
After executing this code, Event Message will be delivered to Order Service.
Order Service may consume it now by running:
(bin/console|artisan) ecotone:run order_service -vvv
Going into more details…
Ways of Routing
#[Distributed]
#[CommandHandler("placeOrder")]
public function placeOrder(PlaceOrderCommand $command): void
We are using “placeOrder” routing name to route the Command to specific Handler.
$this->distributedBus->sendCommand("order_service","placeOrder",...)
You could meet with solutions where routing is based on the Class Name or require sharing actual class implementations between Services.
Sharing PHP Classes between Services makes them become public API.
We can’t anymore simply change the class name, as it will break other Services.
The class becomes hard to change, as modifications now involve other parties.
The second option would be to agree on non PHP, Public API.
We could take JSON Schema instead of PHP Class and do the routing based on custom name like “placeOrder”, just as we did in above example.
Ecotone is flexible and adjust to your needs.
If coupling Services by sharing classes is fine in your context, then you may do it.
If you want to decouple Services you may use custom names.
Using Classes with Custom Routing
Even so, that we have used Custom Routing we are still expecting PlaceOrderCommand class.
#[Distributed]
#[CommandHandler("placeOrder")]
public function placeOrder(PlaceOrderCommand $command): void
Based on the routing Ecotone knows which Handlers it should execute and based on the method declaration which class it should deserialize to.
All you need to do it to register Media Type Converter so Ecotone knows how to deserialize given Content Type to PHP Class.
You could also type hint for string to get JSON or type hint for array, if you would like to work with simple types.
You could also have no arguments at all and execute the Handler based on routing name only.
Sending/Publishing Classes
If you have your Media Type Converter registered on the Publisher side, then you may send Command and Event classes.
Ecotone will take care of serialization before sending it to RabbitMQ.
Passing Metadata
There will be cases, when you will want to enrich the Message with some Metadata.
You can for example publish Event and add Person Id of currently logged user.
If we will put it in the payload it can easily blur the event, especially that there may be more meta data to store.
Message is like letter and letter contains Headers (Metadata).
Ecotone comes with solution that allows us to add and send Metadata with Commands and Events in straight forward way.
Suppose we have Audit Service, that stores the information about who banned the user.
or, if you want to be more specific, you can pass specific Header directly
Delivery Guarantees
There may be situations, when you will want to send more than one Message at time.
If for any reason before sending or during sending second Command our code will fail,
then we may end up in situation, where only first Command went out.
This would would create inconsistency between Services.
If you’re using local Command/Event Handlers, Ecotone on default wraps those handlers in RabbitMQ Transactions.
This guarantees, that you all messages will be sent together at the end of the successful flow.
Handling Great Amount Of Messages
If we will sent a lot of messages, then we will want to target Message only to the Services that can handle specific Message to avoid unnecessary load on the system.
Ecotone sends events only to the Services that are subscribing to it.
Commands are sends only to specific targeted Services.
Handling Error Messages
There may be situations, when Event or Command Handler will fail.
In that case RabbitMQ will try to redeliver the message, till the moment it will be successful. During that time other messages will be blocked.
Ecotone comes with solution, that allows you to set up retries with delays.
So in case message will fail, we can retry it after X minutes, during that time other messages will be unblocked.
After defined amount of retries, you may store Error Message in Dead Letter (database) for further investigation.
Summary
Ecotone provides rich support for building Microservices.
As it’s built around Messaging Concepts from the ground, all the tools are just natural extensions.
Messaging provides stable ground that help jump over a lot of complications that distributed architecture brings into existence.
The aim of Ecotone is to provide developers with solid tools that are powerful, yet easy to use.
If you want to see demo implementation of Microservices Integration in Ecotone Lite, you can check it here.
To follow up on Ecotone Framework go here.