Docs Menu
Docs Home
/ /
Atlas Device SDK
/ /

동기화된 Realm에 데이터 쓰기 - Kotlin SDK

이 페이지의 내용

  • 동기화할 데이터 확인
  • App Services 구성
  • 클라이언트 Realm 데이터 모델 및 구성
  • 동기화된 Realm에 쓰기
  • 성공적인 쓰기
  • 쓰기 보상
  • 쓰기가 쿼리 구독과 일치하지 않음
  • 쓰기가 권한과 일치하지 않음
  • 쓰기 오류 정보 보상
  • 성능 향상을 위한 그룹 쓰기

Flexible Sync 를 사용하여 동기화된 영역 에 데이터를 쓸 때는 로컬 영역 에 쓰는 것과 동일한 API를 사용할 수 있습니다. 그러나 애플리케이션 을 개발할 때 염두에 두어야 할 동작에는 몇 가지 차이점이 있습니다.

동기화된 영역에 쓰는 경우 쓰기 작업이 다음 두 가지 모두 와 일치해야 합니다.

  • 동기화 구독 쿼리

  • App Services App의 권한

쿼리 구독이나 사용자의 권한과 일치하지 않는 데이터를 쓰려고 하면 Realm은 보상 쓰기라는 치명적이지 않은 오류 작업을 통해 쓰기를 되돌립니다.

앱의 권한을 구성하는 방법에 대해 자세히 알아보려면 Atlas App Services 문서에서 역할 기반 권한Device Sync 권한 가이드 를 참조하세요.

권한 거부 오류, 쓰기 오류 보상 및 기타 Device Sync 오류 유형에 대해 자세히 알아보려면 Atlas App Services 문서에서 동기화 오류 를 참조하세요.

동기화된 영역에 쓸 수 있는 데이터는 다음에 따라 결정됩니다.

  • Realm Mobile Sync 구성

  • 앱의 권한

  • 영역을 열 때 사용되는 Flexible Sync 구독 쿼리

이 페이지의 예제에서는 다음 Realm Mobile Sync 구성이 있는 Atlas App Services App과 다음 Realm SDK Realm 데이터 모델 및 구독이 있는 클라이언트 앱을 사용합니다.

이 예제에서 클라이언트 앱은 다음 객체 모델을 사용합니다.

class Item : RealmObject {
@PrimaryKey
var _id: ObjectId = ObjectId()
var ownerId: String = ""
var itemName: String = ""
var complexity: Int = 0
}

위의 예시 객체 모델 을 기반으로 Device Sync는 다음과 같은 쿼리 가능 필드로 구성됩니다.

  • _id (항상 포함됨)

  • complexity

  • ownerId

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
}
]
}

ownerId 이(가) 로그인한 사용자의 user.id 와(과) 일치하지 않는 Atlas collection 객체는 이 영역과 동기화할 수 없습니다.

예제에서는 객체 모델 을 사용하여 이 구독 쿼리 와 일치하는 객체를 동기화하도록 동기화 영역 을 구성합니다.

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}")

complexity 속성의 값이 4 보다 큰 Atlas collection 객체는 이 영역에 동기화할 수 없습니다.

Flexible Sync Realm에 대한 쓰기는 쓰기가 권한Flexible Sync 구독 쿼리와 일치하는지 여부에 따라 크게 두 가지 범주 중 하나로 분류될 수 있습니다.

  • 성공적인 쓰기: 기록된 객체가 쿼리 구독 및 사용자의 권한과 모두 일치합니다. 객체가 영역에 성공적으로 쓰고 App Services 백엔드 및 기타 기기와 성공적으로 동기화됩니다.

  • 쓰기 보상: 기록된 객체가 구독 쿼리와 일치하지 않거나 사용자에게 쓰기를 수행할 수 있는 충분한 권한이 없습니다. 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을 발생시킵니다.

좀 더 구체적으로 말하자면, 쿼리 구독의 범위를 벗어나거나 사용자의 권한과 일치하지 않는 데이터를 쓰면 다음과 같은 상황이 발생합니다.

  1. 클라이언트 영역에는 '불법적인' 쓰기에 대한 개념이 없으므로 Realm이 App Services 백엔드로 변경 집합을 해결할 때까지 처음에는 쓰기가 성공합니다.

  2. 동기화되면 서버는 규칙과 권한을 적용합니다. 서버는 사용자에게 쓰기를 수행할 수 있는 권한 부여가 없다고 판단합니다.

  3. 서버는 "보상 쓰기(compensating write)"라고 하는 되돌리기 작업을 클라이언트에 다시 보냅니다.

  4. 클라이언트의 영역이 잘못된 쓰기 작업을 되돌립니다.

해당 객체에 대한 불법적인 쓰기와 해당 보상 쓰기 사이에 지정된 객체에 대한 모든 클라이언트 사이드 쓰기는 손실됩니다. 실제로 이는 쓰기가 성공한 것처럼 보일 수 있지만 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 로그 에서 찾을 수 있는 정보와 동일합니다. 코틀린 SDK ( 코틀린 SDK (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 보다 작거나 같도록 쿼리 구독이 설정되어 있고 클라이언트가 이 경계를 벗어난 객체를 쓰려고 시도했기 때문입니다.

구독 세트에 대한 모든 쓰기 트랜잭션(write transaction)에는 성능이 소모됩니다. 세션 중에 영역 객체를 여러 번 업데이트해야 하는 경우 모든 변경이 완료될 때까지 편집한 객체를 메모리에 보관하는 것이 좋습니다. 이렇게 하면 모든 변경 사항 대신 완전하고 업데이트된 객체만 영역에 기록하므로 동기화 성능이 향상됩니다.

돌아가기

구독 관리