# Getting Started with Malla

```elixir
Mix.install([:malla])
```

## Introduction

Welcome to Malla! This tutorial will guide you through building distributed services with a plugin-based architecture. You'll learn how to:

1. Create a simple service with an API
2. Extract functionality into reusable plugins
3. Compose plugins to modify behavior
4. Dynamically reconfigure services at runtime

Let's build a simple calculator service to demonstrate these concepts.

## Part 1: Creating a Basic Service

First, let's create a simple calculator service that can add numbers.

```elixir
defmodule Calculator do
  use Malla.Service

  # Public API - can be called remotely
  def add(a, b) do
    a + b
  end

  # Callback that participates in the plugin chain
  defcb calculate(:add, a, b), do: {:ok, a + b}
end

# Start the service
{:ok, _pid} = Calculator.start_link([])

# Test the API
IO.puts("2 + 3 = #{Calculator.add(2, 3)}")

# Test the callback
{:ok, result} = Calculator.calculate(:add, 5, 7)
IO.puts("5 + 7 = #{result}")
```

Great! We have a working service. The `calculate/3` callback is special - it participates in a **callback chain** where plugins can intercept and modify behavior.

## Part 2: Moving Functionality into a Plugin

Now let's extract the addition logic into a reusable plugin. This makes it easier to share functionality across multiple services.

```elixir
defmodule AdditionPlugin do
  use Malla.Plugin

  # This callback will be part of the chain
  defcb calculate(:add, a, b), do: {:ok, a + b}

  # If we don't support this, let other plugins process it
  defcb calculate(_, _, _), do: :cont
end

# Create a new calculator that uses the plugin
defmodule CalculatorV2 do
  use Malla.Service, plugins: [AdditionPlugin]

  # The service doesn't need to implement calculate/3
  # The plugin handles it!
end

# Start and test
{:ok, _pid} = CalculatorV2.start_link([])
{:ok, result} = CalculatorV2.calculate(:add, 10, 20)
IO.puts("Using plugin: 10 + 20 = #{result}")
```

Notice how the service became simpler? The plugin now handles the calculation logic.

## Part 3: Adding More Functionality with Another Plugin

Let's add multiplication by creating another plugin. This demonstrates how plugins **compose** together.

```elixir
defmodule MultiplicationPlugin do
  use Malla.Plugin

  defcb calculate(:multiply, a, b), do: {:ok, a * b}
  defcb calculate(_, _, _), do: :cont
end

# Create a calculator with both plugins
defmodule CalculatorV3 do
  use Malla.Service,
    plugins: [
      AdditionPlugin,
      MultiplicationPlugin
    ]
end

# Start and test both operations
{:ok, _pid} = CalculatorV3.start_link([])

{:ok, sum} = CalculatorV3.calculate(:add, 8, 4)
IO.puts("8 + 4 = #{sum}")

{:ok, product} = CalculatorV3.calculate(:multiply, 8, 4)
IO.puts("8 × 4 = #{product}")
```

### How the Callback Chain Works

When you call `CalculatorV3.calculate(:add, 8, 4)`, here's what happens:

1. **CalculatorV3** checks first (top of chain) - returns `:cont`
2. **MultiplicationPlugin** checks - operation is `:add`, returns `:cont`
3. **AdditionPlugin** checks - operation is `:add`, returns `{:ok, 12}` ✓
4. **Chain stops** - the result is returned

The chain walks from top to bottom until a plugin returns something other than `:cont`.

## Part 4: Plugin Dependencies and Chain Ordering

A plugin can declare dependencies on other plugins using `plugin_deps`. This has two effects:

1. **Automatic inclusion** - dependent plugins are pulled in transitively, so the service only needs to list the top-level plugin.
2. **Chain ordering** - the dependent plugin is placed **above** its dependencies in the callback chain, so it gets called first.

Let's create a `LoggingPlugin` that depends on both math plugins and logs every operation to screen before letting them handle it.

```elixir
defmodule LoggingPlugin do
  use Malla.Plugin,
    plugin_deps: [AdditionPlugin, MultiplicationPlugin]

  defcb calculate(operation, a, b) do
    # This runs BEFORE the math plugins because LoggingPlugin
    # is above them in the chain (it depends on them).
    IO.puts(">> calculate(#{operation}, #{a}, #{b})")

    # Return :cont to let the math plugins handle the actual computation
    :cont
  end
end

# We only need to list LoggingPlugin - its dependencies
# (AdditionPlugin and MultiplicationPlugin) are included automatically.
defmodule CalculatorV4 do
  use Malla.Service,
    plugins: [LoggingPlugin]
end

# Start and test - watch the logs!
{:ok, _pid} = CalculatorV4.start_link([])

{:ok, sum} = CalculatorV4.calculate(:add, 6, 7)
IO.puts("= #{sum}\n")

{:ok, product} = CalculatorV4.calculate(:multiply, 6, 7)
IO.puts("= #{product}")
```

### Chain Order with Dependencies

The chain for `CalculatorV4.calculate(:multiply, 6, 7)`:

1. **CalculatorV4** (top) - no `defcb calculate`, returns `:cont`
2. **LoggingPlugin** - prints `>> calculate(multiply, 6, 7)`, returns `:cont`
3. **MultiplicationPlugin** - handles `:multiply`, returns `{:ok, 42}`
4. **Chain stops** - the result is returned

By declaring `plugin_deps`, we told Malla that LoggingPlugin sits above the math plugins. No need to manually order the `plugins:` list.

## Part 5: Extending Behavior Without Modifying Existing Plugins

A key benefit of the plugin architecture is that you can add new behavior **without touching existing code**. Let's create a `FloatPlugin` that converts all results to floats. It depends on `LoggingPlugin`, so it sits above everything in the chain.

