# 21 — HOCON Configuration

## Overview

HOCON (Human-Optimized Config Object Notation) is Akka.NET's native configuration format. It supports hierarchical keys, includes, substitutions, fallback chains, and default values. While Akka.Hosting provides code-first configuration for most modules, HOCON remains necessary for fine-grained settings that Hosting APIs don't yet cover, and for understanding default behaviors.

In the SCADA system, the recommended approach is **Akka.Hosting as the primary configuration mechanism**, with HOCON used only for settings not exposed by Hosting's fluent API. Site-specific values (hostnames, ports, device lists) come from `appsettings.json` via `Microsoft.Extensions.Configuration`.

## When to Use

- Fine-grained settings not yet covered by Akka.Hosting APIs (e.g., specific dispatcher tuning, transport failure detector thresholds, serialization bindings)
- Understanding and overriding Akka.NET default configurations
- Legacy configuration that predates Akka.Hosting adoption

## When Not to Use

- As the primary configuration mechanism in new .NET 10 projects — use Akka.Hosting instead
- For site-specific deployment values (hostnames, connection strings) — use `appsettings.json` and `IConfiguration`
- Do not duplicate settings in both HOCON and Akka.Hosting — pick one source of truth per setting

## Design Decisions for the SCADA System

### Configuration Layering

The SCADA system uses a three-layer configuration approach:

1. **Akka.Hosting (code-first):** Remoting, Cluster, Singleton, Persistence, Distributed Data — all major module configuration
2. **HOCON (embedded in code):** Fine-grained tuning that Hosting APIs don't expose — SBR details, dispatcher settings, serialization bindings
3. **appsettings.json:** Site-specific values — node hostname, seed node addresses, device configuration path, database connection strings

```csharp
akkaBuilder
    // Layer 1: Hosting APIs
    .WithRemoting(options => { ... })
    .WithClustering(new ClusterOptions { ... })

    // Layer 2: HOCON for fine-grained settings
    .AddHocon(ConfigurationFactory.ParseString(@"
        akka.cluster.split-brain-resolver {
            active-strategy = keep-oldest
            keep-oldest.down-if-alone = on
            stable-after = 15s
        }
        akka.actor.serialization-bindings {
            ""ScadaSystem.Messages.IClusterMessage, ScadaSystem"" = hyperion
        }
    "), HoconAddMode.Prepend);
```

### HOCON Precedence

When combining HOCON with Hosting, use `HoconAddMode.Prepend` to ensure your HOCON overrides defaults, or `HoconAddMode.Append` to provide fallback values. Hosting API settings are applied after HOCON, so they take highest precedence.

Effective precedence (highest to lowest):
1. Akka.Hosting API calls
2. HOCON with `Prepend`
3. Akka.NET internal defaults (reference.conf)

### Avoiding HOCON Files on Disk

Do not deploy `akka.hocon` or `akka.conf` files alongside the SCADA application. All HOCON should be embedded in code (via `ConfigurationFactory.ParseString`) or generated from `appsettings.json`. This avoids configuration drift between nodes — both nodes use the same compiled code with identical embedded HOCON.

Site-specific differences (hostname, seed nodes) come from `appsettings.json`, which is deployed per-node.

## Common Patterns

### Reading HOCON Defaults for Reference

Akka.NET modules ship with `reference.conf` files that contain all default values. These are embedded in the NuGet packages. To view defaults for any module:

```csharp
var defaults = ConfigurationFactory.Default();
var remoteDefaults = defaults.GetConfig("akka.remote");
Console.WriteLine(remoteDefaults.ToString());
```

This is useful when tuning specific settings — you can see the default value before overriding it.

### HOCON Substitutions

HOCON supports variable substitution, useful for sharing values:

```hocon
scada {
  cluster-role = "scada-node"
}

akka.cluster {
  roles = [${scada.cluster-role}]
}

akka.cluster.singleton {
  role = ${scada.cluster-role}
}
```

### Environment Variable Overrides

