Plugins are the fundamental building blocks for creating reusable and composable behaviors in Malla. They allow you to extend Malla services with custom functionality by implementing callbacks that participate in compile-time callback chains.
In Malla, everything is a plugin:
- Service modules themselves are plugins (the top-level plugin in a chain).
Malla.Plugins.Baseis a special plugin that is always at the bottom of the hierarchy.
Using Plugins
You add plugins to a service using the :plugins option in the use Malla.Service macro.
defmodule MyService do
use Malla.Service,
plugins: [Malla.Plugins.Tracer, MyCustomPlugin]
defcb my_callback(arg) do
# Your service's implementation of the callback
:cont
end
endCreating a Plugin
To create a plugin, you use Malla.Plugin in a module. You can then implement lifecycle callbacks and defcb callbacks.
defmodule MyPlugin do
use Malla.Plugin,
plugin_deps: [AnotherPlugin]
@impl true
def plugin_config(srv_id, config) do
# Validate or modify the service configuration
validated_config = validate_my_config(config)
{:ok, validated_config}
end
@impl true
def plugin_start(srv_id, config) do
# Return child specs for the service's supervisor
children = [
{MyWorker, config: config[:key1]}
]
{:ok, children: children}
end
# A `defcb` callback that participates in the callback chain
defcb process_data(data) do
transformed = transform(data)
# Continue the chain with the transformed data
{:cont, transformed}
end
endPlugin Dependencies and Hierarchy
Plugins can declare dependencies on other plugins, creating a hierarchy that determines the execution order of callbacks.
defmodule AuthPlugin do
use Malla.Plugin
end
defmodule LoggingPlugin do
use Malla.Plugin,
plugin_deps: [AuthPlugin] # Depends on AuthPlugin
end
defmodule MyService do
use Malla.Service,
plugins: [LoggingPlugin] # AuthPlugin is automatically included
endThe plugin_deps option establishes a dependency relationship. When a callback is invoked, it walks down the chain from the highest-level plugin (the service) to the lowest-level one (Malla.Plugins.Base).
For the example above, the callback chain order is:
graph TB
A["MyService (top)<br/>- Highest priority<br/>- Can override any behavior<br/>⬅️ Start here"]
B["LoggingPlugin<br/>- Logs all operations<br/>- Depends on AuthPlugin"]
C["AuthPlugin<br/>- Validates authentication"]
D["Malla.Plugins.Base (bottom)<br/>- Default implementations<br/>- Always present"]
A -->|:cont| B
B -->|:cont| C
C -->|:cont| D
style A fill:#e1f5ff,stroke:#0088cc,stroke-width:2px
style D fill:#fff3e1,stroke:#cc8800,stroke-width:2pxExecution order: MyService → LoggingPlugin → AuthPlugin → Malla.Plugins.Base
Optional Dependencies
You can mark a dependency as optional. The plugin will be included only if it can be found in the codebase.
use Malla.Plugin,
plugin_deps: [
RequiredPlugin,
{OptionalPlugin, optional: true}
]Plugin Groups
To enforce a specific ordering among a set of related plugins, you can assign them to a group. Plugins in the same group are made to depend on the previous plugin in the list, ensuring sequential ordering.
# All these plugins belong to the :my_group
defmodule PluginA do; use Malla.Plugin, group: :my_group; end
defmodule PluginB do; use Malla.Plugin, group: :my_group; end
defmodule PluginC do; use Malla.Plugin, group: :my_group; end
defmodule MyService do
use Malla.Service,
plugins: [PluginA, PluginB, PluginC]
endThis results in the dependency chain PluginC → PluginB → PluginA. The callback execution order will be MyService → PluginC → PluginB → PluginA.
The Callback Chain
Callbacks defined with defcb participate in the plugin chain. When a callback is invoked, execution starts at the top of the hierarchy (the service) and proceeds downwards.
Each plugin in the chain can control the flow with its return value:
:cont: Continue to the next plugin in the chain with the same arguments.{:cont, new_args}or{:cont, arg1, arg2}: Continue to the next plugin with new, modified arguments.- Any other value: Stop the chain and return that value immediately.
See the Callbacks guide for a more detailed explanation.
Plugins reconfiguration
Plugins can be added and removed at runtime. See Reconfiguration.
Lifecycle Callbacks
Plugins can hook into the service lifecycle by implementing standard Elixir behaviours. These are optional.
Malla.Plugin.plugin_config/2: Called during the configuration phase (top-down). Each plugin can validate and modify the service configuration.Malla.Plugin.plugin_config_merge/3: Called when new configuration options need to be merged (during startup with runtime config, or during reconfiguration).Malla.Plugin.plugin_start/2: Called during the start phase (bottom-up). Each plugin can initialize resources and return a list of child processes to be supervised.Malla.Plugin.plugin_updated/3: Called when a service's configuration is updated at runtime. Can request a restart if needed.Malla.Plugin.plugin_stop/2: Called during the stop phase (top-down). Each plugin can clean up its resources before its children are stopped.
See the Lifecycle guide for more details on the complete lifecycle, and the Reconfiguration guide for details on runtime configuration updates.