Lesson 1: Messaging Concepts
PHP Messages
Last updated
PHP Messages
Last updated
Not having code for Lesson 1?
git checkout lesson-1
Ecotone from the ground is built around messaging to provide a simple model that allows to connects components, modules or even different Applications together, in seamless and easy way. To achieve that fundamental messaging blocks are implemented using Enterprise Integration PatternsOn top of what we get support for higher level patterns like CQRS, Events, DDD - which help us build systems that make the business logic explicit and maintainable, even in the long term. In this first lesson, we will learn fundamental blocks in messaging architecture and we will start building back-end for Shopping System using CQRS. Before we will dive into implementation, let's briefly understand main concepts behind Ecotone.
A Message is a data record containing of Payload and Message Headers (Metadata). The Payload can be of any PHP type (scalar, object, compound), and the Headers hold commonly required information such as ID, timestamp and framework specific information. Developers can also store any arbitrary key-value pairs in the headers, to pass additional meta information.
Message channel abstracts communication between components. It does allow for sending and receiving messages. This decouples components from knowledge about the transport layer, as it's encapsulated within the Message Channel.
Message Endpoints are consumers and producers of messages. Consumer are not necessary asynchronous, as you may build synchronous flow, compound of multiple endpoints.
If you are familiar with Symfony Messager/Simplebus, for now you can think of Endpoint as a Message Handler, that can be connected to asynchronous or synchronous transport.
The Messaging Gateway encapsulates messaging-specific code (The code required to send or receive a Message) and separates it from the rest of the application code.
It take your domain specific objects an convert them into a Message that is send via Message channel.
To not have dependency on the Messaging Framework Ecotone
provides the Gateway as interface and generates proxy class for it.
Command/Query/Event buses are implemented using Messaging Gateway.
You will not have to implement Messages, Message Channels or Message Endpoints directly, as those are lower level concepts. Instead you will be able to focus on your specific domain logic with an implementation based on plain PHP objects. By providing declarative configuration we will be able to connect domain-specific code to the messaging system.
Great, now when we know fundamental blocks of Ecotone
and Messaging Architecture, we can start implementing our Shopping System!
If you did not understand something, do not worry, we will see how does it apply in practice in next step.
Do you remember this command from Setup part?
If yes and this command does return above output, then we are ready to go.
this method will be run, whenever we executeecotone:quickstart
.
This class is auto-registered using auto-wire system, both Symfony and Laravel provides this great feature. For Lite
clean and easy to use PHP-DI
is taken.
Thanks to that, we will avoid writing configuration files for service registrations during this tutorial.
And we will be able to fully focus on what can Ecotone
provides to us.
We will start by creating Command Handler. Command Handler is place where we will put our business logic. Let's create namespace App\Domain\Product and inside RegisterProductCommand, command for registering new product:
Describing types, will help us in later lessons with automatic conversion. Just remember right now, that it's worth to keep the types defined.
Let's register a Command Handler now by creating class App\Domain\Product\ProductService
First thing worth noticing is #[CommandHandler].
This attribute marks our register
method in ProductService as an Endpoint, from that moment it can be found by Ecotone.
Ecotone will read method declaration and base on the first parameter type hint will know that this CommandHandler is responsible for handling RegisterProductCommand.
Ecotone make use Attributes to provide declarative configuration. In most of the scenarios we will be stating "what" we want to achieve with Attributes, and Ecotone will take care of "how". This way our application logic will stay decoupled from the technical concerns.
#[ClassReference]
is a special Attribute it informs Ecotone
how this service is registered in Depedency Container
. As a default it takes the class name, which is compatible with auto-wiring system.
If ProductService would be registered in Dependency Container as "productService", we would use the Attribute this way:
We also need the possibility to query ProductService for registered products and this is the role of Query Handlers. Let's starts with GetProductPriceQuery class. This query will tell us what is the price of specific product.
We also need Handler for this query. Let's add Query Handler
to the ProductService
Some CQRS frameworks expects Handlers be defined as a class, not method. This is somehow limiting and producing a lot of boilerplate. Ecotone
does allow for full flexibility, if you want to have only one handler per class, so be it, otherwise just annotate next methods.
It's time to call our Endpoints. You may remember that endpoints need to be connected using Message Channels and we did not do anything like this yet. Thankfully Ecotone does create synchronous channels for us, therefore we don't need to bother about it.
Synchronous channels are created automatically for our Message Handlers. We will learn easily can they be replaced with asynchronous channels in next lessons.
We need to create Message
and send it to correct Message Channel
.
In order to send Message we will use Messaging Gateway.
Message Gateways are responsible for creating Message
from given parameters and send them to the correct channel
.
Special types of Gateways are Command and Query Buses:
- For sending Commands we will use Command Bus.
- For sending Queries we will use Query Bus.
Let's inject and call Query and Command bus into EcotoneQuickstart class.
Gateways are auto registered in Dependency Container and available for auto-wire.
Ecotone
comes with few Gateways out of the box like Command and Query buses.
We are sending command RegisterProductCommand to the CommandHandler we registered before.
Same as above, but in that case we are sending query GetProductPriceQuery to the QueryHandler
As you can see we have not defined any Message Channels, Messages or Gateways, yet they all being used in this scenario. This is can happen because Ecotone is using high level abstractions so our daily development is focused on the business side of the code, yet under the hood is using powerful Messaging capabilities.
If you run our testing command now, you should see the result.
We want to notify, when new product is registered in the system.
In order to do it, we will make use of Event Bus Gateway which can publish events.
Let's start by creating ProductWasRegisteredEvent.
As you can see Ecotone
does not really care what class Command/Query/Event is. It does not require to implement any interfaces neither prefix or suffix the class name.
In fact commands, queries and events can be of any type and we will see it in next Lessons.
In the tutorial however we use Command/Query/Event suffixes to clarify the distinction.
Let's inject EventBus into our CommandHandler in order to publish ProductWasRegisteredEvent after product was registered.
Ecotone
does control method invocation for endpoints, if you have type hinted for specific class, framework will look in Dependency Container for specific service in order to inject it automatically.
In this scenario it injects for us Event Bus. If you want to know more, check chapter about Method Invocation.
Now, when our event is published, whenever new product is registered, we want to subscribe to that Event and send notification. Let's create new class and annotate method with EventHandler.
EventHandler tells Ecotone to handle specific event based on declaration type hint, just like with CommandHandler.
Commands are targeting single Handler, Events
on other hand can have multiple Handlers subscribing to it.
If you run our testing command now, you should see the result.
Great, we have just finished Lesson 1. In this lesson we have learned basic of Messaging and CQRS. That was the longest lesson, as it had to introduce new concepts. Incoming lessons will be much shorter :)
We are ready for Lesson 2!