HOCON can read environment variables, useful for container deployments:

```hocon
akka.remote.dot-netty.tcp {
  hostname = ${?SCADA_HOSTNAME}  # Override from env var if set
  port = 4053
}
```

The `?` prefix makes the substitution optional — if the env var isn't set, the setting is omitted (and the default applies).

### Complete HOCON Reference for the SCADA System

This is the full HOCON block for settings that Hosting APIs don't cover:

```hocon
akka {
  # SBR fine-tuning
  cluster {
    split-brain-resolver {
      active-strategy = keep-oldest
      keep-oldest.down-if-alone = on
      stable-after = 15s
    }

    failure-detector {
      heartbeat-interval = 1s
      threshold = 8.0
      acceptable-heartbeat-pause = 10s
    }
  }

  # Serialization bindings
  actor {
    serializers {
      hyperion = "Akka.Serialization.HyperionSerializer, Akka.Serialization.Hyperion"
    }
    serialization-bindings {
      "ScadaSystem.Messages.IClusterMessage, ScadaSystem" = hyperion
    }
  }

  # Remote transport tuning
  remote {
    dot-netty.tcp {
      maximum-frame-size = 256000b
    }
    transport-failure-detector {
      heartbeat-interval = 4s
      acceptable-heartbeat-pause = 20s
    }
  }

  # Distributed Data tuning
  cluster.distributed-data {
    gossip-interval = 2s
    notify-subscribers-interval = 500ms
    durable {
      keys = ["pending-commands"]
      lmdb {
        dir = "C:\\ProgramData\\SCADA\\ddata"
        map-size = 104857600
      }
    }
  }

  # IO tuning for custom protocol connections
  io.tcp {
    max-channels = 1024
  }
}
```

## Anti-Patterns

### HOCON Files with Different Content Per Node

If `akka.conf` files are deployed separately to each node and they drift out of sync, cluster behavior becomes unpredictable. Both nodes must have identical Akka configuration except for node-specific values (hostname). Use embedded HOCON + `appsettings.json` to enforce this.

### Overriding Hosting Settings with HOCON

If you configure Remoting via `WithRemoting()` and also set `akka.remote.dot-netty.tcp.port` in HOCON, the effective value depends on precedence order. This is confusing and error-prone. Pick one mechanism per setting.

### Deep HOCON Nesting Without Comments

HOCON is powerful but can become unreadable. Always comment non-obvious settings, especially timeouts and thresholds that affect failover behavior.

### Ignoring reference.conf Defaults

Akka.NET's defaults are generally sensible. Do not override every setting "just to be explicit." Override only settings where the default doesn't match your SCADA requirements (e.g., SBR strategy, failure detector tuning).

## Configuration Guidance

### appsettings.json Structure

```json
{
  "ScadaSite": {
    "NodeHostname": "nodeA.scada.local",
    "SeedNodes": [
      "akka.tcp://scada-system@nodeA.scada.local:4053",
      "akka.tcp://scada-system@nodeB.scada.local:4053"
    ],
    "RemotePort": 4053,
    "ManagementPort": 8558,
    "DeviceConfigPath": "C:\\ProgramData\\SCADA\\devices.json",
    "PersistenceDbPath": "C:\\ProgramData\\SCADA\\persistence.db",
    "DistributedDataPath": "C:\\ProgramData\\SCADA\\ddata"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Akka": "Warning"
    }
  }
}
```

### File Paths

All SCADA data files (persistence DB, distributed data, device config) should live under a dedicated directory (`C:\ProgramData\SCADA\`) with appropriate ACLs. Both nodes should use the same path structure for consistency, even though the files are node-local.

## References

- Official Documentation: <https://getakka.net/articles/configuration/hocon.html>
- Module Configs: <https://getakka.net/articles/configuration/modules/akka.html>
- Akka.Remote Config: <https://getakka.net/articles/configuration/modules/akka.remote.html>
- Akka.Persistence Config: <https://getakka.net/articles/configuration/modules/akka.persistence.html>
