一、背景
部门的对外接口API会经常发生变更,这在新版本的开发中尤为突出:添加新功能的接口、修改已有接口的不足、删除废弃过时的接口。
API变更通常按照“C类型”划分,由“变更提出人”给出变更需求(包含变更细节)并提交评审,评审通过后再由“变更负责人”来实施变更(对照变更细节逐个修改API头文件中的相关定义)。
由此可以看出,API变更的三个阶段中:“提出”和“评审”阶段都是技术活,而“实施”阶段完全就是体力活。兼职干这个体力活已经有一段时日,我一直琢磨着“码农不该干民工活”,于是决定让“实施”阶段automatic。
二、需求
由于API变更细节是按照“C类型”划分的,因此传统的.h头文件的组织方式粒度太大,且缺乏有效的自动化手段。只有将API同样按照“C类型”划分并存储在数据库中,才能真正做到自动化。
由上可知,系统中涉及两种数据:“API”和“API变更”。“API”是状态数据,用来呈现当前的接口内容;“API变更”是动作数据,用来表征对接口内容的修改细节。为了保证“API”和“API变更”的数据独立性,二者应该分别用两个数据库来实现。
以下重点介绍“API”数据库的设计与实现。
三、分析
API以“元素”为单位,每个“元素”包含名称、类型、模块、定义和描述。
名称:元素的名称,不允许有重复。
类型:即“C类型”,包括宏、枚举(正常,或typedef)、结构体(正常,或typedef)和函数(正常,或typedef)。通常C语言类型包括宏、typedef、枚举、结构体和函数,而typedef往往会被滥用在枚举、结构体和函数的定义中(至少我们部门有这种情况),因此这里为简化起见做了上述分类。
模块:多个元素可以同属于一个模块,概念上等价于多个接口位于同一.h头文件中。
定义:根据类型的不同而不同,如宏定义、枚举定义、结构体定义和函数定义的内容是各自不同的。
描述:对元素的解释说明,概念上等价于通常的C注释。
四、数据库设计
1、基于以上讨论,在数据库中的表设计的推演过程如下:
(1)首先为“元素”建立表Element作为数据库的主表:
Element=id+名称+类型+模块+定义+描述
(2)为了便于单独管理“类型”和“模块”,分别建立表Type和Module:
Type=id+类型名
Module=id+模块名
(3)根据第2步,在Element中去掉“类型”和“模块”,并分别使用外键type_id、module_id关联Type、Module:
Element=id+名称+type_id+module_id+定义+描述
(4)不同类型对应的定义结构不同,因此在Element中去掉“定义”,建立表MacroDetail、EnumDetail、StructDetail和FuncDetail,并分别用外键element_id关联Element:
Element=id+名称+type_id+module_id+描述
MacroDetail=id+是否有宏参+宏参+内容+element_id
EnumDetail=id+是否typedef+typedef原名+枚举字段+element_id
StructDetail=id+是否typedef+typedef原名+结构体字段+element_id
FuncDetail=id+是否typedef+返回类型+返回值描述+函数参数+element_id
(5)EnumDetail与“枚举字段”之间是一对多的关系,因此在EnumDetail中去掉“枚举字段”,建立表EnumField,并使用外键detail_id关联EnumDetail:
EnumDetail=id+是否typedef+typedef原名+element_id
EnumField=id+字段名+初值+字段描述+顺序id+detail_id
(6)StructDetail与“结构体字段”之间是一对多的关系,因此在StructDetail中去掉“结构体字段”,建立表StructField,并使用外键detail_id关StructDetail:
StructDetail=id+是否typedef+typedef原名+element_id
StructField=id+字段类型+字段名+字段描述+顺序id+detail_id
(7)FuncDetail与“函数参数”之间是一对多的关系,因此在FuncDetail中去掉“函数参数”,建立表FuncParameter,并使用外键detail_id关联FuncDetail:
FuncDetail=id+是否typedef+返回类型+返回值描述+element_id
FuncParameter=id+参数类型+参数名+参数描述+顺序id+detail_id
(8)综上,共有10个表,各个表的最终表结构见上面的蓝色字体部分。
2、在此选择使用MySQL数据库,在MySQL Workbench中的表关系图建模如下:
五、创建数据库的SQL
MySQL Workbench支持直接从表关系图自动生成创建数据库的SQL脚本,执行生成的SQL脚本就可以按照预定要求创建数据库。自动生成的SQL脚本非常完善,往往涵盖schema、index等大规模、复杂数据库才会用到的元素和概念,为了增强对SQL核心语法的理解,这里给出自己写的简单版本:
1. create database:
CREATE DATABASE ApiManage;
2. select database:
use ApiManage;
3. create tables:
(1) Type
CREATE TABLE Type
(
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
name VARCHAR(100) NOT NULL UNIQUE,
PRIMARY KEY(id)
);
(2) Module
CREATE TABLE Type
(
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
name VARCHAR(100) NOT NULL UNIQUE,
PRIMARY KEY(id)
);
(3) Element
CREATE TABLE Element
(
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
name VARCHAR(100) NOT NULL UNIQUE,
type_id INT UNSIGNED NOT NULL,
module_id INT UNSIGNED NOT NULL,
description TEXT NOT NULL,
PRIMARY KEY(id),
FOREIGN KEY(type_id) REFERENCES Type(id),
FOREIGN KEY(module_id) REFERENCES Module(id)
);
(4) MacroDetail
CREATE TABLE MacroDetail
(
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
has_parameter INT UNSIGNED NOT NULL,
parameter TEXT NOT NULL,
content TEXT NOT NULL,
element_id INT UNSIGNED NOT NULL,
PRIMARY KEY(id),
FOREIGN KEY(element_id) REFERENCES Element(id)
);
(5) EnumDetail
CREATE TABLE EnumDetail
(
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
is_typedef INT UNSIGNED NOT NULL,
original_name VARCHAR(100) NOT NULL UNIQUE,
element_id INT UNSIGNED NOT NULL,
PRIMARY KEY(id),
FOREIGN KEY(element_id) REFERENCES Element(id)
);
(6) StructDetail
CREATE TABLE StructDetail
(
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
is_typedef INT UNSIGNED NOT NULL,
original_name VARCHAR(100) NOT NULL UNIQUE,
element_id INT UNSIGNED NOT NULL,
PRIMARY KEY(id),
FOREIGN KEY(element_id) REFERENCES Element(id)
);
(7) FuncDetail
CREATE TABLE FuncDetail
(
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
is_typedef INT UNSIGNED NOT NULL,
return_type TEXT NOT NULL,
return_description TEXT NOT NULL,
element_id INT UNSIGNED NOT NULL,
PRIMARY KEY(id),
FOREIGN KEY(element_id) REFERENCES Element(id)
);
(8) EnumField
CREATE TABLE EnumField
(
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
name TEXT NOT NULL,
initial_value INT UNSIGNED NOT NULL,
description TEXT NOT NULL,
order_id INT UNSIGNED NOT NULL UNIQUE,
detail_id INT UNSIGNED NOT NULL,
PRIMARY KEY(id),
FOREIGN KEY(detail_id) REFERENCES EnumDetail(id)
);
(9) StructField
CREATE TABLE StructField
(
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
type TEXT NOT NULL,
name TEXT NOT NULL,
description TEXT NOT NULL,
order_id INT UNSIGNED NOT NULL UNIQUE,
detail_id INT UNSIGNED NOT NULL,
PRIMARY KEY(id),
FOREIGN KEY(detail_id) REFERENCES StructDetail(id)
);
(10) FuncParameter
CREATE TABLE FuncParameter
(
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
type TEXT NOT NULL,
name TEXT NOT NULL,
description TEXT NOT NULL,
order_id INT UNSIGNED NOT NULL UNIQUE,
detail_id INT UNSIGNED NOT NULL,
PRIMARY KEY(id),
FOREIGN KEY(detail_id) REFERENCES StructDetail(id)
);
六、访问数据库的SQL(待续)