使用 Flexible Sync将数据写入同步域时,您可以使用与写入本地域相同的 API。 但是,在开发应用程序时,需要记住一些行为差异。
当您写入同步 Realm 时,写入操作必须匹配以下两项:
同步订阅查询
App Services App 中的权限
如果您尝试写入与查询订阅或用户权限不匹配的数据,Realm 会使用称为补偿写入的非致命错误操作来恢复写入。
要了解有关为应用程序配置权限的更多信息,请参阅 Device SyncAtlas App Services文档中 的基于角色的权限 和 权限指南 。
要了解有关权限被拒绝错误、补偿写入错误和其他Device Sync 错误类型的更多信息,请参阅 Atlas App Services文档中的 同步错误 。
确定同步哪些数据
您可以写入同步 Realm 的数据由以下因素决定:
Device Sync 配置
应用中的权限
打开 Realm 时使用的 Flexible Sync 订阅查询
本页上的示例使用具有以下 Device Sync 配置的 Atlas App Services App 以及具有以下 Realm 软件开发工具包(Realm SDK) Realm 数据模型和订阅的客户端应用程序。
在此示例中,客户端应用程序使用以下对象模型:
class Item : RealmObject { var _id: ObjectId = ObjectId() var ownerId: String = "" var itemName: String = "" var complexity: Int = 0 }
App Services 配置
根据上述示例对象模型,Device Sync 配置有以下可查询字段:
_id(始终包含在内)complexityownerId
App Services App 的权限配置为仅允许用户读取和写入自己的数据:
{ "roles": [ { "name": "readOwnWriteOwn", "apply_when": {}, "document_filters": { "write": { "ownerId": "%%user.id" }, "read": { "ownerId": "%%user.id" } }, "read": true, "write": true, "insert": true, "delete": true, "search": true } ] }
在Atlas collection中的任何对象,如果ownerId与登录用户的user.id不匹配,则无法同步到此 域。
客户端Realm 数据模型和配置
使用对象模型,示例将同步域配置为同步与此订阅查询匹配的对象:
val app = App.create(FLEXIBLE_APP_ID) val user = app.login(credentials) val flexSyncConfig = SyncConfiguration.Builder(user, setOf(Item::class)) // Add subscription .initialSubscriptions { realm -> add( // Get Items from Atlas that match the Realm Query Language query. // Uses the queryable field `complexity`. // Query matches objects with complexity less than or equal to 4. realm.query<Item>("complexity <= 4"), "simple-items" ) } .build() val syncRealm = Realm.open(flexSyncConfig) syncRealm.subscriptions.waitForSynchronization() Log.v("Successfully opened realm: ${syncRealm.configuration}")
在Atlas collection中,complexity属性的值大于4的任何对象都无法同步到此域。
写入同步 Realm
对Flexible Sync Realm的写入大致可以分为以下两类Flexible Sync
成功写入:写入的对象同时与查询订阅和用户权限匹配。 该对象成功写入到域,并成功同步到 App Services 后端和其他设备。
补偿写入:写入的对象与订阅查询不匹配,或者用户没有足够的权限来执行写入。 Realm 通过补偿写入操作来恢复非法写入。
提示
如果要写入与查询订阅不匹配的对象,可以打开该对象与查询订阅匹配的另一个域。或者,您可以将对象写入不强制执行权限或订阅查询的非同步 Realm。
成功写入
当写入与用户权限和客户端中的查询订阅匹配时,Realm Kotlin SDK 可以成功将该对象写入同步 Realm。当设备具有网络连接时,此对象与 App Services 后端同步。
// Per the Device Sync permissions, users can only read and write data // where the `Item.ownerId` property matches their own user ID. val userId = user.id val newItem = Item().apply { ownerId = userId itemName = "This item meets sync criteria" complexity = 3 } syncRealm.write { // `newItem` is successfully written to the realm and synced to Atlas // because its data matches the subscription query (complexity <= 4) // and its `ownerId` field matches the user ID. copyToRealm(newItem) }
补偿写入
当写入与查询订阅或用户权限不匹配时,Realm 会恢复写入并抛出CompensatingWriteException。
更详细地说,当写入超出查询订阅范围或与用户权限不匹配的数据时,会出现以下情况:
由于客户端 Realm 没有“非法”写入的概念,因此写入最初会成功,直到 Realm 使用 App Services 后端解析变更集。
同步后,服务器会应用规则和权限。 服务器确定用户无权执行写入操作。
服务器向客户端发回恢复操作(称为“补偿写入”)。
客户端 Realm 恢复非法写入操作。
在对给定对象的非法写入和相应的补偿写入之间,客户端对该对象的任何写入都将丢失。 实际上,这可能看起来像写入成功,但当 Realm 与 App Services 后端同步并执行补偿写入时,该对象“消失”。
发生这种情况时,您可以参考Atlas App Services日志或使用客户端中的CompensatingWriteInfo对象来获取有关该错误的其他信息。
写入与查询订阅不匹配
根据上面详细介绍的 Flexible Sync Realm配置,尝试写入此对象会导致补偿写入错误,因为该对象与查询订阅不匹配:
// The complexity of this item is `7`. This is outside the bounds // of the subscription query, which triggers a compensating write. val itemTooComplex = Item().apply { ownerId = user.id itemName = "This item is too complex" complexity = 7 } syncRealm.write { copyToRealm(itemTooComplex) }
[Session][CompensatingWrite(231)] Client attempted a write that is disallowed by permissions, or modifies an object outside the current query, and the server undid the change.
您将在 App Services 日志中看到以下错误消息:
Error: Client attempted a write that is outside of permissions or query filters; it has been reverted (ProtocolErrorCode=231) Details: { "Item": { "63bdfc40f16be7b1e8c7e4b7": "write to \"63bdfc40f16be7b1e8c7e4b7\" in table \"Item\" not allowed; object is outside of the current query view" } }
写入与权限不匹配
给定上述 Device Sync 配置中的权限,尝试写入此对象会导致补偿写入错误,因为ownerId属性与登录用户的user.id不匹配:
// The `ownerId` of this item does not match the `user.id` of the logged-in // user. The user does not have permissions to make this write, which // triggers a compensating write. val itemWithWrongOwner = Item().apply { ownerId = "not the current user" itemName = "A simple item" complexity = 1 } syncRealm.write { copyToRealm(itemWithWrongOwner) }
[Session][CompensatingWrite(231)] Client attempted a write that is disallowed by permissions, or modifies an object outside the current query, and the server undid the change.
您将在 App Services 日志中看到以下错误消息:
Error: Client attempted a write that is outside of permissions or query filters; it has been reverted (ProtocolErrorCode=231) Details: { "Item": { "63bdfc40f16be7b1e8c7e4b7": "write to \"63bdfc40f16be7b1e8c7e4b7\" in table \"Item\" was denied by write filter in role \"readOwnWriteOwn\"" } }
补偿写入错误信息
1.9.0 版本中的新增功能。
您可以使用CompensatingWriteInfo对象在客户端中获取有关为何进行补偿写入的其他信息,该对象提供:
客户端尝试写入的对象的
objectType特定对象的
primaryKey用于补偿写入错误的
reason
此信息与您在App Services日志中找到的信息相同。 出于方便和调试目的, Kotlin SDK在客户端公开此对象。
以下示例说明了如何记录有关补偿写入错误的信息:
val syncErrorHandler = SyncSession.ErrorHandler { session, error -> runBlocking { if (error is CompensatingWriteException) { error.writes.forEach { writeInfo -> val errorMessage = """ A write was rejected with a compensating write error The write to object type: ${writeInfo.objectType} With primary key of: ${writeInfo.primaryKey} Was rejected because: ${writeInfo.reason} """.trimIndent() Log.e(errorMessage) } } } }
A write was rejected with a compensating write error The write to object type: Item With primary key of: RealmAny{type=OBJECT_ID, value=BsonObjectId(649f2c38835cc0346b861b74)} Was rejected because: write to "649f2c38835cc0346b861b74" in table "Item" not allowed; object is outside of the current query view
此消息中的
Item是此页面上的对象模型中使用的Item对象。主键是客户端尝试写入的特定对象的
objectId。table "Item"指的是此对象将同步的 Atlas collection。此示例中的
object is outside of the current query view原因是查询订阅设置为要求对象的complexity属性小于或等于4,并且客户端尝试写入此边界之外的对象。
对写入进行分组以提高性能
订阅集的每个写入事务都会产生性能成本。如果需要在会话期间对 Realm 对象进行多次更新,请考虑将编辑的对象保留在内存中,直到所有更改完成。这通过仅将完整且更新的对象写入 Realm 而不是每次更改来提高同步性能。