官方文件存在的問題
可能由於 Apollo 配置中心的客戶端原始碼一直處於更新中,導致其相關文件有些跟不上節奏,部分文件寫的不規範,很容易給做對接的新手朋友造成誤導。
比如,我在參考如下兩個文件使用傳統 .NET 客戶端做接入的時候就發現了些問題。
- ctripcorp/apollo - .Net客戶端使用指南
- ctripcorp/apollo.net - .Net客戶端之與 System.Configuration.ConfigurationManager 整合
1.兩個文件關於標識應用身份的AppId
的配置節點不一致。
2.第二個文件關於應用配置釋出環境的Environment
配置節點的描述出現明顯錯誤。
當然,這些問題隨時都有可能被修復。若您看到文件內容與本文描述不符,請以官方文件為準。
傳統 .NET 專案快速接入
快速進入正題。
安裝依賴包
在您專案的基礎設施層,通過 NuGet 包管理器或使用如下命令新增傳統 .NET 專案使用的客戶端:
Install-Package Com.Ctrip.Framework.Apollo.ConfigurationManager -Version 2.0.3
從上面的包名能看出什麼?我這裡選裝的是2.0.3
的版本。還有,這應該是一個 Javaer 起的名字。
配置應用標識 & 服務地址
在您的啟動專案中,開啟App.config
或Web.config
配置檔案,在<appSettings>
節點中增加如下節點:
<!-- Change to the actual app id -->
<add key="Apollo.AppID" value="R01001" />
<add key="Apollo.MetaServer" value="http://localhost:8080" />
若您部署了多套 Config Service,支援多環境,請參考如下配置:
<!-- 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
註冊到應用容器中,請參考如下程式碼:
// 這裡我們專案使用的 DI 框架是`Autofac`,按需修改吧,記得將例項註冊成單例模式。
public class DependencyRegistrar : IDependencyRegistrar
{
public void Register(ContainerBuilder builder, ITypeFinder typeFinder)
{
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");
...
}
...
}
}