Problems
- ECE Tools currently has a tight coupling between commands and the individual steps necessary for them.
- There is no way for for third parties to inject their own steps in the middle of different commands (shell scripts may be run before or after a command but not around individual steps).
Goals
- Provide optimal flexibility for others to add their own steps inline with individual commands.
- Do not reinvent the wheel: wherever practical rely on common programming practices, APIs, and libraries.
Proposal
Update ECE Tools to be an event driven system. Externally, nothing would change; the same commands would be used to build Magento and deploy it on servers. Internally, the way these commands coordinate with their steps would be overhauled. Additionally, should users need it, there would be a way to add additional steps to the deployment flow.
Commands
Rather than having their steps injected to them, commands become pure event dispatchers. Their responsibility would be to dispatch their respective events in order and do basic error handling. More specific steps should be performed by individual event listeners.
Events
Events are referenced by their name. An event name is made up of multiple parts separated by .
. An event name should start with a namespace. Some standard namespaces that will come with ECE Tools will be command
and task
. Other namespaces may be created as need be. After the namespace will be any additional sub-namespaces followed by the object's name. Lastly, the event name will end with a specific event for that object. Default specific events are starting
, run
, finished
, and error
. Other event names may be added as necessary. For example there may be specific events for validators such as passed
and failed
. Some example event names would be:
command.build.generate.starting
command.deploy.error
task.compile-di.finished
task.vendor-name.some-custom-task.run
Events for a given command or task will be dispatched in order (starting
, run
, finished
). Other events may dispatched in between these as required. Unless an error occurs, all three events should always be dispatched. If an error does occur, the error
event must be dispatched.
Configuring Event Listeners
Event listeners are PHP callables. Custom event listeners may be added in .magento.env.yaml
. For example:
system:
events:
"command.build.generate.starting": # Run these listeners before the main steps of `build:generate`
- \Vendor\Some\Invokable\ClassName # An invokable class
- [\Vendor\Some\Other\ClassName, someMethodName] # A method of a given class
ECE Tools will use its container to create an instance of whatever class is configured and call methods non-statically.
Events handlers may include a priority as well. The default priority is 0
. Higher priorities will be called earlier in the event dispatch sequence.
system:
events:
"task.compile-di.ended": # Run these listeners immediately after the `di:compile` task
- handler: \Vendor\Another\Invokable\ClassName
priority: 100 # Make sure this listener is run before others, such as the task's own handler.
Tasks & Task Dispatcher
Tasks are specialized event subscribers. A task must implement a run()
method that performs the primary duties of the task. A task may also implement before()
, after()
, and error()
methods. These will listen for starting
, finished
, and error
events respectively. The before()
method should be use for any precondition work necessary for the task, such as verifying system state. The after()
method can be used for any cleanup steps that should be run after the task. Finally, error()
is the error handler for the task itself.
Tasks do not listen for command events directly. Instead, there is a task dispatcher that sits between the command events and individual tasks. Just like with generic events, tasks are configured via .magento.env.yaml
:
system:
tasks:
"command.build.transfer.finished" # Run these task at the end of the `build:transfer` command
- \Vendor\Tasks\MyFirstTask
- \Vendor\Tasks\MySecondTask
"command.deploy.starting" # Run these tasks at the begining of the `deploy` command
- \Vendor\Tasks\MyDeployTask
The task dispatcher will register itself as a listener to the command.build.transfer.ended
and command.deploy.starting
events. Whenever it receives one of those events, it will in turn register the tasks for that event as subscribers and then dispatch the events for those tasks in order. The sequence would look like this:
Build Transfer Command
Dispatches 'command.build.transfer.starting'
// ...
Dispatches 'command.build.transfer.run'
// ...
Dispatches 'command.build.transfer.finished'
Task Dispatcher
Dispatches 'task.vendor.my-first-task.starting'
Dispatches 'task.vendor.my-first-task.run'
Dispatches 'task.vendor.my-first-task.finished'
Dispatches 'task.vendor.my-second-task.starting'
Dispatches 'task.vendor.my-second-task.run'
Dispatches 'task.vendor.my-second-task.finished'
// ...
The use of tasks over generic events has several advantages for developers. First, it creates a consistent series of events for other listeners to be able to use. This means that third parties can easily listen for a particular task's starting
or finished
event and perform some actions before or after that task reliably. It also takes the onus of declaring and dispatching those events away from the client code. Instead the task dispatcher is responsible for the events and the task code can focus on simply performing whatever actions are necessary.
There are a couple of trade offs to generic event listeners however. Tasks always have the default priority of 0
, so they are always run in the order that they are registered. On the other hand, tasks provide an easy short hand to setup a standard set of events that other listeners may be able to leverage.
Comparison to Scenario Based Deployments
Both scenarios based deployments and event driven APIs attempt to solve similar limitations of ECE Tools, but with different mechanisms and drawbacks. Event APIs are a common design pattern, with several different third party libraries implementing them already. Their behavior is predictable and well understood. The chosen syntax for configuring events is intentionally light weight, easy to understand, and fits into the existing Magento Cloud configuration scheme.
On the other hand, scenarios based deployments offers the ability to remove or re-order steps of the deployment process. The event API as laid out here would not offer that functionality. While it is technically feasible to de-register an event listener, exposing it though the API here would add additional complexity to the syntax. Additionally, removing deployment steps also introduces greater risk that some key process may be skipped or altered in such a way to break the deployment. For these reasons, it was decided that adding this to the event driven API was not worth the difficulty.