状态字段和类似的预定义值集存在反复出现的问题。
让我们举一个具有订单实体的订购系统为例,该实体的状态可能是"新建"、"进行中"、"已支付"等。
问题:
订单的状态需要
- 存储(在数据库中)
- 已处理(在后端)
- 通信(到 Web 服务 API 中的前端)
如何在保持的同时进行这三项活动:
- 保留状态的含义。
- 高效存储。
以下是一些示例实现及其优缺点:
1- 状态表
- 数据库将包含一个带有 id、name 的状态表
-
订单表引用状态的 ID。
CREATE TABLE `status` ( `id` INT NOT NULL, `name` VARCHAR(45) NOT NULL, PRIMARY KEY (`id`)); CREATE TABLE IF NOT EXISTS `order` ( `id` INT NOT NULL AUTOINCREMENT, `status_id` INT NOT NULL, PRIMARY KEY (`id`), INDEX `order_status_idx` (`status` ASC), CONSTRAINT `order_status_id` FOREIGN KEY (`status_id`) REFERENCES `status` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION);
-
后端代码有一个枚举,该枚举在代码中赋予这些预定义整数的含义
enum Status { PAID = 7; }; // While processing as action ... order.status = Status::PAID;
-
Web 服务 API 将返回状态编号
order: { id: 1, status_id: 7 }
-
前端代码具有类似的枚举,该枚举在代码中赋予这些预定义整数的含义。(如后端代码)
-
优点:
- 数据库已明确定义和规范化
-
缺点:状态
- 号和含义之间的映射在三个地方完成,这为人为错误和定义特定状态号码的含义不一致提供了空间。
- 从 API 返回的数据不是描述性的,因为它不包含
status_id: 7
的含义,因此status_id: 7
不提供具体的含义
2-状态枚举
-
在数据库中,订单表将包含 ENUM 类型的状态列,其中包含预定义的状态。
CREATE TABLE IF NOT EXISTS `order` ( `id` INT NOT NULL AUTOINCREMENT, `status` ENUM('PAID') NULL, PRIMARY KEY (`id`));
-
后端代码具有常量值作为预定义状态的代码工件
enum Status { PAID = 'PAID' };
或
class Status { public: static const string PAID = PAID; };
用途如下
// While processing as action ... order.status = Status::PAID;
-
Web 服务 API 将返回状态常量
order: { id: 1, status: 'PAID' }
-
前端代码将具有预定义状态常量的类似构造。(如后端代码)
-
优点:
- 数据库已明确定义和规范化
- 从 API 返回的数据是描述性的,并提供所需的含义。
- 使用的状态常量已经包含其含义,从而减少了出错的机会。
-
缺点:
- 对数据库中的列使用 ENUM 类型有其局限性。稍后使用 ALTER 命令向该枚举添加新的状态常量非常昂贵,特别是对于像表这样的大型表
order
。
- 对数据库中的列使用 ENUM 类型有其局限性。稍后使用 ALTER 命令向该枚举添加新的状态常量非常昂贵,特别是对于像表这样的大型表
3-我提出的解决方案:
-
数据库将包含一个状态表,其中包含一个名为
key
的字段,其类型为字符串,该字段是此表的主键。CREATE TABLE `status` ( `key` VARCHAR(45) NOT NULL, PRIMARY KEY (`key`));
-
订单表将包含一个名为
status
的字段,该字段的类型为 string,该字段引用status
表的key
字段。CREATE TABLE IF NOT EXISTS `order` ( `id` INT NOT NULL AUTOINCREMENT, `status` VARCHAR(45) NOT NULL, PRIMARY KEY (`id`), INDEX `order_status_idx` (`status` ASC), CONSTRAINT `order_status` FOREIGN KEY (`status`) REFERENCES `status` (`key`) ON DELETE NO ACTION ON UPDATE NO ACTION);
-
后端代码具有常量值作为预定义状态的代码工件
enum Status { PAID = 'PAID' };
或
class Status { public: static const string PAID = PAID; };
用途如下
// While processing as action ... order.status = Status::PAID;
-
Web 服务 API 将返回状态常量
order: { id: 1, status: 'PAID' }
-
前端代码将具有预定义状态常量的类似构造。(如后端代码)
-
优点:
- 数据库已明确定义和规范化
- 从 API 返回的数据是描述性的,并提供所需的含义。
- 使用的状态常量已经包含其含义,从而减少了出错的机会。
- 使用状态表中的 INSERT 命令添加新的状态常量很简单。
-
缺点:
- ???
我想知道这是否是一个可行的解决方案,或者对于这个反复出现的问题有更好的解决方案。
请包括为什么建议的解决方案不好的原因以及为什么你更好的解决方案更好
谢谢。
这是我解决这个问题的方法:
- 我在
orders
表中添加一个类型为string
的列status
。 - 定义类中所有状态的常量,以便您可以轻松引用它们。
- 在创建订单时制定验证规则,说明状态值位于您之前定义的唯一允许值中。
这使得只需编辑代码库即可轻松添加新状态,并且检索到的状态值仍然是字符串(描述性)。
我希望这能回答你的问题。
我建议这样做:
- 存储在数据库中作为状态(无符号的tinyint,char(5))。
- Id 必须是 2:1,2,4,8 的幂,...
- 在后端代码中,常量名称必须人性化,但值 -- int:
const PAID = 2
- 在后端,您不应该直接使用 consts,而是使用状态类对象,它将包含一些方法,如
value
和name
。 - 此类的测试将检查它的所有值是否都在数据库中,并且所有数据库的值是否都包含在类中。
人为错误的空间
为避免人为错误而发明的测试。
状态通常不是那么复杂,也没有那么多值来弄乱它们。
枚举是邪恶的。 http://komlenic.com/244/8-reasons-why-mysqls-enum-data-type-is-evil/
关于您的提案:
数据库已明确定义和规范化
不。它是非规范化的。
从 API 返回的数据是描述性的,并提供所需的含义。
你总是可以使用包装器,它进入状态表来获取人名。
使用的状态常量已经包含其含义,从而减少了出错的机会。
康斯特的名字是人类的,价值观是本德斯的。
使用状态表中的 INSERT 命令添加新的状态常量很简单。
在第一个和我的解决方案中相同。