简介:
在企业级应用的开发和运行中,配置数据是必不可少的。在以往的应用中,配置数据往往以许多不同方式保存,比如:文件、注册表以及数据库等。对配置数据的松散管理造成了在应用系统运行过程中查找、使用和迁移的不便,在项目的开发和部署过程中也需要系统开发人员考虑配置数据的逻辑。本文结合
SOA 的思想提出了一种基于适配器模式的配置模块架构,此架构总结了多种配置方案,提出了管理配置数据的统一数据结构,构建了对配置数据访问与管理的统一接口,使得配置逻辑与业务逻辑解耦合。如此,项目开发人员与业务配置人员的分工更加明确,项目开发人员在开发过程中不需要关心配置数据的存放及管理情况,将精力集中在业务逻辑实现上;而业务配置人员则可以更清楚系统的管理应用系统的配置数据。
引言及背景
在业务系统(CRM、ERP、WMS 等)中,处处都离不开配置数据。例如:系统启动时所需的启动数据和预加载信息,数据库连接信息,权限管理数据,用户信息,等等。配置数据即是业务系统能够正常运行的保证,也是业务系统存在的意义。因此,在业务系统中配置数据的重要性也就不言而喻了。这些业务数据根据其性质的不同,存储方式也往往不尽相同。常见的有
property 文件,XML 文件,数据库等。这些配置数据种类繁多,存储方式各异,在开发阶段无可避免的给系统开发人员带来了极大的麻烦,同时也对后期的维护和迁移造成了很大的影响。于是,建立一个统一配置模块,以提供对各种各样的配置数据进行管理的功能,从而为系统的开发、维护和迁移提供方便,就显得尤为迫切。
而目前在市场上,仅有 Microsoft Enterprise Library
提供了一个 Configuration Manager Block,方便了 .net 开发时配置数据的管理,而并没有成型的
Java 产品或开源库。本文通过设计一个通用的配置模块,使得开发人员可以不用关心底层配置数据的存储,而业务人员也可以方便的进行业务数据的管理。
问题与分析
在现今的企业级业务系统中,系统配置数据是必不可少的。这些配置数据总的来讲可以分为两类:
程序级配置数据,系统运行必须的配置数据。此类配置数据主要是用来做系统的底层配置,例如数据库连接字符串、Web
Service 地址等。这些配置数据通常会保存在 web.xml,注册表等地方。在开发过程中由开发人员使用,在系统上线时由系统实施人员进行配置,在运行过程中由系统管理员进行维护。在系统运行过程中的改动相对较少,除非发生
IT 架构变化才会更改此类配置;
业务级配置数据,业务相关配置数据。此类配置数据主要是用来做业务支撑的配置,例如地理位置信息、汇率以及工作流配置等。这些配置数据通常会保存在
XML 文件、关系型数据库等地方。在系统上线时由业务人员梳理需求并由实施人员进行配置,在系统的日常运维过程中业务人员可以根据业务需求动态的变更配置,这种配置数据相对于程序级配置数据的变化较频繁。
开发团队在系统开发与维护的过程中经常碰到以下两个问题:
在每一个系统的开发过程中都要遇到配置数据的读取问题,需要对程序级配置和业务级配置进行数据结构设计与逻辑代码开发。然而这些存取逻辑是类似的,不同的系统之间变化很小,这些逻辑代码在不同的系统或项目之间重复开发;
在系统运维的过程中,有时会遇到需要对某配置数据进行迁移的需求。例如系统上线时地理位置数据是以
XML 文件形式保存,在具体的业务中发现需要将其迁移到关系型数据库的表中进行保存。此时,需要将系统中跟此
XML 存取有关的所有代码找出并替换成数据库的存取代码。这无疑是很大的一个工作量,并且风险很大;
配置数据在每一个系统中是不可避免的,系统开发人员在开发过程中要写跟配置数据有关的逻辑代码。按照
SOA 的思想,逻辑模块各执其职,模块间高内聚、低耦合,业务代码不应该与配置代码耦合在一起。这样即降低了开发人员的开发效率,又使得日后的维护风险增大,不易变更。本文提出的解决方案可以很好的解决这两个问题。
设计思路
基于上述两点问题,本文提出统一配置模块架构,有两个目的:
- 抽象提取配置数据的通用逻辑,使得在开发新的业务系统时开发人员可以重用配置管理逻辑而专注于业务逻辑的实现;
- 统一的配置数据存取接口可以使得业务代码与配置逻辑解耦合,由于接口的统一,配置逻辑对业务代码来说是透明的,这样的设计使得在日后进行系统维护需要对配置数据进行迁移时,不会对业务代码造成改变。数据迁移风险大大减小。迁移过后只需对配置数据存取接口进行测试即可。
架构设计
图 1. 统一配置模块架构
- Configuration Module Core:提供配置数据的数据组织结构、配置树的构建以及索引算法;
- Configuration Manager:提供配置数据的管理策略:排序、缓存、统计、认证、安全等;
- Sorting:配置数据排序,为了加快查找速度。对配置数据的排序依赖与 Hit Rating 的数据统计,统计访问次数越多的配置节点越被优先搜索;
- Cache:配置数据的物理存储可能数据量非常大,将所有的配置数据加载到内存中并不是一个优化的方案,因此在此配置模块中只缓存常用的配置数据。缓存替换机制可以参考目前成熟的缓存技术,例如基于
FIFO,或基于使用频率等。由于对于配置数据的使用,读取是最经常的逻辑,而更新缓存往往发生频率较小。由此特点我们可以在设计更新接口时同时更新物理存储数据和缓存中的配置节点数据,以保证配置缓存的有效性;
- Statistics/Hit Rating:为缓存提供筛选机制,对每次配置节点的访问进行统计,按照访问率从大到小选择加载到缓存中的配置节点;
- Authorization:对配置信息进行权限认证。Configuration Module 可以为多个应用提供配置服务,为了避免不同应用间的非法访问,可以使用权限认证机制保证数据安全性;
- Security:对需要加密的配置数据进行加密;
- Configuration Interface:定义统一配置数据访问接口:GetValueList(String
configPath), GetKeyValuePairs(String configPath),
GetAttributeValue(String configPath, String attrName);
- Configuration Adaptor:配置适配器,针对不同的配置存储方式加载与保存配置数据;
数据结构
在介绍统一数据结构之前,我们先来总结分析一下目前业务系统中常用的配置方式:
- 注册表,基于 Windows 系统平台的软件,特别是桌面软件通常使用的配置方式;
- 文件,文本文件(ini)或者自定义 XML 文件;
- web.config、app.config、web.xml 等框架配置文件,目前是 ASP.NET
及 J2EE 常用的配置方式;
- 关系型数据库表,是业务系统中常用的配置保存方式。
综合上述 4 类配置方式,本文提出统一配置数据结构:树。下图是树形节点的结构:
图 2. 统一配置数据结构
对于注册表,文件和 XML 配置文件,很好理解,因为其本身就类似于树形结构,可以很方便的把注册表,文件和
XML 配置文件转换成树。而对于关系型数据库的二维表,则需要做一定的论证。
证明
根据上述分析,我们需要论证的命题如下:二维表(m, n)和树在一定条件下可以相互转化。
在论证之前我们需要对二维表中的关系做一下抽象,二维表中存在三种关系:
- 没有关系(每个节点是一个独立的树);
- 列与列之间是父子关系;
- 行与行之间是兄弟关系。
证明:
1.当 m=1, n=1 时,可转化成树:
图 3. 当 m=1, n=1 时
2.当 m=2, n=1 时,可转化成树:
图 4. 当 m=2, n=1 时
3.当 m=1, n=2 时,可转化成树:
图 5. 当 m=1, n=2 时
4.假设当 m=i, n=j 时,可转化成树:
图 6. 当 m=I, n=j 时
则当 m=i+1, n=j 时,多出来的行和已有的行有兄弟关系,所以所出来的数据可以作为树某一节点的兄弟分支;
当 m=i, n=j+1 时,多出来的列和已有的列有映射关系,所以所出来的数据可以作为树某些节点的子节点。
至此,我们可以看出通过某种提取规则我们完全可以将二维表抽象成树这种数据结构。这种提取规则一般不会很复杂,因为配置在二维表中的数据关系一般比较简单。
示例(以一个业务场景展示配置模块的用法及带来的好处)
假设当下需要为一个网络书城客户做一套库存管理系统,此网络书城公司有三个库存地点:北京、上海、广州。每个库存地点的快递费用不同,客服电话也不同。假设这三个库存地点都部署了一套库存管理系统,但是这三套库存管理系统中需要公用库存地点这个配置数据,我们应用统一配置模块可以抽象数据结构如下:
图 7. 示例数据结构
在系统中获取库存地点列表代码如下:
清单 1. 获取库存地点示例代码
WMSConfig wmsConfig = new WMSConfig();
List locations = wmsConfig.getValueList("InventoryLocation");
获取快递费用代码如下:
清单 2. 获取快递费用示例代码
for (int i = 0; i < locations.size();
i++) {
String charge = wmsConfig.getAttributeValue("ExpressCharge",locations.get(i));
System.out.println(locations.get(i) + " : "
+ charge);
}
假设系统中配置数据使用 XML 文件如下:
清单 3. 库存地点配置 XML
<wmsconfig>
<inventoryLoc>
<location name="北京">
<expressChange value="5" />
<hotline value="010-xxxxxxxx" />
</location>
<location name="上海">
<expressChange value="6" />
<hotline value="021-xxxxxxxx" />
</location>
<location name="广州">
<expressChange value="10" />
<hotline value="020-xxxxxxxx" />
</location>
</inventoryLoc>
</wsconfig>
如前文所述,XML 配置文件本身就类似于树形结构,则无需转换,直接读取到系统中。其适配器代码如下:
清单 4. XML 适配器示例代码
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
try {
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(new File("config.xml"));
NodeList nl = doc.getElementsByTagName("location");
int len = nl.getLength(); for (int i = 0; i < len;
i++) {
Element location = (Element) nl.item(i);
wmsConfig.setValue(
"InventoryLocation", location.getAttribute("name"));
Node expressCharge = location.getElementsByTagName(
"expressCharge").item(0);
Node hotline = location.getElementsByTagName(
"hotline").item(0);
wmsConfig.setAttributeValue(
"ExpressCharge",
location.getAttribute("name"),
expressCharge.getNodeValue());
wmsConfig.setAttributeValue(
"Hotline",
location.getAttribute("name"),
hotline.getNodeValue());
}
} catch (ParserConfigurationException e) {
e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
在系统运行过程中由于需求需要将配置数据迁移到数据库表中,如下:
图 8. 库存地点数据库结构
数据库类的配置文件是二维表,需要转换为树形结构。因此需要在适配器中完成这个转换过程。我们不需要改变任何业务代码,只需要重新编写适配器如下:
清单 5. 数据库适配器示例代码
try {
Class.forName("COM.ibm.db2.jdbc.app.DB2Driver").newInstance();
Connection conn = DriverManager.getConnection(
"jdbc:db2://localhost/50000:bookstore",
"db2admin",
"password");
PreparedStatement stat = conn.prepareStatement(
"SELECT locationname FROM inventorylocation");
ResultSet rs = stat.executeQuery(); while (rs.next())
{
wmsConfig.addValue("InventoryLocation", rs.getString(0));
}
stat.close();
stat = conn.prepareStatement("SELECT locationname,configvalue
"
+ "FROM inventorylocation,locationconfig "
+ "WHERE inventorylocation.locationid"
+ "=locationconfig.locationid " + "AND
configname=?");
stat.setString(0, "ExpressCharge");
rs = stat.executeQuery();
while (rs.next()) {
wmsConfig.addAttributeValue(
"ExpressCharge",
rs.getString(0),
rs.getString(1));
}
stat.setString(0, "Hotline");
rs = stat.executeQuery();
while (rs.next()) {
wmsConfig.addAttributeValue(
"Hotline",
rs.getString(0),
rs.getString(1));
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
结束语
本文提出了一种基于适配器模式的统一配置模块架构,和管理配置数据的统一数据结构,构建了对配置数据访问与管理的统一接口,同时结合一个示例展示了部分实现细节。读者根据本文的思想,可以使得配置逻辑与业务逻辑解耦合。如此,项目开发人员与业务配置人员的分工更加明确,项目开发人员在开发过程中不需要关心配置数据的存放及管理情况,将精力集中在业务逻辑实现上;而业务配置人员则可以更清楚系统的管理项目的配置数据
。 |