设计持久层

Designing a Persistence Layer

对于一个项目,我们开始研究持久性功能以及我们希望如何实现它。目前我们正在考虑保持清洁架构,可能会选择洋葱架构。因此,我们要定义一个新的外层,持久层位于其中。

我们正在研究使用 SQLite 作为数据存储的各种 ORM 解决方案(我们似乎正在趋同于 Entity Framework),但我们遇到了障碍:应该如何管理 ID 和处理 add/removal 在一些集合中或在不同集合之间移动一些实例。

在我们 'onion' 的核心中,我们希望保留我们的 POCO 对象。因此,我们不希望在我们的业务对象中添加某种 'ID' 属性。只有在持久层内部,我们才希望 classes 具有对象 ID。因为这次分离:

环顾互联网,我还没有在某个地方找到一个演示清洁架构设计中的持久层的实现。大量高级图表和 "depend only inward",但没有源代码示例进行演示。 到目前为止我们提出的一些可能的解决方案:

  1. 在持久层内的 POCO 实例及其代表 'database model objects'(具有 ID 等)之间进行一些查找。保存项目状态时,业务模型对象将与此数据库模型对象匹配并相应地更新匹配项的状态。然后对象被持久化。
  2. 加载项目时,业务对象的持久层 returns 装饰器对象将 ID 添加到业务对象,只有通过将对象转换为该装饰器才能在持久层内可见 class.然而,这使我们无法定义密封的 POCO 对象,并且似乎打破了 Clean Architecture 的设计理念。

由于有效地将内存中的业务对象加倍,选项 1 似乎在内存中开销很大。选项 2 似乎是最优雅的,但正如我所写:感觉它破坏了清洁架构。

有没有更好的选择?我们是否应该只选择选项 2 并将清洁架构更多地作为指南而不是规则?有人能给我们指出一个代码中的工作示例吗(我确实在 https://github.com/luisobo/clean-architecture 找到了一个 iOs 示例,但由于我不了解该语言,因此无法用它做很多事情)。

正如其他人在评论中提到的,ID 是应用程序的自然组成部分,通常在持久性以外的其他部分也需要。 因此,不惜一切代价避免使用 ID 会产生笨拙的设计。

形象设计

但是,身份设计(在何处使用哪些 ID、在 ID 中放入哪些信息、用户定义与系统生成等)是非常重要的,需要深思熟虑。

确定什么需要 ID 什么不需要 ID 的一个很好的起点是领域驱动设计的值对象/实体区别。

  • 值对象是由其他值组成且不会改变的东西 - 因此您不需要 ID。
  • 实体有一个生命周期,并且会随着时间而改变。因此,仅凭它们的值不足以识别它们 - 它们需要一个明确的 ID。

如您所见,推理与您在问题中所采用的技术观点截然不同。但是,这并不意味着您应该忽略框架施加的约束(例如 entity framework)。

如果您想深入讨论身份设计,我可以推荐 Vaughn Vernon 的 "Implementing DDD"(第 5 章 - 实体中的 "Unique Identity" 节)。


注意:我并不因此建议您使用 DDD。我只是认为 DDD 有一些关于 ID 设计的很好的指导方针。在这个项目中是否使用 DDD 是一个完全不同的问题。

首先,现实世界中的万物皆有ID。你有你的社会安全号码。汽车有他们的登记号码。商店中的商品具有 EAN 代码(和生产标识)。没有 id 世界上什么都行不通(有点夸张,但希望你明白我的意思)。

申请也是一样

如果您的业务对象没有任何自然键(如社会保险号),您必须有办法识别它们。否则,一旦您复制对象或将其传输到进程边界,您的应用程序就会失败。 因为那是一个新的对象。就像你克隆了绵羊多莉。是同一只羊吗?不,是迷你多莉。

另一部分是,当你建造复杂的结构时,你就违反了得墨忒耳法则。比如像:

public class ForumPost
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Body { get; set; }
    public User Creator { get; set; }
}

public class User
{
    public string Id { get; set; }
    public string FirstName { get; set; }
}

当您使用该代码并调用时:

post.User.FirstName = "Arnold"; 
postRepos.Update(post);

你希望发生什么?您的论坛 post 存储库是否应该突然对用户所做的更改负责?

这就是 ORM 如此糟糕的原因。他们违反了良好的架构。

返回 ID。一个好的设计是使用 user id。因为这样我们就没有违反 Demeter 的法律,并且仍然得到了很好的关注分离。

public class ForumPost
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Body { get; set; }
    public int CreatorId { get; set; }
}

所以结论是:

  1. 不要放弃 ID,因为它会在尝试从您获得的所有副本中识别真实对象时引入复杂性。

  2. 在引用不同的实体时使用 id 可以帮助您保持具有不同职责的良好设计。