```elixir
defmodule FloatPlugin do
  use Malla.Plugin,
    plugin_deps: [LoggingPlugin, MultiplicationPlugin, AdditionPlugin]

  defcb calculate(operation, a, b) do
    # Convert arguments to floats before passing them down the chain
    {:cont, [operation, a / 1, b / 1]}
  end
end

# FloatPlugin depends on LoggingPlugin, which depends on both math plugins.
# All four plugins are included automatically.
defmodule CalculatorV4b do
  use Malla.Service,
    plugins: [FloatPlugin]
end

{:ok, _pid} = CalculatorV4b.start_link([])

{:ok, sum} = CalculatorV4b.calculate(:add, 6, 7)
IO.puts("= #{sum}")
# => 13.0 (float!)

{:ok, product} = CalculatorV4b.calculate(:multiply, 6, 7)
IO.puts("= #{product}")
# => 42.0 (float!)
```

Notice that we didn't modify `AdditionPlugin`, `MultiplicationPlugin`, or `LoggingPlugin`. FloatPlugin uses `{:cont, [operation, a / 1, b / 1]}` to pass **modified arguments** down the chain, converting integers to floats before the math plugins see them. The existing plugins work unchanged - they just receive floats instead of integers.

## Part 6: Adding and Removing Plugins at Runtime

Malla can add or remove plugins from a running service without stopping it. The service restarts automatically with the updated plugin chain. This is useful for debugging in production, feature rollouts, or disabling problematic plugins.

Let's start a calculator with `FloatPlugin` and then remove it at runtime.

```elixir
defmodule CalculatorV6 do
  use Malla.Service,
    plugins: [FloatPlugin, MultiplicationPlugin, AdditionPlugin]
end

{:ok, _pid} = CalculatorV6.start_link([])

IO.puts("--- With FloatPlugin ---")
IO.inspect(CalculatorV6.calculate(:add, 6, 7))
# => {:ok, 13.0}

# Remove FloatPlugin at runtime
IO.puts("\n--- Removing FloatPlugin ---")
Malla.Service.del_plugin(CalculatorV6, FloatPlugin)
Process.sleep(100)

IO.inspect(CalculatorV6.calculate(:add, 6, 7))
# => {:ok, 13}

# Add it back
IO.puts("\n--- Adding FloatPlugin back ---")
Malla.Service.add_plugin(CalculatorV6, FloatPlugin)
Process.sleep(100)

IO.inspect(CalculatorV6.calculate(:add, 6, 7))
# => {:ok, 13.0}
```

**No code changes, no recompilation, no downtime**. The callback chain is rebuilt and the service restarts with the new plugin configuration automatically.

## Part 7: Runtime Configuration and Reconfiguration

Malla services can be configured through `use Malla.Service` and reconfigured at runtime with `Malla.Service.reconfigure/2`. By convention, configuration is organized as keyword lists under keys named after each plugin or the service itself.

Plugins can read service configuration inside callbacks using `Malla.get_service_id!/0` and `srv_id.get_config/0`.

```elixir
defmodule RoundingPlugin do
  use Malla.Plugin,
    plugin_deps: [AdditionPlugin, MultiplicationPlugin]

  # Handle merging our config key when reconfigure/2 is called.
  # Without this, updates are ignored (the default returns :ok = "skip").
  @impl true
  def plugin_config_merge(_srv_id, config, update) do
    case update[:rounding_plugin] do
      nil -> :ok
      my_update ->
        my_config = config |> Keyword.get(:rounding_plugin, %{}) |> Map.merge(my_update)
        {:ok, Keyword.put(config, :rounding_plugin, my_config)}
    end
  end

  defcb calculate(operation, a, b) do
    srv_id = Malla.get_service_id!()
    config = srv_id.get_config()
    decimals = get_in(config, [:rounding_plugin, :decimals]) || 2

    # Modify the arguments by rounding, then continue the chain
    {:cont, [operation, Float.round(a / 1, decimals), Float.round(b / 1, decimals)]}
  end
end

defmodule CalculatorV7 do
  use Malla.Service,
    plugins: [RoundingPlugin],
    # Default config: round to 2 decimal places (use a map)
    rounding_plugin: %{decimals: 2}
end

{:ok, _pid} = CalculatorV7.start_link([])

IO.puts("--- Rounding to 2 decimals (default) ---")
IO.inspect(CalculatorV7.calculate(:add, 1.005, 2.006))

# Change rounding precision at runtime (use a map)
IO.puts("\n--- Reconfiguring to 4 decimals ---")
Malla.Service.reconfigure(CalculatorV7, rounding_plugin: %{decimals: 4})
Process.sleep(100)

IO.inspect(CalculatorV7.calculate(:add, 1.005, 2.006))

# You can also pass config at startup via start_link
# CalculatorV7.start_link(rounding_plugin: %{decimals: 0})
```

## Key Takeaways

1. **Services** are the main building blocks - they define what your system does
2. **Plugins** are reusable pieces of functionality that can be composed together
3. **Callbacks** (defined with `defcb`) form chains that execute top-to-bottom
4. **Plugin dependencies control chain order** - dependent plugins run before their dependencies
5. **Return `:cont`** to continue the chain, or any other value to stop and return
6. **Configuration** - any non-reserved key in `use Malla.Service` becomes config, readable with `get_config/0` and changeable with `reconfigure/2`
7. **Plugins can be added/removed at runtime** with `add_plugin/2` and `del_plugin/2`

## Next Steps

This tutorial only scratches the surface of what Malla offers. Check out the [guides](introduction.html) and API documentation for the full picture: service lifecycle, distributed service discovery, remote calls, request handling, tracing, and more.
