传统 .NET 项目对接携程 Apollo 配置中心实践
可能由于 Apollo 配置中心的客户端源码一直处于更新中,导致其相关文档有些跟不上节奏,部分文档写的不规范,很容易给做对接的新手朋友造成误导。
比如,我在参考如下两个文档使用传统 .NET 客户端做接入的时候就发现了些问题。
- ctripcorp/apollo - .Net客户端使用指南
- ctripcorp/apollo.net - .Net客户端 System.Configuration.ConfigurationManager 集成
问题一:两个文档关于标识应用身份的AppId
的配置节点不一致。
问题二:第二个文档关于应用配置发布环境的Environment
配置节点的描述出现明显错误。
当然,这些问题随时都有可能被修复,若您看到文档内容与本文描述不符,请以官方文档为准。
进入本文正题。
安装依赖包
首先,在项目的基础设施层通过 NuGet 包管理器或使用如下命令添加传统 .NET 项目使用的 Client:
Install-Package Com.Ctrip.Framework.Apollo.ConfigurationManager -Version 2.0.3
配置应用标识 & 服务地址
在启动项目中,打开App.config
或Web.config
配置文件,在
节点中增加如下节点:
若部署多套 Config Service,支持多环境,请参考如下配置:
配置完成后,就可以准备在项目中使用 Apollo 客户端了。
二次封装代码
我们习惯在项目中使用第三方库的时候封装一层,这种封装是浅层的,一般都是在项目的基础设施层来做,这样其他层使用就不需要再次引入依赖包。
代码结构大致如下:
├─MyCompany.MyProject.Infrastructure # 项目基础设施层
│ │
│ └─Configuration
│ ApolloConfiguration.cs # Apollo 分布式配置项读取实现
│ ConfigurationChangeEventArgs.cs # 配置更改回调事件参数
│ IConfiguration.cs # 配置抽象接口,可基于此接口实现本地配置读取
直接贴代码。
IConfiguration
using System;
using System.Configuration;
namespace MyCompany.MyProject.Infrastructure
{
///
/// 配置抽象接口。
///
public interface IConfiguration
{
///
/// 配置更改回调事件。
///
event EventHandler ConfigChanged;
///
/// 获取配置项。
///
/// 键
/// 命名空间集合
///
string GetValue(string key, params string[] namespaces);
///
/// 获取配置项。
///
/// 值类型
/// 键
/// 命名空间集合
///
TValue GetValue(string key, params string[] namespaces);
///
/// 获取配置项,如果值为 则取参数 值。
///
/// 键
/// 默认值
/// 命名空间集合
///
string GetDefaultValue(string key, string defaultValue, params string[] namespaces);
///
/// 获取配置项,如果值为 则取参数 值。
///
/// 值类型
/// 键
/// 默认值
/// 命名空间集合
///
TValue GetDefaultValue(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 ChangedKeys => Changes.Keys;
public bool IsChanged(string key) => Changes.ContainsKey(key);
public string Namespace { get; }
public IReadOnlyDictionary Changes { get; }
public ConfigurationChangeEventArgs(string namespaceName, IReadOnlyDictionary 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 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(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(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
注册到服务容器中,我们项目使用的依赖注入框架是 Autofac,这个地方按需修改吧,注意将实例注册成单例。
参考代码:
public class DependencyRegistrar : IDependencyRegistrar
{
public void Register(ContainerBuilder builder, ITypeFinder typeFinder)
{
builder.RegisterType()
.As()
.Named("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");
...
}
...
}
}