攜程 Apollo 配置中心傳統 .NET 項目集成實踐

官方文檔存在的問題

可能因爲 Apollo 配置中心的客戶端源碼一直處於更新中,致使其相關文檔有些跟不上節奏,部分文檔寫的不規範,很容易給作對接的新手朋友形成誤導。git

好比,我在參考以下兩個文檔使用傳統 .NET 客戶端作接入的時候就發現了些問題。github

問題一:兩個文檔關於標識應用身份的AppId的配置節點不一致。

app

問題二:第二個文檔關於應用配置發佈環境的Environment配置節點的描述出現明顯錯誤。
框架

固然,這些問題隨時都有可能被修復。若您看到文檔內容與本文描述不符,請以官方文檔爲準。分佈式

傳統 .NET 項目快速接入

快速進入正題。ui

安裝依賴包

在您項目的基礎設施層,經過 NuGet 包管理器或使用以下命令添加傳統 .NET 項目使用的客戶端:url

Install-Package Com.Ctrip.Framework.Apollo.ConfigurationManager -Version 2.0.3

從上面的包名能看出什麼?我這裏選裝的是2.0.3的版本。還有,這應該是一個 Javaer 起的名字。spa

配置應用標識 & 服務地址

在您的啓動項目中,打開App.configWeb.config配置文件,在<appSettings>節點中增長以下節點:.net

<!-- Change to the actual app id -->
<add key="Apollo.AppID" value="R01001" />
<add key="Apollo.MetaServer" value="http://localhost:8080" />

若您部署了多套 Config Service,支持多環境,請參考以下配置:code

<!-- Change to the actual app id -->
<add key="Apollo.AppID" value="R01001" />

<!-- Should change the apollo config service url for each environment -->
<add key="Apollo.Env" value="DEV" />
<add key="Apollo.DEV.Meta" value="http://localhost:8080"/>
<add key="Apollo.FAT.Meta" value="http://localhost:8081"/>
<add key="Apollo.UAT.Meta" value="http://localhost:8082"/>
<add key="Apollo.PRO.Meta" value="http://localhost:8083"/>

配置完成後,就能夠準備在咱們項目中使用 Apollo 客戶端了。

二次封裝代碼

咱們習慣在項目中使用第三方庫的時候封裝一層,這種封裝是淺層的,通常都是在項目的基礎設施層來作,這樣其餘層使用就不須要再次引入依賴包。

不說了,直接上代碼吧。

代碼結構大體以下:

├─MyCompany.MyProject.Infrastructure         # 項目基礎設施層
│  │                                                       
│  └─Configuration                         
│          ApolloConfiguration.cs            # Apollo 分佈式配置項讀取實現     
│          ConfigurationChangeEventArgs.cs   # 配置更改回調事件參數
│          IConfiguration.cs                 # 配置抽象接口,可基於此接口實現本地配置讀取

IConfiguration

using System;
using System.Configuration;

namespace MyCompany.MyProject.Infrastructure
{
    /// <summary>
    /// 配置抽象接口。
    /// </summary>
    public interface IConfiguration
    {
        /// <summary>
        /// 配置更改回調事件。
        /// </summary>
        event EventHandler<ConfigurationChangeEventArgs> ConfigChanged;

        /// <summary>
        /// 獲取配置項。
        /// </summary>
        /// <param name="key">鍵</param>
        /// <param name="namespaces">命名空間集合</param>
        /// <returns></returns>
        string GetValue(string key, params string[] namespaces);

        /// <summary>
        /// 獲取配置項。
        /// </summary>
        /// <typeparam name="TValue">值類型</typeparam>
        /// <param name="key">鍵</param>
        /// <param name="namespaces">命名空間集合</param>
        /// <returns></returns>
        TValue GetValue<TValue>(string key, params string[] namespaces);

        /// <summary>
        /// 獲取配置項,若是值爲 <see cref="null"/> 則取參數 <see cref="defaultValue"/> 值。
        /// </summary>
        /// <param name="key">鍵</param>
        /// <param name="defaultValue">默認值</param>
        /// <param name="namespaces">命名空間集合</param>
        /// <returns></returns>
        string GetDefaultValue(string key, string defaultValue, params string[] namespaces);

