Loosely coupled Microservices in PHP
In this article we will get deep into the subject of integrating Microservices in PHP and keeping them loosely coupled.
We will focus on integration via
messaging, as integration of microservices over HTTP has a lot of drawbacks and requires separate article to tackle.
Besides of the details and theory how to achieve that, we will learn how to actually make it happen in PHP with Ecotone Framework (works with
Sharing message classes
So one of mostly used solution in PHP is to share classes in separate package or just copy them in each of the service.
This gives a hint of what we are dealing with and helps with deserialization.
However the more classes we share the more changes we need to do.
Then it's go down the spiral, where it's not about releasing a single service when we do a change, it's about releasing several services, because we need to keep them in sync.
Route messages, not classes
Routing message by class works by keeping name of the class in header's of the message.
This header is later used in order to know, which class we should deserialize to.
When we expect other service to understand our class, it creates situation, where the other service need to have a class with exactly same name.
We can easily imagine situation, where we forget to change the name in one of the services or release another service with delay, which would create
failure due to mismatch in class names.
Actually if we handle message within single service it still may create issues.
If message is in the queue and we will
change class name or namespace, there will be no way to deserialize it.
So how should be messages routed?
In most of the cases we already use this solution, however we have not promoted it to the application level, the solution is
Routing keys can be seen as application level names for the events and commands.
They create contract and shared naming between services so we can understand the intention behind the message.
When message is routed by routing key, we can map given routing key to class name.
In result refactoring class name in one service will not require changes in another one.
With Ecotone you actually do not need to build any kind of
routing-key -> class-namemap.
Message is routed to given handler based
routing keyand then
payloadof the message is deserialized
based on first method parameter.
Being explicit about your public API
Whenever given event or command is exposed to other services, it becomes part of your public API.
If you publish every single event to outside world, it becomes a problem when you want to change it, as it's not easily clear will it affect external services.
Contracts between services are part of application level, they should be explicit and not hidden in Message Broker implementation.
Ecotone provides `DistributedBus` for services publishing messages.
In this way we can be
explicit on the application level if event is meant to be consumed by other services and we need to be
aware, if we want to change it.
On the side of consumer, we make it explicit that message we are subscribing to, is coming from external service.
In case we don't add
Event Handler this event will be treated as local (private) one.
The same works for
Distributed Command Handler, none of the services will be able to execute our
Command Handler, if it's not explicitly
This make it clear for new joiners and in the long term of the project, which Handlers are executed by
external services and which are
For pushing this forward, you may add Consumer Driven Contract with Pact.
This will allow you to know what fields from your message are used by other services, so you can easily modify ones that are not.
Public and Private events
Even, if we will explicitly publish distributed events and limit their amount, we still may be blocked by other services to make internal changes.
If we will modify
payload or we will want to drop or replace the event, then we may end up in affecting other parties.
That is why it's worth to distinguish between
DistributedBus we already made a step forward by being explicit about what events we are sending outside, however there is one more benefit to this.
Before publishing event we can now transform it into a different one. This event will become our public API and the other one will stay private within publishing service.
This gives us back a lot of power. As long as we can deliver this
public event, we can modify our internal service the way we want without affecting other parties.
We can enrich public events with extra details, so the
need for consumption of other events or calling our HTTP Api will decrease.
Know what you send
Messages, however there is semantic difference to each of them.
targets specific handler, which means there is only a single endpoint that handles this message.
Event on other hand, does not target anything. It's being published and parties that are interested may
subscribe. This means that there may be 20 subscribers, but also there may be none.
So how to deal with this in distributed environment.
send command, we should target specific Service and given Handler in this Service. This identify targeted service and make sure that no other service will handle this command.
Services names, just like routing keys are part of the application level. We need to know, who and why we are communicating with.
The first parameter it targeted service name, where command will go and second is the routing key for the handler.
In case there will be
no given handlerin targeted service or it
distributed, message will fail and may land in
DLQdepending on the configuration.
In case of events, each service explicitly states what event it want to subscribe too.
Subscribing and receiving only events that given services is interested need, help in keeping your message flow performant.
Message payload can be anything
Current frameworks in PHP have built mental modal of
PHP Message implementation being equal to
payload being equal to
That was never a case with messaging principles, payload of the message can be anything a
class or even an
simple text string.
Neither message, neither payload of the message must be a Class.
Message have payload which can be deserialized to
Class, but at the same time we should be able to deserializable it to
arrayor even keep it as
json, if we wishes to handle it this way.
What if you just need the intention? For example it may be enough to know that invoice was generated, to send an sms to the customer. You don't need to know the details at all. Looking at the message this way create much more loosely coupled interface.
Let's take as an example publishing events that
Order Was Placed.
We may not really want to create classes, as the only thing we need it to retrieve
userId to send an email.
Other scenario can be that we want to just store message payload for later auditing purposes directly in
In Ecotone, whatever content-type (xml, json, avro, protobuf etc) message has, as long as you've registered Converter for it, you will be able to deserialize it to class if you want to.
In general we may even use empty method declaration, if there is a need.
This gives power back to developers. We create classes when we feel the need, not because we need to.
What, if we want to provide details like who was the executor of given action, or a time where when it happened, or maybe some infrastructure information like from which domain given order was placed?
This could be potentially added to the
payload during step for enriching
public event. However this may blur the image of the event, and in general may not related with it at all.
Suppose we have multiple sites where customers make order and we want to enrich event's metadata with domain where order was placed.
Ecotone provides easy way to work with metadata, it will take care of passing it via Message Broker and allow you to make use of it on other end.
With time and with maturity of the project we want more solid and long term solutions for integrations. Those solutions were used in other languages considered more mature and now
Ecotone brings those into PHP, so we all can benefit and build on solid foundations.
Loosely coupling is art. It reveals things that are hidden, by making them explicit.
The things that before could look hard, becomes smooth and just feels good to do.
And this is the aim, good experience for us and shared understanding, so we can change services with smile on our faces.
If you want ask question or have a discussion about
Messaging in general, join official Community Channel. Let's build community around Messaging in PHP together and push our language even further :)