使用字符串与布尔值来控制Mongoose文档状态?

3

你好,我想知道在保存文档的“状态”时应该采用什么更好的方法?我能想到的两种方法是使用字符串和枚举:

const proposal = new Schema({
    state: {
        type: String,
        enum: ['pending', 'approved', 'denied'],
        default: 'pending'
    }
});

或者使用布尔值:
const proposal = new Schema({
    approved: {
        type: Boolean,
        default: false
    },
    denied: {
        type: Boolean,
        default: false
    }
});

就性能和安全而言,哪一个更好?从外表上看,寻找 booleans 比查找 string 更快。


布尔值版本可能存在矛盾。 - Igal Klebanov
1个回答

6

除了搜索之外,还有一些其他需要考虑的事情,比如:条件逻辑、设置值、验证文档、文档大小和索引。

让我们回顾一下提出的两个模式,它们分别被称为proposalAproposalB。其中proposalA使用枚举,而proposalB则使用多个字段来模拟枚举:

const proposalA = new Schema({
    state: {
        type: String,
        enum: ['pending', 'approved', 'denied'],
        default: 'pending'
    }
});

const proposalB = new Schema({
    approved: {
        type: Boolean,
        default: false
    },
    denied: {
        type: Boolean,
        default: false
    }
});

假设

proposalA 表示文档状态只能是三个值中的一个:'pending','approved' 和 'denied'。

proposalB 意味着为了支持 proposalA 的假设,'pending' 状态下 'approved' 和 'denied' 都应该为false。

问题

查询、索引和修改

虽然 proposalA 使用字符串值,但任一建议的匹配都是相等检查,对于搜索查询,使用 { state : 'approved' } 或者 { approved: true }。不同之处在于 pending

  • proposalA{ state: 'pending' }
  • proposalB{ approved: false, denied: false }

假设没有其他查询参数,则需要对 proposalAstate 创建一个单索引,而对于 proposalB,则需要创建两个索引,分别对应 approveddenied,并使用 mongo 的索引交集或一个 复合索引 包含 approveddenied

将其留给索引交集的问题在于,如果查询变得更加复杂,则很难预测将使用哪个交集,这有多种原因。例如,目前虽然只有3种状态,但如果添加了新状态,则需要创建更多索引以确保查询有效。

这会导致另一个问题,每个索引都占用 mongo 服务器上的内存空间。尽管复合索引减少了此查询的索引数量,但它可能仍是一个比 proposalA 更大的索引。

关于内存大小,文档 { state: 'pending' } 大约是 { approved: false, denied: false } 的一半。虽然目前看起来微不足道,但正如先前提到的那样,如果添加更多状态或继续使用其他字段,则很容易看出文档大小也会迅速膨胀。

从程序视角返回搜索查询可以发现,proposalA 相当简单:

function getDocsFromState(state) {
  const Foo = mongoose.Model('foo');
  const query = { state }; // assuming state is a string of 'pending', 'approved', or 'denied'

  return Foo.find(query).exec(); // Promise
}

虽然需要使用一些条件代码来构建查询proposalB(可能是此逻辑的一个变体):

function getDocsFromState(state) {
  const Foo = mongoose.Model('foo');
  const query = {
    approved: state === 'approved',
    denied: state === 'denied'
  };

  return Foo.find(query).exec(); // Promise
}
proposalA的代码更加简明扼要,不需要进行更新以支持新的状态,而proposalB则需要更新。
对于状态值的更新也是同样的情况。proposalA依然简明:
function updateDocState(_id, state) {
  const Foo = mongoose.Model('foo');
  const update = { state }; // assuming state is a string of 'pending', 'approved', or 'denied'

  return Foo.update({ _id }, update).exec(); // Promise
}

尽管proposalB仍需要更多的附加逻辑:

function updateDocState(_id, state) {
  const Foo = mongoose.Model('foo');
  const update = {
    approved: state === 'approved',
    denied: state === 'denied'
  };

  return Foo.update({ _id }, update).exec(); // Promise
}

条件逻辑和验证

当使用多个字段表示每个枚举值来模拟枚举时,验证会变得有些棘手。按定义,枚举只允许存储一个值,proposalB需要使用验证来防止同时为approveddenied。这样强制执行可能会基于所使用的验证工具(native mongo vs a 3rd party lib like mongoose)限制更新方法(部分更新 vs 在内存中更新整个文档后再保存)以更新文档。

最后,我们已经看到了查询和更新文档所需的条件逻辑,但代码中可能还有其他需要使用条件逻辑的区域。proposalB在对其当前状态的文档进行检查时,任何时候都需要使用类似的条件逻辑,而proposalA只需检查枚举值即可。

简化版

我们已经看到枚举提供了内置文档验证、减少了文档和索引的大小、简化了索引策略、简化了当前和可能的未来实现代码,并且在查询方面不会造成过多性能影响,因为两种方法都使用等值检查。

希望这有所帮助!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接