将 Akka.net 与 IConfiguration 一起使用

Using Akka.net with IConfiguration

几乎所有 Akka.net 文档都以某种形式引用通过 HOCON 的文档。根据我的理解,HOCON 是为了解决与 .NET Framework 中基于 XML 的配置相关的问题。现在一切都基于 JSON(从 .NET Core 开始),我真的很想配置 Akka.net 到 appsettings.json,就像我在 .NET 中编写的任何其他服务一样。我发现通过在 appsettings 文件中粘贴一个 HOCON 字符串,或者在源代码中内联 HOCON 对象,我发现这种方法的一些解决方案看起来很老套。将它放在应用程序设置中会非常好,因为这更适合我的团队如何管理配置、部署方式和现代 .NET 应用程序方法配置。

为什么 Akka.net 使用 HOCON 而不是更抽象的接口,例如 IConfiguration,我如何才能按照 .NET 中使用 appsettings.json 和 IConfiguration 的最佳实践最好地配置它?

我认为 Akka.net 使用 HOCON 的原因之一是它是 Akka (Java) 的 1-1 端口,它也严重依赖 HOCON 进行配置。为了可移植性,它是配置框架的首选格式。虽然这只是我的猜测,但为什么不支持 IConfiguration 可能是一个优先事项,因为当前的配置方式“有效”,尽管它与较新的 .NET 应用程序的编写方式不太吻合今天

有几种方法可以从 IConfiguration 实例构建 Akka.Configuration.Config。一种方法是获取 IConfiguration 对象,并从中构造一个 JSON 字符串,然后将其提供给支持解析 JSON 的 ConfigurationFactory.ParseString。然后可以从解析的 Config 实例中获得 HOCON 表示。 为了正确解析 JSON 对象,生成的 HOCON 字符串必须再次解析(我的猜测是因为一个错误导致它以不同的方式解释 JSON 和 HOCON)。 确保 JSON 配置不包含任何属性,例如 {"foo.don" : "bar"},而 HOCON 支持解析 foo.don - JSON 不包含。下面是我放在一起的扩展方法,用于从 IConfiguration 实例解析 Config

public static class ConfigurationExtensions
{
    public static Config ToAkkaConfiguration(this IConfiguration config)
    {
        var jToken = BuildJToken(config);
        var jsonString = JsonConvert.SerializeObject(jToken);
        var hocon = ConfigurationFactory.ParseString(jsonString);

        return hocon;
    }

    // Based on 
    private static JToken BuildJToken(IConfiguration config)
    {
        var obj = new JObject();

        foreach (var child in config.GetChildren())
        {
            if (child.Path.EndsWith(":0"))
            {
                var arr = new JArray();

                foreach (var arrayChild in config.GetChildren())
                {
                    arr.Add(BuildJToken(arrayChild));
                }

                return arr;
            }
            else
            {
                obj.Add(child.Key, BuildJToken(child));
            }
        }

        if (!obj.HasValues && config is IConfigurationSection section)
        {
            if (long.TryParse(section.Value, out long integer))
            {
                return new JValue(integer);
            }
            else if (bool.TryParse(section.Value, out bool boolean))
            {
                return new JValue(boolean);
            }
            else if (decimal.TryParse(section.Value, out decimal real))
            {
                return new JValue(real);
            }

            return new JValue(section.Value);
        }

        return obj;
    }
}

更好的解决方案是实现解析器,类似于 Akka.Configuration.Hocon.Parser 构建 Config 对象的方式。这将是比上面的 hacky 方法更好的解决方案:

public static class ConfigurationExtensions
{
    public static Config ToAkkaConfiguration(this IConfiguration config)
    {
        var root = new HoconValue();
        ParseConfig(root, config);
        var hconRoot = new HoconRoot(root);
        return new Config(hconRoot);
    }

    private static void ParseConfig(HoconValue owner, IConfiguration config)
    {
        if (config is IConfigurationSection section && !config.GetChildren().Any())
        {
            var lit = new HoconLiteral { Value = section.Value };
            owner.AppendValue(lit);
        }
        else
        {
            foreach (var child in config.GetChildren())
            {
                if (child.Path.EndsWith(":0"))
                {
                    var array = ParseArray(config);
                    owner.AppendValue(array);
                    return;
                }
                else
                {
                    if (owner.GetObject() == null)
                        owner.NewValue(new HoconObject());

                    var key = owner.GetObject().GetOrCreateKey(child.Key);

                    ParseConfig(key, child);
                }
            }
        }
    }

    private static HoconArray ParseArray(IConfiguration config)
    {
        var arr = new HoconArray();

        foreach (var arrayChild in config.GetChildren())
        {
            var value = new HoconValue();
            ParseConfig(value, arrayChild);
            arr.Add(value);
        }

        return arr;
    }
}

在任何一种情况下,一个 appsettings.json with element 包含 Akka 配置然后可以使用上述任何一种方法进行解析。例如:

{
  "AkkaConfiguration": {
    "akka": {
      "actor": {
        "provider": "cluster"
      },
      "remote": {
        "dot-netty": {
          "tcp": {
            "port": 8081,
            "hostname": "localhost"
          }
        }
      },
      "cluster": {
        "seed-nodes": [ "akka.tcp://test@localhost:8081" ],
        "roles": [ "myService" ]
      }
    }
  }
}

可以通过以下方式检索配置:

var config = config.GetSection("AkkaConfiguration").ToAkkaConfiguration();