Malla.Service (malla v0.0.1-rc.1)

Copy Markdown View Source

Defines the core behavior for a Malla service.

Malla.Service is the foundation of the Malla framework. By use Malla.Service, a module is transformed into a service with features like automatic cluster discovery, a plugin-based architecture, and compile-time optimized callback chains.

When you use this module at your service module, a number of utility functions are inserted into your module. They are documented at Malla.Service.Interface.

For comprehensive documentation, please see the guides:

Summary

Functions

Macro that transforms a module into a Malla service.

Adds a new plugin to the service at run-time.

Utility function to register a name for a plugin's child, using Malla.Registry.

Gets a previously registered child with child_via/3

Macro for defining service callbacks.

Deletes a value from service's store table.

Removes a plugin from the service at run-time.

Function used to prepare the node for stop.

Gets a value from service's store table.

Retrieves all implementations of a callback function across plugins.

Returns supervisor PID for a specific plugin children, if defined.

Get current service status.

Get current running status.

Function used to detect if the service is live. It returns true if the service is in a live state (starting, running, pausing, paused, stopped, stopping).

Function used to detect if the service is ready.

Inserts a value in service's store table.

Inserts a new value in service's store table. Returns false if object already exists.

Updates config for the service at runtime.

Instructs to the service to restart a plugin.

Sets current admin status for the service.

Starts a new service instance.

Stops a service instance.

Types

admin_status()

@type admin_status() :: :active | :pause | :inactive

class()

@type class() :: Malla.class()

id()

@type id() :: Malla.id()

running_status()

@type running_status() ::
  :starting | :running | :pausing | :paused | :stopping | :stopped | :failed

service_info()

@type service_info() :: %{
  id: Malla.id(),
  vsn: Malla.vsn(),
  hash: pos_integer(),
  admin_status: admin_status(),
  running_status: running_status(),
  last_status_time: pos_integer(),
  last_status_reason: term(),
  last_error: term(),
  last_error_time: pos_integer() | nil,
  pid: pid(),
  node: node(),
  callbacks: [{atom(), pos_integer()}]
}

t()

@type t() :: %Malla.Service{
  callbacks: %{required({atom(), integer()}) => {atom(), [module()]}},
  class: class(),
  config: Keyword.t(),
  global: boolean(),
  id: id(),
  plugin_chain: [module()],
  plugins: [module()],
  start_paused: boolean(),
  vsn: String.t(),
  wait_for_services: [id()]
}

use_opt()

@type use_opt() ::
  {:class, Malla.class()}
  | {:vsn, Malla.vsn()}
  | {:otp_app, atom()}
  | {:global, boolean()}
  | {:paused, boolean()}
  | {:plugins, [module()]}
  | {atom(), any()}

Options for configuring a service when using Malla.Service.

Options include:

  • :class - The service class. Any atom can be used. Not used by Malla but available in metadata.
  • :vsn - Version string. Not used by Malla but available in metadata.
  • :otp_app - If provided, configuration will be fetched from application config and merged.
  • :global - Whether the service is globally visible.
  • :paused - Whether to start in paused state.
  • :plugins - List of plugin modules this services depends on.

Any other key is considered configuration for the service. See Configuration.

Functions

__using__(use_spec)

(macro)
@spec __using__([use_opt()]) :: Macro.t()

Macro that transforms a module into a Malla service.

This macro inserts required functions, sets up plugin configuration, registers callbacks, and prepares compile-time hooks for building the service structure and dispatch logic.

See use_opt/0 for configuration options.

add_plugin(srv_id, plugin, opts \\ [])

@spec add_plugin(Malla.id(), module(), keyword()) :: :ok | {:error, term()}

Adds a new plugin to the service at run-time.

New plugin will be added to the callback chain, taking into account any declared dependency, and starting any declared children.

It will also recalculate the whole callback chain, according to callbacks defined in this new plugin.

Dispatch module will be recompiled, so new callback chain is available immediately.

Options:

child_via(srv_id, plugin, name)

@spec child_via(Malla.id(), module(), term()) :: {:via, module(), any()}

Utility function to register a name for a plugin's child, using Malla.Registry.

You can use child_whereis/3 to find it later.

child_whereis(srv_id, plugin, name)

@spec child_whereis(Malla.id(), module(), term()) :: pid() | nil

Gets a previously registered child with child_via/3

defcb(ast, list)

(macro)

Macro for defining service callbacks.

Transforms a function definition into a callback that can be chained across plugins. The original function is renamed to {name}_malla_service and registered for the callback system. At compile time, chained dispatch functions are generated that call implementations in dependency order, supporting continuation logic.

