# 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: - Module Configs: - Akka.Remote Config: - Akka.Persistence Config: