前言
配置是咱们必不可少的性能,咱们在开发中,常常会遇到须要获取配置信息的需要,那么如何能力优雅的获取配置信息?
咱们心愿新的配置:
- 反对强类型
- 配置变更后告诉
- 学习难度低
疾速入门
依据应用场景咱们将配置分为本地配置以及近程配置,上面咱们就来看一下本地配置与近程配置是如何来应用的?
- 装置.Net 6.0
本地配置
- 新建ASP.NET Core 空我的项目
Assignment.MasaConfiguration
,并装置Masa.Contrib.Configuration
dotnet new web -o Assignment.MasaConfigurationcd Assignment.MasaConfigurationdotnet add package Masa.Contrib.Configuration --version 0.6.0-preview.7
- 新建类
AppConfig
、ConnectionStrings
,用于存储数据库配置
/// <summary>/// 利用配置类/// </summary>public class AppConfig : LocalMasaConfigurationOptions{ public ConnectionStrings ConnectionStrings { get; set; }}public class ConnectionStrings{ public string DefaultConnection { get; set; }}
- 批改文件
appsettings.json
{ "AppConfig": { "ConnectionStrings": { "DefaultConnection": "server=localhost;uid=sa;pwd=P@ssw0rd;database=identity" } }}
- 注册
MasaConfiguration
,批改类Program
builder.AddMasaConfiguration();
- 如何应用?批改类
Program
app.MapGet("/AppConfig", (IOptions<AppConfig> appConfig){ return appConfig.Value.ConnectionStrings.DefaultConnection);});
如果心愿监听配置变更事件,则可应用IOptionsMonitor<TOptions>的OnChange办法
近程配置
目前咱们近程配置的能力仅实现了MASA DCC(Dcc: Distributed Configuration Center 是一个以DDD为指导思想、应用.Net6.0开发的分布式配置核心), 上面就让咱们看看如何来应用它
- 选中
Assignment.MasaConfiguration
,并装置Masa.Contrib.Configuration.ConfigurationApi.Dcc
dotnet add package Masa.Contrib.Configuration.ConfigurationApi.Dcc --version 0.6.0-preview.7
- 批改
appsettings.json
{ //Dcc配置,扩大Configuration能力,反对近程配置 "DccOptions": { "ManageServiceAddress ": "http://localhost:8890", "RedisOptions": { "Servers": [ { "Host": "localhost", "Port": 8889 } ], "DefaultDatabase": 0, "Password": "" } }}
- 新建类
RedisOptions
, 用于配置业务我的项目中应用的缓存地址
public class RedisOptions : ConfigurationApiMasaConfigurationOptions{ public string Host { get; set; } public int Port { get; set; } public string Password { get; set; } public int DefaultDatabase { get; set; }}
- 批改类
Program
var app = builder.AddMasaConfiguration(configurationBuilder =>{ configurationBuilder.UseDcc();}).Build();
- 如何应用?
// 举荐应用,通过IOptions<TOptions>获取配置,反对强类型app.MapGet("/AppConfig", (IOptions<RedisOptions> options){ return options.Value.Host;});
进阶
到目前为止,咱们曾经学会了如何应用Masa提供的配置,但只有理解原理,咱们才敢在我的项目中大胆的用起来,呈现问题后能力疾速的定位并解决问题,上面咱们就来深刻理解下
分类
依据应用场景咱们将配置划分为:
- 本地配置(配置存储在本地配置文件中,前期配置变更不变)
- 近程配置(配置在近程配置核心、例如MASA DCC、Apollo、其它配置核心)
IConfiguration构造
在应用MasaConfiguration后,IConfiguration的文件构造变更为:
IConfiguration├── Local 本地节点(固定)│ ├── Platforms 自定义配置│ ├── ├── Name 参数├── ConfigurationAPI 近程节点(固定)│ ├── AppId 替换为你的AppId│ ├── AppId ├── Platforms 自定义节点│ ├── AppId ├── Platforms ├── Name 参数
除了以下配置源以及配置的提供者提供的配置除外,其余的配置会迁徙到Local
节点下
配置源
- CommandLineConfigurationSource
- EnvironmentVariablesConfigurationSource
- KeyPerFileConfigurationSource
- MemoryConfigurationSource
配置提供者
- CommandLineConfigurationProvider
- EnvironmentVariablesConfigurationProvider
- KeyPerFileConfigurationProvider
- MemoryConfigurationProvider
全局配置
MasaConfiguration中提供了全局配置的性能,并默认反对AppId
、Environment
、Cluster
- 优先级
获取参数值的优先级为:
自定义全局配置 > 从IConfiguration中获取(反对命令、环境变量、配置文件) > 约定配置
- 自定义全局配置
service.Configure<MasaAppConfigureOptions>(options => { options.AppId = "Replace-With-Your-AppId"; options.Environment = "Replace-With-Your-Environment"; options.Cluster = "Replace-With-Your-Cluster"; options.TryAdd("Replace-With-Your-ConfigKey", "Replace-With-Your-ConfigValue");// 自定义全局配置键、值})
- IConfiguration中获取
当未指定配置的值时,将会从配置中获取失去配置的值,默认配置与Key的关系为:
AppId
:AppId
Environment
:ASPNETCORE_ENVIRONMENT
Cluster
:Cluster
当命令行与环境变量获取参数失败后,则会尝试从配置文件依据配置的Key获取对应的值
- 约定默认值
当未自定义配置,且无奈从IConfiguration中获取到绝对应参数的配置后,咱们将依据约定好的规定生成对应的值
AppId
: 启动程序名.Replace(".", "-")Environment
:Production
Cluster
:Default
配置映射
在疾速入门的例子中,看似很简略就能够通过IOptions<TOptions>
获取到AppConfig
的配置信息以及Dcc
中配置的Redis
信息,这所有是如何做到的呢?
在MasaConfiguration中提供了两种映射形式,用来映射配置与类的对应关系,别离是:主动映射、手动映射。
- 主动映射
分为本地配置以及近程配置的主动映射
- 本地配置: 由
Masa.Contrib.Configuration
提供 - 近程配置
- Dcc: 由
Masa.Contrib.Configuration.ConfigurationApi.Dcc
提供
1.1 当配置存储在本地时,则将对应的配置类继承LocalMasaConfigurationOptions
// <summary>/// 利用配置类/// </summary>public class AppConfig : LocalMasaConfigurationOptions{ // /// <summary> // /// 如果以后配置挂载在根节点(一级节点)时,则无需重载,如果挂载在二级节点时,则须要重载ParentSection并赋值为一级节点名 // /// 根节点名:默认为一级节点,可不写,格局:一级节点:二级节点:三级节点…… // /// </summary> // [JsonIgnore] // public override string? ParentSection => null; // /// <summary> // /// 如果类名与节点名保持一致,则可疏忽不写,否则重写`Section`并赋值为节点名 // /// </summary> // [JsonIgnore] // public override string? Section => "RabbitMq"; public ConnectionStrings ConnectionStrings { get; set; }}public class ConnectionStrings{ public string DefaultConnection { get; set; }}
当配置中的参数间接平铺挂载根节点下,而不是挂载到跟节点下的某个指定节点时,ParentSection
无需重载,Section
须要重载并赋值为空字符串
1.2 当配置存储在Dcc,则将对应的配置类继承ConfigurationApiMasaConfigurationOptions
public class RedisOptions : ConfigurationApiMasaConfigurationOptions{ /// <summary> /// 配置所属的AppId,当AppId与默认AppId统一时,可疏忽 /// </summary> // public virtual string AppId { get; } /// <summary> /// Dcc的配置对象名称,当配置对象名称与类名统一时,可疏忽 /// </summary> // public virtual string? ObjectName { get; } public string Host { get; set; } public int Port { get; set; } public string Password { get; set; } public int DefaultDatabase { get; set; }}
- 手动映射
尽管主动映射的形式很简略,也很不便,但总是有一些场景使得咱们无奈通过主动映射来做,那如何手动指定映射关系呢?
为了不便大家了解,手动映射依然应用AppConfig
以及Redis
来举例
builder.AddMasaConfiguration(configurationBuilder =>{ configurationBuilder.UseDcc();//应用Dcc 扩大Configuration能力,反对近程配置 configurationBuilder.UseMasaOptions(options => { options.MappingLocal<AppConfig>("AppConfig");//其中参数"AppConfig"可不写(当类与节点名称统一时可疏忽) options.MappingConfigurationApi<RedisOptions>("{替换为Dcc中配置所属的AppId}", "{配置对象名称}");//其中配置对象名称可不写(当配置对象名与类名统一时可疏忽) });});
Dcc配置
残缺的Dcc配置如下:
{ "DccOptions": { "ManageServiceAddress ": "http://localhost:8890", "RedisOptions": { "Servers": [ { "Host": "localhost", "Port": 8889 } ], "DefaultDatabase": 0, "Password": "" }, "AppId": "Replace-With-Your-AppId", "Environment": "Development", "ConfigObjects": [ "Platforms" ], "Secret": "", "Cluster": "Default", "ExpandSections" : [ { "AppId": "Replace-With-Your-AppId", "Environment": "Development", "ConfigObjects": [ "Platforms" ], "Secret": "", "Cluster": "Default", } ], "PublicId": "Replace-With-Your-Public-AppId", "PublicSecret": "Replace-With-Your-Public-AppId-Secret" }}
- ManageServiceAddress: 用于更新近程配置应用
- RedisOptions(必填):Dcc会在Redis中存储配置的正本,此处是存储Dcc配置的的Redis地址
- AppId:我的项目中须要获取配置的AppId,也被称为Dcc的默认AppId,当未赋值时从全局配置中获取
- Environment:我的项目中须要获取配置的环境信息,当未赋值时从全局配置中获取
- ConfigObjects:我的项目中须要应用的配置对象名称,未赋值时默认获取以后环境、以后集群、以后AppId下的全副配置对象
- Secret:秘钥,用于更新近程配置,每个AppId有一个秘钥,非必填(不可应用更新近程配置的能力)
- Cluster:须要加载配置的集群,前面咱们简称为Dcc的默认集群,未赋值时从全局配置中获取
- PublicId:Dcc中公共配置的AppId,默认:public-$Config
- PublicSecret:Dcc中公共配置的AppId的秘钥
- ExpandSections:扩大配置的汇合,实用于以后利用须要获取多个AppId下的配置时应用,其中AppId为必填项、Environment、Cluster为非必填项,当不存在时将与Dcc默认环境、集群统一
扩大其它的配置核心
下面提到了目前的近程配置能力仅反对Dcc,那如果我心愿接入本人开发的配置核心或者其它更优良的配置核心须要接入如何做?
以Apollo
为例:
- 新建类库
Masa.Contrib.Configuration.ConfigurationApi.Apollo
- 新建
ApolloConfigurationRepository
并实现类AbstractConfigurationRepository
internal class ApolloConfigurationRepository : AbstractConfigurationRepository{ private readonly IConfigurationApiClient _client; public override SectionTypes SectionType => SectionTypes.ConfigurationAPI; public DccConfigurationRepository( IConfigurationApiClient client, ILoggerFactory loggerFactory) : base(loggerFactory) { _client = client; //todo: 借助 IConfigurationApiClient 获取须要挂载到近程节点的配置信息并监听配置变动 // 当配置变更时触发FireRepositoryChange(SectionType, Load()); } public override Properties Load() { //todo: 返回以后挂载到近程节点的配置信息 }}
- 新建类
ConfigurationApiClient
,为ConfigurationApi
提供获取根底配置的能力
public class ConfigurationApiClient : IConfigurationApiClient{ public Task<(string Raw, ConfigurationTypes ConfigurationType)> GetRawAsync(string configObject, Action<string>? valueChanged = null) { throw new NotImplementedException(); } public Task<(string Raw, ConfigurationTypes ConfigurationType)> GetRawAsync(string environment, string cluster, string appId, string configObject, Action<string>? valueChanged = null) { throw new NotImplementedException(); } public Task<T> GetAsync<T>(string configObject, Action<T>? valueChanged = null); { throw new NotImplementedException(); } public Task<T> GetAsync<T>(string environment, string cluster, string appId, string configObject, Action<T>? valueChanged = null); { throw new NotImplementedException(); } public Task<dynamic> GetDynamicAsync(string environment, string cluster, string appId, string configObject, Action<dynamic> valueChanged) { throw new NotImplementedException(); } public Task<dynamic> GetDynamicAsync(string key) { throw new NotImplementedException(); }}
- 新建类
ConfigurationApiManage
,为ConfigurationApi
提供治理配置的能力
public class ConfigurationApiManage : IConfigurationApiManage{ // 通过治理端初始化AppId下的近程配置 public Task InitializeAsync(string environment, string cluster, string appId, Dictionary<string, string> configObjects) { throw new NotImplementedException(); } // 通过治理端更新指定配置的信息 public Task UpdateAsync(string environment, string cluster, string appId, string configObject, object value) { throw new NotImplementedException(); }}
- 新建
ConfigurationApiMasaConfigurationOptions
类,并继承MasaConfigurationOptions
咱们心愿其它自定义配置也能依据约定实现主动映射,咱们也分明不同的配置核心中存储配置的名称是不一样的,例如在Apollo
中配置对象名称叫做命名空间,因而为了不便开发人员能够应用起来更不便,咱们倡议不同的配置核心能够有本人专属的属性,比方Apollo
的Namespace
,以此来升高开发人员的学习老本
public abstract class ConfigurationApiMasaConfigurationOptions : MasaConfigurationOptions{ /// <summary> /// The name of the parent section, if it is empty, it will be mounted under SectionType, otherwise it will be mounted to the specified section under SectionType /// </summary> [JsonIgnore] public sealed override string? ParentSection => AppId; // public virtual string AppId => StaticConfig.AppId; /// <summary> /// The section null means same as the class name, else load from the specify section /// </summary> [JsonIgnore] public sealed override string? Section => Namespace; /// <summary> /// /// </summary> public virtual string? Namespace { get; } /// <summary> /// Configuration object name /// </summary> [JsonIgnore] public sealed override SectionTypes SectionType => SectionTypes.ConfigurationApi;}
- 选中类库
Masa.Contrib.BasicAbility.Apollo
,并新建IMasaConfigurationBuilder的扩大办法UseApollo
public static class MasaConfigurationExtensions{ public static IMasaConfigurationBuilder UseApollo(this IMasaConfigurationBuilder builder) { //todo:将IConfigurationApiClient、IConfigurationApiManage注册到到服务汇合中,并通过builder.AddRepository()增加ApolloConfigurationRepository return builder; }}
总结
如何应用MasaConfiguration?
- 新增:
builder.AddMasaConfiguration()
- 新增:
为何通过IOptions<TOptions>获取到的配置为空,但通过IConfiguration或者IMasaConfiguration依据节点能够获取到?
- 查看下是否没有绑定节点关系,如何绑定节点关系请查看问题2
- 查看节点绑定是否谬误
IConfigurationApiClient
与IConfiguration
之间有什么关系?IConfigurationApiClient
、IConfigurationApiManage
别离是治理近程Api的客户端以及治理端,与IConfiguration
相比,IConfigurationApiClient
的信息更全,每次获取配置须要像配置核心申请获取数据,而IConfiguration
是通过调用IConfigurationApiClient
将须要应用的配置对象获取并增加到IConfiguration
中,后续用户获取配置时无需向配置核心申请数据
近程配置对象更新后,
IConfiguration
中的信息会更新吗?为什么?- 会更新、近程配置更新后会通过valueChanged告诉近程配置的提供者,而后近程配置的提供者会刷新本地的近程配置并告诉
IConfiguration
从新刷新数据
- 会更新、近程配置更新后会通过valueChanged告诉近程配置的提供者,而后近程配置的提供者会刷新本地的近程配置并告诉
本章源码
Assignment08
https://github.com/zhenlei520/MasaFramework.Practice
开源地址
MASA.Framework:https://github.com/masastack/MASA.Framework
如果你对咱们的 MASA Framework 感兴趣,无论是代码奉献、应用、提 Issue,欢送分割咱们