编写后端
后端是在一种技术上实现 Wow 存储端口的模块。内置的 PostgreSQL 和 MongoDB 后端与其他后端没有区别:它们实现同样的端口,以同样的方式被找到,通过同样的契约测试。这些端口是 @WowSpi:次版本可能改变它们,使用它们需要 @OptIn(WowSpi::class)(API 级别)。
端口
存储保存节点的持久状态。它的端口在 :spi 中:
| 端口 | 保存 | 契约 |
|---|---|---|
EventLog | 上下文的日志:按分区和位置组织的已提交流,按版本和按请求唯一 | EventLogSpec |
SnapshotStore | 每个聚合的最新快照 | SnapshotStoreSpec |
CheckpointStore | 每个订阅在每个分区上的位置 | CheckpointStoreSpec |
LeaseStore | 哪个节点拥有哪个分区 | LeaseStoreSpec |
FailureLedger | 停放的聚合 | FailureLedgerSpec |
ReservationStore | 已预留的唯一键 | ReservationStoreSpec |
TimerStore | 为稍后安排的命令 | TimerStoreSpec |
存储可以保存日志、快照或两者:Elasticsearch 存储保存快照,从不保存日志。在存储之外,Sink 把提交复制到别处(SinkSpec),topic 传输在上下文之间传递发布的事件流和命令(TopicTransportSpec),查询后端提供某个模型的查询(:query-tck 中的 QueryBackendSpec、EventQueryBackendSpec 和 DatasetQueryBackendSpec)。
提供者
后端向宿主提供一个 StoreProvider:配置所指明的 type(wow.stores.<name>.type)、它需要应用提供什么、它是否仅凭应用的资源就可用(wow.backend: auto 据此选择),以及 open,它为一个配置的实例返回一个 Store。open 不触碰数据库中的任何东西:存储在每个端口首次使用时才打开它,每个上下文一个日志,并记住由什么创建它的 schema,所以由 wow.ddl 决定它们是被创建、被校验还是保持不动。
宿主通过 Java 的 ServiceLoader 从模块的 META-INF/services/me.ahoo.wow.spi.store.StoreProvider 中找到提供者,也会在应用的 bean 中寻找;bean 会取代类路径上同一类型的提供者。提供者是无状态的:每个存储实例都得到自己的 Store,基于它的配置所找到的资源,即一个 DataSource 或某个驱动的客户端。
契约测试
后端的正确性就是契约。它的容器测试继承它所实现端口的每个 spec,并提供一个全新的空存储:
class PostgresEventLogTest : EventLogSpec() {
override fun createLog(context: String, partitions: Int): EventLog = PostgresFixture.log(partitions, context)
}spec 的每个测试都在自己的上下文上运行,所以每个上下文保留一张表或一个集合的后端会把它们隔离开。查询后端声明它的存储可能不支持的特性,比如 keyset 分页或百分位数,spec 会跳过相应的测试;与过滤语义之间已知的差异按 ID 列出,而不是隐藏起来。:tck-containers 以固定的镜像版本启动共享的容器,后端的模块在 src/integrationTest 中运行这些套件。
规则
- 后端依赖
:spi,提供查询时还依赖:query;它从不依赖另一个后端或运行时。:architecture保证这一点,新模块要加入ALLOWED_PROJECT_DEPENDENCIES。 - 它记录自己创建的每个结构的格式,并拒绝另一种格式的结构,所以节点永远不会写入它不理解的布局。
- 它是一个已发布的模块:它保留 ABI 转储、一个链接到站点的 README,并在“运维”下为它的运维人员提供一个页面。
- 无法访问的存储报告为
StorageUnavailableException,节点把它当作系统性的失败:订阅暂停并重试,不停放任何东西。
PostgreSQL 后端 backends/postgres 是可以对照其测试阅读的参照。