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

Manages distributed Malla services across a cluster of nodes.

`Malla.Node` is a `GenServer` that monitors services, performs health checks,
and provides tools for remote procedure calls (RPC). It is the core of Malla's
service discovery mechanism.

When a `Malla.Service` is defined with `global: true`, it registers with `Malla.Node`,
making it discoverable by other nodes in the cluster.

Key features include:
- **Service Discovery**: Tracks which services are running on which nodes.
- **RPC Calls**: Provides `call/4`, `call_cb/4` and `call_cb_all/4` to invoke functions on
  remote services with automatic failover.
- **Health Checks**: Periodically re-checks all services on the network.
- **Virtual Modules**: Can dynamically generate proxy modules to make remote
  calls transparent.

For more information on how services are discovered and called, see the guides:
- **[Service Discovery](guides/08-distribution/02-service-discovery.md)**
- **[Remote Calls](guides/08-distribution/03-remote-calls.md)**

# `call_opt`

```elixir
@type call_opt() ::
  {:timeout, timeout()} | {:service_id, Malla.id()} | {:nodes, [node()]}
```

# `call_result`

```elixir
@type call_result() ::
  {:error, :malla_service_not_available | {:malla_rpc, {term(), String.t()}}}
  | term()
```

# `instance_status`

```elixir
@type instance_status() :: {node(), metadata :: service_info()}
```

# `service_info`

```elixir
@type service_info() :: Malla.Service.service_info()
```

# `service_info_message`

```elixir
@type service_info_message() :: %{id: atom(), pid: pid(), meta: service_info()}
```

# `call`

```elixir
@spec call(module(), atom(), list(), [call_opt()]) :: call_result()
```

  Launches a call to a any module and function at a remote node.

  A node list is obtained from `nodes` parameter, or, if not present,
  a _service's id_ must be obtained from `service_id` parameter or
  must be present in process dictionary (see `Malla.put_service_id/1`).
  Then we will use `get_nodes/1` to find nodes running this service.

  First node in the list will be called and, only if it returns `{:error, :malla_service_not_available}` from remote,
  the next one is tried. If the local node is running the service, it will be called first.

  Returns `{:error, :malla_service_not_available}` if we exhaust the instances and none is available.
  Returns `{:error, {:malla_rpc, {term, text}}}` if an exception is produced remotely or in `:erpc.call/5`.

  If `:timeout` is set to zero, call will be asynchronous.

# `call_cb`

```elixir
@spec call_cb(Malla.id(), atom(), list(), [call_opt()]) :: call_result()
```

  Launches a request to call a callback defined on a local or remote service.

  It works by using `call/4` but calling special function `c:Malla.Service.Interface.malla_cb_in/3`,
  that is always defined in remote services.

  This function will change process group (so that IO is not redirected to caller),
  set _service id_ and call malla callback `c:Malla.Plugins.Base.service_cb_in/3`;
  this, by default, will simply call the requested callback.

  If `:timeout` is set to zero, call will be asynchronous.

# `call_cb_all`

```elixir
@spec call_cb_all(Malla.id(), atom(), list(), [call_opt()]) :: [call_result()]
```

  Similar to `call_cb/4` but sends the call to all nodes implementing the requested service.

  It returns all responses from all nodes. If `:timeout` is set to zero, calls will be asynchronous.

# `get_instances`

```elixir
@spec get_instances(Malla.id()) :: [instance_status()]
```

  Gets all nodes implementing a specific service if it is in _running_ state at that node,
  along with metadata.

  If the local node is running the service, it will be first in the list.
  The rest will shuffle every service recheck for load-balancing purposes.

# `get_nodes`

```elixir
@spec get_nodes(Malla.id()) :: [node()]
```

  Gets all nodes implementing a specific service if it is in _running_ state at that node.

  If the local node is running the service, it will be first in the list.
  The rest will shuffle every service recheck for load-balancing purposes.

# `get_services`

```elixir
@spec get_services() :: %{
  services_info: %{required(pid()) =&gt; {Malla.id(), [{:meta, service_info()}]}},
  services_id: [Malla.id()]
}
```

Retrieves all detected _service ids_, their corresponding _pids_ and _metadata_.

# `precompile_stubs`

```elixir
@spec precompile_stubs() :: :ok
```

Precompiles stub modules for services defined in application configuration.

Reads the `:precompile` config key from `:malla` application and creates
stub modules for each service. These stubs will return `{:error, :malla_service_not_available}`
until the actual service is discovered.

Configuration format:
    config :malla,
      precompile: [
        MyService: [callback1: 0, callback2: 3]
      ]

# `wait_for_services`

```elixir
@spec wait_for_services(
  [Malla.id()],
  keyword()
) :: :ok | :timeout
```

Waits for all services in the list to become available by checking `get_nodes/1`.

Options can include `:retries` (default 10) for number of attempts, with 1-second sleeps between retries.

Returns `:ok` if all services are available, `:timeout` otherwise.

---

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