        /// <summary>
        /// 獲取配置項,若是值爲 <see cref="null"/> 則取參數 <see cref="defaultValue"/> 值。
        /// </summary>
        /// <typeparam name="TValue">值類型</typeparam>
        /// <param name="key">鍵</param>
        /// <param name="defaultValue">默認值</param>
        /// <param name="namespaces">命名空間集合</param>
        /// <returns></returns>
        TValue GetDefaultValue<TValue>(string key, TValue defaultValue, params string[] namespaces);
    }
}

ConfigurationChangeEventArgs

using Com.Ctrip.Framework.Apollo.Model;
using System.Collections.Generic;

namespace MyCompany.MyProject.Infrastructure
{
    public class ConfigurationChangeEventArgs
    {
        public IEnumerable<string> ChangedKeys => Changes.Keys;
        public bool IsChanged(string key) => Changes.ContainsKey(key);
        public string Namespace { get; }
        public IReadOnlyDictionary<string, ConfigChange> Changes { get; }
        public ConfigurationChangeEventArgs(string namespaceName, IReadOnlyDictionary<string, ConfigChange> changes)
        {
            Namespace = namespaceName;
            Changes = changes;
        }
        public ConfigChange GetChange(string key)
        {
            Changes.TryGetValue(key, out var change);
            return change;
        }
    }
}

ApolloConfiguration

using System;
using System.Configuration;
using System.Globalization;
using Com.Ctrip.Framework.Apollo;
using Com.Ctrip.Framework.Apollo.Model;

namespace MyCompany.MyProject.Infrastructure
{
    public class ApolloConfiguration : IConfiguration
    {
        private readonly string _defaultValue = null;

        public event EventHandler<ConfigurationChangeEventArgs> ConfigChanged;

        private IConfig GetConfig(params string[] namespaces)
        {
            var config = namespaces == null || namespaces.Length == 0 ?
                ApolloConfigurationManager.GetAppConfig().GetAwaiter().GetResult() :
                ApolloConfigurationManager.GetConfig(namespaces).GetAwaiter().GetResult();

            config.ConfigChanged += (object sender, ConfigChangeEventArgs args) =>
            {
                ConfigChanged(sender, new ConfigurationChangeEventArgs(args.Namespace, args.Changes));
            };

            return config;
        }

        public string GetValue(string key, params string[] namespaces)
        {
            key = key ?? throw new ArgumentNullException(nameof(key));
            var config = GetConfig(namespaces);
            return config.GetProperty(key, _defaultValue);
        }

        public TValue GetValue<TValue>(string key, params string[] namespaces)
        {
            var value = GetValue(key, namespaces);
            return value == null ?
                default(TValue) :
                (TValue)Convert.ChangeType(value, typeof(TValue), CultureInfo.InvariantCulture);
        }

        public string GetDefaultValue(string key, string defaultValue, params string[] namespaces)
        {
            key = key ?? throw new ArgumentNullException(nameof(key));
            var config = GetConfig(namespaces);
            return config.GetProperty(key, defaultValue);
        }

        public TValue GetDefaultValue<TValue>(string key, TValue defaultValue, params string[] namespaces)
        {
            var value = GetDefaultValue(key, defaultValue, namespaces);
            return value == null ?
                default(TValue) :
                (TValue)Convert.ChangeType(value, typeof(TValue), CultureInfo.InvariantCulture);
        }
    }
}

正確使用姿式

在使用以前須要先把ApolloConfiguration註冊到應用容器中,請參考以下代碼:

public class DependencyRegistrar : IDependencyRegistrar
{
    public void Register(ContainerBuilder builder, ITypeFinder typeFinder)
    {
        // 咱們項目使用的 DI 框架是 Autofac,註冊這個地方按需修改吧,注意將實例註冊成單例。
        builder.RegisterType<ApolloConfiguration>()
            .As<IConfiguration>()
            .Named<IConfiguration>("configuration")
            .SingleInstance();
            
        ...
    }

    public int Order
    {
        get { return 1; }
    }
}

接下來就能夠在項目中使用了,請參考以下代碼:

public class UserController : BaseController
{
    private readonly IConfiguration _configuration;

    public UserController(IConfiguration configuration)
    {
        _configuration = configuration;
    }
    
    public ActionResult Add(AddUserInput model)
    {
        if (ModelState.IsValid)
        {
            // 從 Apollo 分佈式配置中心 項目 R01001 默認命名空間`application`下 讀取配置項。
            model.Password = _configuration.GetValue("DefaultUserPassword");
            ...
        }
        ...
    }
}
相關文章
相關標籤/搜索