企业配置管理






4.36/5 (9投票s)
通过数据库配置 .NET 应用程序
背景
本文详细介绍了在企业应用程序设置中,使用通用配置存储进行配置管理。
任何大型企业应用程序都有许多活动的组件。它们都需要进行配置才能使应用程序正常运行。随着应用程序规模的增加或为了实现可伸缩性,相同的配置必须在不同的应用程序中重复。对于大多数应用程序,一旦配置发生更改,应用程序就需要重新启动。
本文更像是一种讨论,我将尝试阐述我的思考过程。
需要注意的是,本文的目的不是提供企业配置的解决方案,而是让读者了解可能遇到的挑战以及一套可能的解决方案,应用程序的设计者届时将有选择的余地。
引言
本文探索了一种技术,在该技术中,我们将开发一种配置管理机制,其中
- 有一个集中的位置用于存放通用配置元素
- 用于存放实例特定配置的机制
- 一种机制(如果可能,一个框架)用于实例化类,例如 Web 服务、WCF 客户端或服务器以及其他使用 app.config / web.config 配置的类
- 用于缓存存储在通用配置存储中的配置的机制。
- 用于读取配置的通用代码库
- 如何部署此通用代码库。
让我们尝试为我们的每个设计目标提供选项/想法,看看还需要学习什么才能实现这些设计目标。
这样做的工作量很大,因此仅当您拥有一个大型且包含许多活动组件的应用程序时,才应尝试这种技术。如果您认为您的应用程序将来会发展成一个大型系统,请阅读本文中关于**今天就规划可伸缩性**的部分。
如果下一节非常令人困惑,请不要担心,我们将深入了解更多细节。
为什么要这样做
本文是为安装在生态系统中有多个实例的应用程序而写的。只有当您需要负载均衡和跨多个站点重复配置时,它才会有帮助。如果您使用此应用程序,您将不必在多个位置更新配置。
如果您有一个只有几个组件的简单 Web 应用程序或服务,那么这种技术不适合您。
想法
我们将要面对的事情的挑战和想法 - 本节详细介绍了如何解决上述要求的想法。我们尚未进入解决方案模式。我们所做的只是收集我们将要面临的所有挑战。
- 通用配置元素的集中位置 - 这相对简单,我们应该创建一个单独的数据库来存放这些配置元素。一旦离开构思阶段,我们将为这项任务详细介绍一个通用模式。
- 用于存放实例特定配置的机制 - 这有点可辩论,我们可以考虑两种方法:一种是修改中心数据库并添加一些额外的字段来标识配置是全局的还是针对中央服务器的;另一种是将实例特定信息保存在常规的 app.config / web.config 文件中。如果将其保存在 .config 文件中,那么对于大型安装,我们需要一个安装实用程序。我更倾向于使用中心数据库来完成此任务。
- 用于加载配置的机制 - 您将面临两种配置项:一种由您的业务逻辑定义,例如您的 .NET 组件所需的 SMTP 服务器名称,或者可能是最大网格大小。这些将很简单,因为您不必读取键值对或从自定义配置节读取,而需要从数据库读取。下一个是 .NET 提供的组件,例如 Web 服务代理类或 WCF 配置。这些将更具挑战性。我们需要一个中心位置来获取这些类的实例,这些类会创建一个实例,该实例会在返回实例之前配置这些元素。例如,如果我们想要一个 Web 服务代理的实例,我们需要设置 Web 服务器的 URL,并且这些必须在自定义位置完成。
为了做到这一点,我们需要提供一组包装器工厂,并且只使用它们来创建对象的实例。此工厂方法的示例可以是WebServiceProxyClass GetInstanceOfWebServiceProxyClass(string webServiceId) { // Create an instance of the proxy // Get values from database for the url, Soap format etc... // Set values for some optional parameters such as time outs... // return the instance }
这个包装器工厂本质上是一个工厂。
- 用于缓存配置的机制 - 为了获得最大的吞吐量,配置需要被配置的客户端缓存,而不是被配置提供者缓存。这意味着,如果我们的应用程序需要一个 Windows 服务和一个 Web 站点,那么这两个应用程序都必须缓存此配置,并提供一个通用的 API 来访问此配置元素。此缓存将提供更好的性能,但它不是明确的要求。一旦缓存到位,诸如缓存过期和相关策略之类的事情就会起作用。在本文中,我们将使用 Microsoft Enterprise Library 来支持所需的缓存。有关此块的详细信息,可以从这里获得。
- 用于读取配置的通用代码库 - 一旦您实际规划使用这种集中的配置管理,就需要配置从一个通用类读取,这样,当读取配置的机制发生变化时,我们就不会措手不及。这个想法是拥有一个代码库,我们在其中使用类似
ConfigManager.GetConfig["KeyName"]
或ConfigManager.MyTestKey
的代码。 - 我们如何部署此通用代码库 - 最后一个问题听起来可能不相关,但它非常重要,我们在做决策。有两种方法可以部署通用代码库,我们可以部署。一种是将其作为进程内 DLL - 只要只有一个使用多个组件的应用程序将使用此配置,那么这样做就可以。另一方面,如果您想使此配置管理成为企业应用程序(请注意复数)的配置工具怎么办?如果多个应用程序将从该应用程序获取配置,那么对于许多企业应用程序来说,为了保持一致性,SMTP 服务器或网格中的行数等配置数据可能会存储在中央位置。在这种情况下,我们需要将我们的小型实用程序升级为企业配置服务。我们需要问自己这是否是过度的,或者我们将来是否会遇到这种情况,如果我们今天想保持这种部署选项的开放性,而现在只做最少的事情怎么办?嗯,WCF 可以为您效劳。这篇关于WCF null proxy 的文章允许在进程中执行 WCF 请求,从而在今天快速工作,但又可在未来扩展。
所以,让我们总结一下为了继续阅读本文,我们需要学习以下内容
- 。NET 应用程序是如何配置的
- 工厂和包装器设计模式(我们的目的是学习包装器,尽管这里的代码本质上是工厂)
- Enterprise Library 缓存块,您可以选择阅读其他缓存提供程序或自己编写
- WCF 和 WCF 部署选项
今天就规划可伸缩性 - 您的业务案例可能今天并不支持如此大规模的方法,但您需要保护自己免受未来的变化。要做到这一点,您今天只需要做一件事,那就是构建一个通用类,您将通过该类访问所有配置。这意味着您的业务类中不会出现 ConfigurationManager.ApplicationConfiguration["SampleKey"]
这样的代码,而应该在某些自定义配置类中。但是,您将不会受到 WCF 或 Web 服务等默认配置提供程序的保护。
逻辑架构
考虑到以上所有因素,让我们深入探讨此应用程序的逻辑架构。我们将尝试以纯粹性为目标进行设计,并将任何与用例相关的任务降到最低。我们将定义各种组件,并尝试将它们映射到我们上面设定的六个标准。
应用程序 A / B - 这是给定进程空间中的任何 .NET 可执行程序集。它可以是 Windows 服务、Web 站点或任何独立执行的组件。
应用程序有许多需要使用配置的**组件**。应用程序具有**配置缓存**,此组件的工作是缓存应用程序的配置项。这将带来更好的性能。此缓存将调用配置 API 来获取配置值,它将提供添加站点特定信息的必要逻辑。
应用程序还有一个**包装器工厂**。这是一个通用组件,用于创建需要使用配置缓存的对象的实例。一些例子是创建数据库连接、WCF 或 Web 服务代理类、WCF 托管等。其思想是我们将使用此类来获取所需的实例。
例如,SqlConnection sqlConnection = WrapperFactory.GetConnection(Id);
配置访问 API - 这是一个独立的组件,将为我们提供配置值。这将是配置缓存将访问的数据存储。这是现在取代 .NET 中 ConfigurationManager
类的位置,而我们之所以将缓存放在这里,只是为了确保我们有一个高性能的应用程序。
配置数据库 - 这是所有配置的实际数据存储,并且正在取代 web.config 或 app.config。它将包含用于存储全局和站点特定信息的模式。
设计
通常,当我尝试设计某项内容时,我心中有两种方法:一种是我会提出 E-R 模型或数据库模型并在此基础上进行构建。在其他时候,我尝试提出对象模型/域模型并从中开始。在这种情况下,我将提出对象/域模型,原因是此应用程序的性质是面向服务的,而不是数据存储。
最重要的接口,也是此应用程序中其他事物将被驱动的接口是配置访问 API。数据库模式将很容易地由此流出。缓存将始终是轻而易举的事情,因为它将是此 API 的包装器,以确保快速访问,而包装器工厂虽然很有挑战性,但并不是此应用程序的核心。包装器工厂仅用于提供某些类的实例,这些类本应...
配置 API 接口 - 我们期望从这个配置 API 获得什么?
我们期望将站点名称和应用程序名称传递给它,它将返回给我们
- 所有跨站点和应用程序的全局配置
- 所有跨应用程序的全局配置
- 该站点的所有本地配置
- 该应用程序的所有全局配置
最后,作为此信息的使用者,我并不关心某些配置是全局的还是与我相关的,我只想知道影响我的所有配置。
这些配置信息将是什么?
- 简单的键值对
- 键值对,这些键值对理想情况下存储在自定义配置节的一部分,但已被分解。当然,我们需要一种机制来将它们组合在一起,同时保持通用性。
所以,本质上,整个配置可以被视为需要逻辑分组的键 - 值对。逻辑组是一堆从 API 角度任意分组的键值对。然而,此信息的用户需要这种分组来正确地实例化配置的消费者。
考虑到这一点,让我们尝试定义 Entity
对象(或值对象、数据传输对象或 DTO,无论您如何称呼它)来捕获此信息,以及接口上用于访问 DTO 的方法。
为了实现这一点,让我们描述一组将在我们的对象模型中使用的定义。
- 站点 - 站点是在单独的应用程序域中在机器上执行的应用程序的实例。
- 应用程序 - 应用程序是在进程空间中托管的可执行程序集。
- 机器 - 机器是一个物理对象,一个或多个应用程序在其上执行。
覆盖标准
在系统机器中,可以为任何可执行程序集定义应用程序和站点级别的配置,在发生冲突的情况下,机器级别的配置将被应用程序级别覆盖,应用程序级别将被站点级别覆盖。考虑以下示例
机器配置
键 | 值 |
Key1 | M1 |
Key2 | M2 |
Key3 | M3 |
Key4 | M4 |
应用程序配置
键 | 值 |
Key1 | A1 |
Key2 | A2 |
Key5 | A2 |
Key6 | A3 |
站点配置
键 | 值 |
Key1 | S1 |
Key3 | S2 |
Key5 | S3 |
Key7 | S4 |
Key8 | S5 |
在应用程序中可用的键是
键 | 覆盖顺序 | 值 |
Key1 | M1 <A1 <S1 => 站点覆盖机器和应用程序 | S1 |
Key2 | M2 <A2 => 应用程序覆盖机器 | A2 |
Key3 | M3 < S2 => 站点覆盖机器 | S2 |
Key4 | M4 | M4 |
Key5 | A2<S3 => 站点覆盖应用程序 | S3 |
Key6 | A3 | A3 |
Key7 | S4 | S4 |
Key8 | S5 | S5 |
设计应支持自定义覆盖标准,以便将来的更改更简单。
服务器端对象模型
类详细信息
ConfigurationElement
-此类本质上包含一对键值对,它们构成了各种配置属性的值。实体还包含PropertyName
属性,该属性可用于标识正在设置的属性。客户端可以使用反射/内省或索引器(如果可能)来使用它。OverrideType
属性可用于定义是什么赋予了该字段其值。ConfigurationGroupData
-此类用于逻辑上表示与站点某个组件相关联的键组。例如,可以是给定的 WCF 组件、一个webservice
、一个数据库连接string
、appSettings
中的一些键等等。ConfigurationEntity
-此类包含站点所有配置。站点由站点 ID 标识。此站点 ID 是应用程序和机器的唯一组合。
这些类详细信息/对象模型构成了我们应用程序的基本数据合同。
下一节包含从数据存储获取配置的接口。
根据站点(即机器+应用程序名称)的描述,我们返回配置信息。数据根据覆盖标准进行覆盖。如果未指定机器或未找到机器,则应返回给定应用程序的配置。
所以,原则上,我们获取站点所有键(可以是机器级别、应用程序级别或站点级别键),并检查哪个覆盖哪个,创建一个 ConfgurationEntity
并将其返回。
IConfigurationStore
原则上构成了我们工作的操作合同。
数据库设计
本节详细介绍应用程序的数据库模式。我们不会深入研究每个列及其用途。我相信读者了解用于软删除的 IsDeleted
标志,或者 CreationDateTime
和 LastUpdateDateTime
的用途。
- 部署 - 此表包含各种部署单元的名称,例如应用程序、机器或机器与应用程序的组合(即站点)的定义。
一些示例
Machine Name Application Name 注释 Server1
当在任何应用程序的上下文中请求 Server1
时,都会返回此配置。Application1
当在任何应用程序的上下文中请求 Application1
时,都会返回此配置。Server2
Application1
当 Server2
请求Application1
时,会返回此配置。此站点的键会覆盖Application1
中的键。 - 组件 - 组件是正在执行的应用程序中任何独立的逻辑分组的键集合。例如,WCF 相关键、Web 服务相关键、DB 相关键等。我们给组件类型一个松散的定义——这可能是 WCF,而组件名称是组件类型的实例,例如,用于 DB 连接的身份服务的 WCF 服务器键,用于报表服务器的键。组件类型告诉配置的确切领域,而实例是具体的部署。它类似于类与类的实例。
- 键 - 这是与组件关联的实际键集合。例如,对于 WCF 实例,它可能包含 URL、超时和其他项。对于每个键,我们还存储一个属性。
- KeyValues - 这些是给定键的实际值。它们可以在应用程序和站点级别被覆盖。
- ComponentDeployment - 这是部署中组件的逻辑分组。例如,Web 应用程序包含一组 WCF 部署、一个数据库部署以及一些 HTTP 运行时详细信息。
上述模式相当通用,但为了提高操作效率,您可以创建针对各种实体的结构化表结构。
覆盖算法
作为设计的一部分,我们在此还提供了覆盖算法。这只是委托的一种可能实现方式,框架的消费者可以选择实现自己的委托。
public bool IsOverrideImplementation (OverrideType oldValue, OverrideType newValue)
{
//Return true if new value can override old value.
return newValue > oldValue;
}
这标志着服务器端的结束,让我们稍微讨论一下客户端方面的内容,其中包括键的缓存以及实例化类所需的一系列工厂。
缓存
ConfigurationEntity
必须在客户端应用程序中缓存。我们需要一种缓存刷新机制。强烈建议在继续之前,我们应该阅读本文。
我们之所以将其作为缓存工具而不是 .NET 缓存来推荐,是因为它允许在任何类型的 .NET 应用程序中进行缓存,而不仅仅是 Web 应用程序。此外,此工具还允许进行过期清除和其他功能。
工厂
在客户端,必须创建一整套工厂。这些将用于实例化元素。为什么我们需要这些?
考虑当我们希望使用 Web 服务时,我们创建一个代理的实例
Service = new Service();
内部,此构造函数将执行几项操作
- 分配用于访问 Web 服务的 URL。
- 分配请求的格式,例如 Soap 或 Http Post。
- 设置超时和其他许多属性。
此工厂的工作将是创建一个实例并从缓存中分配这些值。
如果应用程序中有太多受此配置影响的类类型和类,我们可以使用抽象工厂模式而不是工厂模式。
客户端 .config 文件内容
我们应该理解并承认,并非所有内容都可以移至数据库结构。我们至少需要维护一个 WCF 端点来访问进程外配置存储的配置存储。诸如 httpModules
的详细信息、调试/发布模式和其他需要早期加载的设置需要保留在 .config 文件中。
需要一个序列图
让我们尝试在下面的序列图中理解某个应用程序如何配置的总体流程。
这里没有什么太多可说的。任何需要实例化的类都会请求包装器工厂来创建实例。包装器工厂拥有创建实例的所有逻辑,除了配置值。这些值从缓存中获取。缓存从 WCF API 中填充其值。我们一次性填充整个缓存。
此时,我打算停止,因为本文太长了。将来某个时候,我将分享示例实现。
最后,如果您觉得本文有帮助,请评价它...