我们为什么要远离数据库生成的ID?- Tugberk Ugurlu


在我们当前为团队构建SQL Server数据目录的过程中,我们正在优化我们的解耦工程工作。有一些具体的因素对我们非常重要,从根本上说,这归结为两个核心原则,我希望每个软件工程专业人士都会同意:

  • 我们不希望我们的复杂性随着我们在系统中添加更多功能而线性增长,随着我们在业务和价值信心方面的增长,这将极大地降低我们的速度。
  • 我们希望能够适应不断变化的需求和需求,以满足新客户需求,性能,新查询模型,业务模式变更等各方面的需求。换句话说,我们希望能够交换我们系统中的任何组件都有一个更合适的组件,适合当今的需求,而不是过去的需求。

这如何影响持久性?
考虑到这个高层原则,我们不希望将自己耦合到特定的数据库引擎以实现状态持久性。实际上,这意味着我们不会将持久性特定问题泄漏到我们的领域层中。我们想要实现这一目标的主要原因与我们今天对真理的看法可能使我们依赖某种数据库技术(如SQL Server)这一事实有关,但不确定这将满足未来的能力需求。
当我们想要提供一个真正可审计的系统时,就会出现这方面的具体例子。根据该特定要求,能够使用有界上下文来持久发生领域事件而不是存储当前状态(也称为事件源)更有意义。这将需要根本不同的存储需求。

服务器与数据库的ID生成
许多数据存储系统(如SQL Server)都有方法为每个记录生成唯一标识符(例如行,文档等)。自动增量键允许在将新记录插入表中时生成唯一编号。因此,每次我们要创建新记录时,数据库引擎都会自动创建主键,并且它有责任确保此键对表是唯一的。

但是,数据库技术可以为我们的领域聚合生成标识符的假设再次将我们与数据存储系统联系起来。

如果我们想要更改一个没有生成自动增量主键功能的数据存储系统,该怎么办?我们做不到。而且,每个数据存储系统具有不同的生成这些主键标识符的方式,这可能导致我们最终得到不同的基元类型。除此之外,这些类型的Key可能不适用于分布式系统。例如,当我们在SQL Server数据库中有一个生成主键的表时,我们没有一种简单的方法可以在分布式环境中水平扩展该表。

通过让领域层的消费者(即通过命令和查询与领域层通信的传输层)实现唯一的聚合标识符生成,可以克服这些问题。

这减少了环境依赖性,这反过来又让我们不依赖于数据库来生成Id。这种方法还有另一个好处:它可以支持分发。例如,我们可以将一个表分区到两个物理SQL Server上,并分摊查找成本。如果我们有一个自动增量键,这对SQL Server不起作用。

我们决定做什么?
基于这些事实,我们决定让领域层的消费者为领域聚合生成标识符。我们将这些标识符表示为领域层中的64位无符号整数。领域消费者可以根据其上下文自由决定它的表示形式(例如,ASP.NET Core MVC可以序列化标识符string,以便使其客户端易于使用资源对象等)。

为什么是64位整数,为什么不是UUID?
最后,您可能想知道为什么64位整数。这里的主要目的是让我们能够在聚合根中生成唯一标识符。考虑到几乎每个平台都有静态API(例如Guid.NewGuid()在.NET等),UUID是非常便宜的方法。UUID最大的痛苦是存储系统在存储和索引方面的成本。这对我们来说不是什么大问题。但是,已经建立了将唯一标识符生成为更有效的原始类型(如分布式系统中的64位整数)的方法。Twitter Snowflake算法就是其中之一,这使得我们选择64位整数优于UUID。我们正在使用来自Rob Janssen的开源IdGen库,这是一款适用于.NET的Twitter Snowflake-like ID生成器。