# `Malla.Service`
[🔗](https://github.com/netkubes/malla/blob/main/lib/malla/service.ex#L21)

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:
- **[Services](guides/03-services.md)**: For an overview of creating services.
- **[Plugins](guides/04-plugins.md)**: For extending services with plugins.
- **[Callbacks](guides/05-callbacks.md)**: For understanding the callback chain.
- **[Lifecycle](guides/06-lifecycle.md)**: For the service lifecycle.
- **[Configuration](guides/07-configuration.md)**: For configuring services.
- **[Storage](guides/10-storage.md)**: For data storage options.

# `admin_status`

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

# `class`

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

# `id`

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

# `running_status`

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

# `service_info`

```elixir
@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`

```elixir
@type t() :: %Malla.Service{
  callbacks: %{required({atom(), integer()}) =&gt; {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`

```elixir
@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](guides/07-configuration.md).

# `__using__`
*macro* 

```elixir
@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 `t:use_opt/0` for configuration options.

# `add_plugin`

```elixir
@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:

  - `:config` - Optional keyword list of configuration to merge when adding the plugin.
  If provided, the config will be merged using the `c:Malla.Plugin.plugin_config_merge/3`.

# `child_via`

```elixir
@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`

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

Gets a previously registered child with `child_via/3`

# `defcb`
*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](guides/05-callbacks.md)** for details.

# `del`

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

Deletes a value from service's store table.

See [Storage](guides/10-storage.md) for details.

# `del_plugin`

```elixir
@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`

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

  Function used to prepare the node for stop.

  Callback `c: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`

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

Gets a value from service's store table.

See [Storage](guides/10-storage.md) for details.

# `get_callbacks`

```elixir
@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`

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

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

# `get_service_info`

```elixir
@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`

```elixir
@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?`

```elixir
@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?`

```elixir
@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 `c: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`

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

Inserts a value in service's store table.

See [Storage](guides/10-storage.md) for details.

# `put_new`

```elixir
@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](guides/10-storage.md) for details.

# `reconfigure`

  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 `c:Malla.Plugin.plugin_config_merge/3`.

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

# `restart_plugin`

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

Instructs to the service to restart a plugin.

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

# `set_admin_status`

```elixir
@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`

```elixir
@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 `c:Malla.Plugin.plugin_config_merge/3`.

See [Lifecycle](guides/06-lifecycle.md) for deatils

# `stop`

```elixir
@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 `c:Malla.Service.Interface.child_spec/1`,
  restart would be set to `:transient` so the supervisor will not restart it.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