A Malla callback can return any of the following:

  • :cont: continues the call to the next function in the call chain.
  • {:cont, [:a, b:]}: continues the call, but changing the parameters used for the next call. in chain. The list of the array must fit the number of arguments.
  • {:cont, :a, :b}: equivalent to {:cont, [:a, :b]}.
  • any: any other response stops the call chain a returns this value to the caller.

See Callbacks for details.

del(srv_id, key)

@spec del(id(), term()) :: :ok

Deletes a value from service's store table.

See Storage for details.

del_plugin(srv_id, plugin)

@spec del_plugin(Malla.id(), module()) :: :ok | {:error, term()}

Removes a plugin from the service at run-time.

Plugin will be removed from plugins chain, and children will be stopped.

It will also recalculate the whole callback chain, according to callbacks defined in this new plugin.

Dispatch module will be recompiled, so new callback chain is available immediately.

drain(srv_id)

@spec drain(Malla.id()) :: boolean()

Function used to prepare the node for stop.

Callback Malla.Plugins.Base.service_drain/0 is called to allow each plugin to clean its state, and return :cont if it is ready to stop, or false if it is not.

If all plugins completed the drain, this returns true and the node can be stopped. If any returned false, this function returns false and the node should not be yet stopped.

get(srv_id, key, default \\ nil)

@spec get(id(), term(), term()) :: term()

Gets a value from service's store table.

See Storage for details.

get_callbacks(service_id, fun)

@spec get_callbacks(Malla.id(), atom()) :: [{{atom(), integer()}, [module()]}]

Retrieves all implementations of a callback function across plugins.

Returns a list of {callback_name, arity} along the list of modules that implement it, useful for debugging or introspection.

get_plugin_sup(srv_id, plugin)

@spec get_plugin_sup(Malla.id(), module()) :: pid() | nil

Returns supervisor PID for a specific plugin children, if defined.

get_service_info(srv_id, timeout \\ 5000)

@spec get_service_info(Malla.id() | pid(), timeout()) ::
  {:ok, service_info()} | {:error, term()}

Get current service status.

This is implemented as a call to the service's GenServer.

get_status(srv_id)

@spec get_status(Malla.id()) :: running_status() | :unknown

Get current running status.

This is cached so it is very fast, but it could be slightly outdated on service status changes.

is_live?(srv_id)

@spec is_live?(Malla.id()) :: boolean()

Function used to detect if the service is live. It returns true if the service is in a live state (starting, running, pausing, paused, stopped, stopping).

If the service is in failed or unknown state, returns false, and external caller is expected to reset this node if this keeps returning false.

Useful in Kubernetes pod health checks.

is_ready?(srv_id)

@spec is_ready?(Malla.id()) :: boolean()

Function used to detect if the service is ready.

If the service is in running status we call callback Malla.Plugins.Base.service_is_ready?/0. Any plugin that is not ready should return false, or, if it is ready, :cont to go to next in chain.

For other statuses it will return false. Useful in Kubernetes pod health checks.

put(srv_id, key, value)

@spec put(id(), term(), term()) :: :ok

Inserts a value in service's store table.

See Storage for details.

put_new(srv_id, key, value)

@spec put_new(id(), term(), term()) :: true | false

Inserts a new value in service's store table. Returns false if object already exists.

See Storage for details.

reconfigure(srv_id, update)

Updates config for the service at runtime.

This is a very powerful function, and all used plugins need to support it in order to work properly (unless they are not affected by changes).

The new configuration is deep-merged over the previous one by default. Plugins can customize merge behavior via Malla.Plugin.plugin_config_merge/3.

After the update we will call Malla.Plugin.plugin_updated/3 for existing plugins. If a plugin does not implement it, it won't notice the update.

restart_plugin(srv_id, plugin)

@spec restart_plugin(Malla.id(), module()) :: :ok

Instructs to the service to restart a plugin.

Children supervisor, if started, will be stopped, and Malla.Plugin.plugin_start/2 will be called again.

set_admin_status(srv_id, admin_status, reason \\ :none)

@spec set_admin_status(Malla.id(), admin_status(), atom()) :: :ok | {:error, term()}

Sets current admin status for the service.

See above for a detailed description of each status.

start_link(srv_id, config)

@spec start_link(
  Malla.id(),
  keyword()
) :: {:ok, pid()} | {:error, term()}

Starts a new service instance.

If a non-empty config is provided, it will be deep-merged with the compile-time config. Plugins can customize this behavior via Malla.Plugin.plugin_config_merge/3.

See Lifecycle for deatils

stop(srv_id)

@spec stop(Malla.id()) :: :ok | {:error, any()}

Stops a service instance.

If the service was started under a supervisor, this could try to restart it. If you used service module's Malla.Service.Interface.child_spec/1, restart would be set to :transient so the supervisor will not restart it.