一个灵活的对象存储,支持异步使用模式。
事实上,它通过不提供同步模式来强制使用异步模式!
目标是拥有一个轻量级的 API,易于使用,并且可以高效地实现。
它还支持属性级别的版本控制,保留属性曾经拥有的每个值。
这部分本身很有用,但主要目的是使数据库在设备之间同步时更具弹性,因为各个属性值只会添加,而永远不会被修改。
数据存储包含实体,实体具有属性和关系。
出于管理目的,所有实体都保证具有三个属性:type
、datestamp
和 identifier
。
实体 type
只是一个标签,指示实体是什么类型的。 您可以自行决定它是否也暗示了实体将具有哪些属性 - 数据存储不会强制特定实体类型具有任何结构。
实体 identifier
在所有实体中都是唯一的,与类型无关。
实体 datestamp
指示首次添加到存储的时间。
除了保证的属性之外,实体还具有任意数量的自定义属性。
每个属性都作为 (key, value, datestamp, type)
元组存储在数据库中。
属性的 key
只是一个标签(“name”、“address”等)。
属性的 value
是一个原始类型(int、double、bool、string、date、binary blob),或对另一个实体的引用。
datestamp
指示属性值被设置的时间。
属性的 type
是一个标签,指示属性的语义类型。您可以将其视为指示如何解释或显示属性的原始 value
的一种方式。
属性可以在实体之间形成关系。
关系只是以某种方式将两个实体联系在一起的属性;value
是相关实体,type
可用于指示关系的类型。
属性值具有 datestamp
。
一个实体可以为同一个属性拥有多个条目。
当属性被更改时,会添加一个新条目,其中包含更新后的值和更新的 datestamp。
因此,属性的多个条目是其历史记录,最新的条目是当前值。
所有访问操作都是异步的,并且与底层存储无关。
实体作为不透明的引用类型传入和返回,这些类型隐藏了数据库的实现。
API 专为批量操作而设计;它接受要操作的实体/属性列表,并返回组合结果的列表或字典。
在输入时,引用包含一个用于查找实体的解析器,以及操作执行时使用的一些可选属性。
输入引用可以是未解析的,这意味着它们可能不引用有效的实体。它将在执行请求的操作时被解析。
结果使用回调块传递,回调块会返回实体引用。
在输出时,引用将始终被解析。它在内部仍然有一个解析器,但保证是 CachedResolver
或 NullResolver
的实例。
输出引用包含一个字典,其中包含已获取的实体属性。该字典不能保证包含实体的所有属性 - 仅包含所执行操作请求的属性。属性作为不可变的字典传入和返回。
引用是相对轻量级的结构,可以在客户端中安全地传递,并且不与特定的线程、数据库上下文甚至数据存储绑定。
客户端代码可以通过指定要查找的实体 identifier
,或与实体属性匹配的值(例如 name == "test"
)来创建引用。
在内部,未解析的引用被解析为实际的 EntityRecord
。如果您传入的是先前操作输出的引用,则它已被解析。
有时您的客户端代码知道(或期望)实体存在,如果解析失败,要么是编程错误,要么您只是想什么都不做。
其他时候,您想通过名称或标识符指定一个实体,如果该实体不存在,则创建它。
实体引用通过允许您提供类型和一组初始属性以及搜索键来支持此模式。
当实体被解析时,如果引用与现有对象不匹配,则将使用提供的类型和属性创建一个新对象。
您可以注册继承自 CustomReference
的自定义引用类。
每个引用类都与特定的实体类型相关联。
如果您在客户端代码中创建它们并传入它们请求创建,则将创建一个关联类型的实体。这样就无需显式指定类型了。
当您收到实体结果时,任何具有相应已注册自定义引用类的实体类型都将作为该类的实例返回。这使您可以编写动态代码,而不必测试返回引用的 type
属性。
此功能允许您的客户端代码为您正在存储和数据存储的模型实体定义类,并将业务逻辑和其他代码与它们关联。大多数客户端代码应该能够根据这些类进行操作 - 根据需要将它们传递到数据存储并从数据存储中检索它们。
但是,值得记住的是,这些类通常只是商店中实体的部分表示。实体引用可用的属性完全取决于创建时请求的内容。这有点类似于未执行故障处理的 NSManagedObject
,但仅仅是有点。与核心数据对象不同,实体引用没有故障处理的概念,需要显式地重新获取才能使用其他属性进行更新。
当前的底层存储是 CoreData,但目的是使其完全不透明。
最初使用 CoreData 的主要原因是允许利用其他解决方案,这些解决方案提供 CoreData 在设备之间的自动同步。
可以从基于字典的交换格式读取和写入存储。 这仅使用 JSON 合法类型,因此可以轻松地转换为 JSON/XML/任何格式。
该设计的某些方面使得使用传统数据库实现起来很昂贵。
每个实体的每个属性都作为单独的记录存储在与实体本身不同的表中。同一个实体的同一个属性可以存在多个条目。因此,一个简单的属性查找需要大量工作来过滤和/或排序条目。
现在我不太担心这一点 - 我更感兴趣的是该设计如何工作的其他方面。
我相当确定,如果需要,自定义实现可以大大提高效率。
正在考虑 Combine 支持。
我在 DatastoreCombineTests.swift
中有一个概念验证测试,但我需要稍微考虑一下它是否自然适合。
欢迎对此方面的所有建议。