Notes and documentation covering actors, remoting, clustering, persistence, streams, serialization, hosting, testing, and best practices for the Akka.NET framework used throughout the ScadaLink system.
7.8 KiB
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.jsonandIConfiguration - 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:
- Akka.Hosting (code-first): Remoting, Cluster, Singleton, Persistence, Distributed Data — all major module configuration
- HOCON (embedded in code): Fine-grained tuning that Hosting APIs don't expose — SBR details, dispatcher settings, serialization bindings
- appsettings.json: Site-specific values — node hostname, seed node addresses, device configuration path, database connection strings
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):
- Akka.Hosting API calls
- HOCON with
Prepend - 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:
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:
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:
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:
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
{
"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