동기화된 Realm에 데이터 쓰기 - Flutter SDK
이 페이지의 내용
Flexible Sync를 사용하여 동기화된 영역에 데이터를 쓸 때 로컬 영역에 쓰는 것과 동일한 API를 사용할 수 있습니다. 그러나 애플리케이션을 개발할 때 유의해야 할 동작에는 몇 가지 차이점이 있습니다. 데이터를 읽고 영역에 쓰는 방법에 대해 자세히 알아보려면 데이터 읽기 및 쓰기를 참조하세요.
동기화된 영역에 쓰는 경우 쓰기 작업이 다음 두 가지 모두 와 일치해야 합니다.
- 동기화 구독 쿼리입니다.
쓰기 작업이 구독의 쿼리와 일치하지 않는 경우 쓰기는 치명적이지 않은 보상 쓰기 오류(ErrorCompensatingWrite)로 되돌아갑니다.
쓰기 (write) 오류 보상과 이를 방지하는 방법에 학습 보려면 쓰기 보상 하기 섹션을 참조하세요.
- App Services 앱 의 권한 입니다.
권한 표현식과 일치하지 않는 데이터를 쓰려고 하면 쓰기가 치명적이지 않은 권한 거부 오류와 함께 되돌아갑니다. 클라이언트에서는 오류(ErrorCompensatingWrite)로 표시됩니다. 서버에서는 역할의 쓰기 필터에 의해 쓰기가 거부된 방법에 대한 자세한 내용을 확인할 수 있습니다.
앱의 권한을 구성하는 방법에 대해 자세히 알아보려면 Atlas App Services 문서에서 역할 기반 권한 및 Device Sync 권한 가이드 를 참조하세요.
권한 거부 오류, 쓰기 오류 보상 및 기타 Device Sync 오류 유형에 대해 자세히 알아보려면 Atlas App Services 문서에서 동기화 오류 를 참조하세요.
동기화할 데이터 확인
이 페이지의 예제에서는 다음 Realm Mobile Sync 구성이 있는 Atlas App Services App과 다음 Realm SDK Realm 데이터 모델 및 구독이 있는 클라이언트 앱을 사용합니다.
App Services 구성
Realm Mobile Sync는 다음과 같은 쿼리 가능 필드로 구성됩니다.
_id
(항상 포함됨)miles
ownerId
App Services App에는 사용자가 자신의 데이터만 읽고 쓸 수 있도록 구성된 권한이 있습니다.
{ "name": "owner-read-write", "apply_when": {}, "document_filters": { "read": { "ownerId": "%%user.id" }, "write": { "ownerId": "%%user.id" } }, "read": true, "write": true }
클라이언트 Realm 데이터 모델 및 구성
이 페이지의 예시에서는 다음 스키마를 사용합니다.
()class _Car { "_id") ( () late ObjectId id; // This is the queryable field late String ownerId; late String make; late String? model; late int? miles; }
예제에서는 해당 스키마를 사용하여 이 구독 쿼리와 일치하는 객체를 동기화하도록 동기화 영역을 구성합니다.
final app = App(AppConfiguration(APP_ID)); final user = await app.logIn(Credentials.anonymous()); final config = Configuration.flexibleSync(user, [Car.schema]); final realm = Realm(config); // Add subscriptions realm.subscriptions.update((mutableSubscriptions) { // Get Cars from Atlas that match the Realm Query Language query. // Uses the queryable field `miles`. // Query matches cars with less than 100 miles or `null` miles. final newCarQuery = realm.query<Car>("miles < 100 OR miles == \$0", [null]); mutableSubscriptions.add(newCarQuery, name: "new-car-subscription"); }); await realm.subscriptions.waitForSynchronization();
동기화된 Realm에 쓰기
Flexible Sync Realm에 대한 쓰기는 크게 두 가지 범주 중 하나로 분류될 수 있습니다.
성공적인 쓰기: 기록된 객체가 쿼리 구독 및 사용자의 권한과 모두 일치합니다. 객체가 영역에 성공적으로 쓰고 App Services 백엔드 및 기타 기기와 성공적으로 동기화됩니다.
쓰기 보상: 작성된 객체가 구독 쿼리와 일치하지 않거나 사용자에게 쓰기를 수행할 수 있는 충분한 권한이 없는 경우 Realm은 불법적인 쓰기를 되돌립니다.
성공적인 쓰기
쓰기가 클라이언트의 Atlas App Services 권한 및 Flexible Sync 구독 쿼리 모두와 일치하면 Realm Flutter SDK 는 동기화된 Realm에 객체를 성공적으로 쓸 수 있습니다. 이 객체는 기기가 네트워크에 연결되어 있을 때 Atlas App Services 백엔드와 동기화됩니다.
// Per the Device Sync permissions, users can only read and write data // where the `Car.ownerId` property matches their own user ID. final userId = user.id; realm.write(() { // WRITE SUCCEEDS // `newCar` is successfully written to the realm and synced to Atlas // because it's data matches the subscription query (miles < 100) // and it's `ownerId` field matches the user ID. final newCar = Car(ObjectId(), userId, 'Toyota', miles: 2); realm.add(newCar); });
쓰기 보상
경우에 따라서는 처음에는 성공한 것처럼 보이는 쓰기 작업이 실제로는 불법적인 쓰기 작업인 경우도 있습니다. 이 경우 객체는 데이터베이스에 쓰지만 데이터베이스가 백엔드와 동기화되면 Realm은 보상 쓰기라는 치명적이지 않은 오류 작업을 통해 쓰기를 되돌립니다. 보상 쓰기는 다음과 같은 경우에 발생할 수 있습니다.
쓰기가 쿼리 구독과 일치하지 않음: 기록된 객체가 사용자의 권한과 일치하지만 쿼리 구독과 일치하지 않습니다.
쓰기가 권한과 일치하지 않음: 기록된 객체가 쿼리 구독과 일치하지만 사용자의 권한과 일치하지 않습니다.
좀 더 구체적으로 말하자면, 쿼리 구독의 범위를 벗어나거나 사용자의 권한과 일치하지 않는 데이터를 쓰면 다음과 같은 상황이 발생합니다.
클라이언트 영역에는 '불법적인' 쓰기에 대한 개념이 없기 때문에 영역이 App Services 백 엔드로 변경 집합을 해결할 때까지 처음에는 쓰기가 성공합니다.
동기화되면 서버는 규칙과 권한을 적용합니다. 서버는 사용자에게 쓰기를 수행할 수 있는 권한 부여가 없다고 판단합니다.
서버는 "보상 쓰기(compensating write)"라고 하는 되돌리기 작업을 클라이언트에 다시 보냅니다.
클라이언트의 영역이 잘못된 쓰기 작업을 되돌립니다.
해당 객체에 대한 불법적인 쓰기와 해당 보상 쓰기 사이에 지정된 객체에 대한 모든 클라이언트 사이드 쓰기는 손실됩니다.
실제로 이는 객체가 영역에 기록된 후 서버가 보상 쓰기를 클라이언트에 다시 보낸 후에 사라지는 것처럼 보일 수 있습니다.
권한 거부 오류, 쓰기 오류 보상 및 기타 Device Sync 오류 유형에 대해 자세히 알아보려면 Atlas App Services 문서에서 동기화 오류 를 참조하세요.
Atlas App Services 로그 에는 보상 쓰기 오류가 발생하는 이유에 대한 자세한 정보가 포함되어 있습니다.
쿼리 구독과 일치하지 않는 쓰기
구독 쿼리와 일치하는 경우에만 Flexible Sync 영역에 객체를 쓸 수 있습니다. 구독 쿼리와 일치하지 않는 쓰기를 수행하면 Realm은 처음에 객체를 쓰지만 보상 쓰기를 수행합니다. 이는 구독 쿼리와 일치하지 않는 불법적인 쓰기를 되돌리는 치명적이지 않은 작업입니다.
쿼리 구독과 일치하지 않는 객체를 작성하려면 객체가 쿼리 구독과 일치하는 다른 영역을 엽니다. 또는 권한이나 구독 쿼리를 적용하지 않는 로컬 영역 에 객체를 쓸 수 있습니다.
코드 예시
위의 동기화 영역에 대한 구성을 고려할 때 이 객체를 작성하려는 시도는 쿼리 구독과 일치하지 않습니다.
final carId = ObjectId(); final ownerId = app.currentUser!.id; realm.write(() { // WRITE REVERTED BY QUERY SUBSCRIPTION COMPENSATING WRITE // `oldCar` is initially written to the realm, then later removed // in a compensating write when the server processes the write. // This is because the `miles` property of `oldCar` doesn't match // the subscription query, which is only for cars with less than 100 miles. final oldCar = Car(carId, ownerId, 'Honda', miles: 90000); realm.add(oldCar); }); // Let changes sync to and from server await realm.syncSession.waitForUpload(); await realm.syncSession.waitForDownload(); final noCar = realm.find<Car>(carId); // The Car is no longer in the realm because of // the compensating write from the server. expect(noCar, isNull);
클라이언트 오류
이 시나리오에서 클라이언트 사이드 로그의 오류 메시지는 다음과 같습니다.
[INFO] Realm: Connection[1]: Session[1]: Received: ERROR "Client attempted a write that is outside of permissions or query filters; it has been reverted" (error_code=231, try_again=true, error_action=Warning) [INFO] Realm: Connection[1]: Session[1]: Reporting compensating write for client version 21 in server version 2877: Client attempted a write that is outside of permissions or query filters; it has been reverted [ERROR] Realm: SyncSessionError message: Client attempted a write that is outside of permissions or query filters; it has been reverted Logs: https://services.cloud.mongodb.com/groups/5f60207f14dfb25d23101102/apps/639340a757271cb5e3a0f0cf/logs?co_id=6424433efb0c6bbcc330347c category: SyncErrorCategory.session code: SyncSessionErrorCode.compensatingWrite isFatal: false
App Services 오류
이 시나리오에서 App Services 로그의 오류 메시지는 다음과 같습니다.
Error: Client attempted a write that is outside of permissions or query filters; it has been reverted (ProtocolErrorCode=231) Details: { "Car": { "6424433fd4d9f52ee93ad590": "write to \"6424433fd4d9f52ee93ad590\" in table \"Car\" not allowed; object is outside of the current query view" } }
권한이 일치하지 않는 쓰기
클라이언트에 쓰기를 시도하면 객체가 사용자의 서버 측 쓰기 권한과 일치하지 않을 때 보상 쓰기 오류를 trigger할 수도 있습니다.
클라이언트에서 이러한 유형의 쓰기는 쿼리 구독과 일치하지 않는 쓰기와 동일하게 작동합니다. 실제로 이는 쓰기가 성공한 것처럼 보일 수 있지만 Realm이 App Services 백엔드와 동기화되고 보상 쓰기를 수행하면 객체가 '사라집니다'.
코드 예시
위에서 자세히 설명한 Realm Mobile Sync 구성의 권한이 주어지면 ownerId
속성이 로그인한 사용자의 user.id
와 일치하지 않는 객체를 작성하려고 시도하는 것은 합법적인 쓰기가 아닙니다.
final carId = 'someOtherId'; realm.write(() { // WRITE REVERTED BY PERMISSION ERROR // `otherUsersCar` is initially written to the realm, then removed upon synchronization // because it's `ownerId` property doesn't match the user ID of the user // making the request. final otherUsersCar = Car(ObjectId(), carId, 'Ford'); realm.add(otherUsersCar); }); // sync changes await realm.syncSession.waitForUpload(); await realm.syncSession.waitForDownload(); final noCar = realm.find<Car>(carId); // The Car is no longer in the realm because of // the compensating write from the server. expect(noCar, isNull);
클라이언트 오류
이 시나리오의 클라이언트 오류는 App Services 권한을 벗어난 객체를 쓰려고 할 때 발생하는 오류와 동일합니다.
[INFO] Realm: Connection[1]: Session[1]: Received: ERROR "Client attempted a write that is outside of permissions or query filters; it has been reverted" (error_code=231, try_again=true, error_action=Warning) [INFO] Realm: Connection[1]: Session[1]: Reporting compensating write for client version 25 in server version 2879: Client attempted a write that is outside of permissions or query filters; it has been reverted [ERROR] Realm: SyncSessionError message: Client attempted a write that is outside of permissions or query filters; it has been reverted Logs: https://services.cloud.mongodb.com/groups/5f60207f14dfb25d23101102/apps/639340a757271cb5e3a0f0cf/logs?co_id=6424433efb0c6bbcc330347c category: SyncErrorCategory.session code: SyncSessionErrorCode.compensatingWrite isFatal: false
App Services 오류
App Services 로그의 오류 메시지는 쿼리 구독 문제가 아니라 권한 문제임을 확인하는 데 도움이 되는 몇 가지 추가 정보를 제공합니다. 이 예에서 오류 메시지는 객체가 사용자의 역할과 일치하지 않음을 보여줍니다.
Error: Client attempted a write that is outside of permissions or query filters; it has been reverted (ProtocolErrorCode=231) Details: { "Car": { "6424433fd4d9f52ee93ad591": "write to \"6424433fd4d9f52ee93ad591\" in table \"Car\" was denied by write filter in role \"owner-read-write\"" } }