MongoDB Realm 是什么
Realm 是一个专门为移动端设计的数据库。主要特点是性能好,易使用。在移动端 Realm 颇有一些人气,在轻量的场景下使用还是挺适合的。随着 App 的发展,越来越多的本地数据需要和服务器进行同步。如果团队自己开发,需要制定服务器端数据库的格式(和移动端数据库不一样)、服务器端的存储、需要写一个和服务器进行数据同步的程序。对于小团队而言,为了实现这个需求可是不小的工作量。如果 Realm 也提供一个在云上数据库,再提供一个同步的程序。那么数据同步的需求对于中小规模开发者就非常变得非常轻松了。于是 Realm 推出了 Realm Platform,也叫 Realm Cloud。Realm platform 的收费也是按照容量来算钱。
我们有一个 APP 的功能是驱动无人机进行数据采集,有一个核心需求需要记录很多的硬件相关的日志。如果某次无人机采集任务失败,我们希望有日志能及时的帮助我们检查是哪个部分出的问题。所以我们有一个类似即时通讯的需求。当我们发现有 Realm platform 这样的产品的时候我们就马上付费(每个月 20 刀)用起来了。
使用了几个月后发现现实还是很骨感,Realm platform 有几个严重的问题:
- 数据同步不太稳定。虽然也是基于 AWS,但是只有美国和新加坡两个节点可选。因为某些神秘力量,时常出现数据节点无法连接的问题(科学上网就会有所缓解)。
- 服务端数据库访问做的稀烂。没有一个网页端后台处理数据,想用 GraphQL 访问云端的数据也是漏洞百出。本来计划做一个网页展示数据,最后发现只能利用提供的移动端 SDK 做了一个 iPad 版本的 APP。
总结下来,Realm 移动端数据库做的没毛病,数据同步也做的没毛病。但是云端的数据库的存取对于 Realm 这个小团队而言还是门槛太高。
MongoDB 是一个流行的 NoSQL 数据库,主要是在服务器端使用。后来推出了基于云端的 MongoDB Atlas。随着移动互联网的发展,MongoDB 就想着开发一个在移动端的服务,可以把数据方便的同步到 MongoDB Atlas 上。MongoDB 当时发布了一个 iOS 端的 beta SDK。我当时试用了一下,做的很差。
大概是 MongoDB 的意志更坚决一些,我自己做不好我就看看有没有现成的我去买一个。结果还真有,于是和 Realm 一拍即合。收购半年多以后推出了全新的 MongoDB Realm。移动端的数据库还是 Realm,本地的数据存取接口几乎保持一致。通过 Realm sync 这个程序把数据同步到 MongoDB Atlas 中。理论上看这是一个强强联合,互相补齐了各自的短板。
对于我们来说合并后已知的最大缺点是费用提高了,原来我们每个月是 20 刀。现在 MongoDB Realm 因为是一个新服务,MongoDB 收费要每个月 57 刀起。希望未来这个服务成熟会降价吧。
服务端配置:创建 Atlas
第一步我们需要在云端创建一个可以运行数据库的实例:Get Started with Atlas。在这个过程里可以选择选择集群的节点和性能:
需要强调的是因为我们要使用 Realm sync 同步数据,实例的 MongoDB 的版本必须大于 4.4。
选用了 4.4 的版本后,你会发现你的集群就只能是专用的机器,不能是共用的机器,因此费用就是每个月 57 刀起步了。
创建 Realm App
接下来我们选择到 Realm tab 下,点击右上角的创建就可以创建 Realm App 实例了。
创建的窗口里要输入实例的名字,选择要链接的集群:
在创建完 Realm App 后,你就可以看到这个实例的分配 App Id,并且对这个实例进行相关的配置(比如授权、Schema)。
创建移动端数据模型
服务端配置好后我们就可以在移动端里定义数据模型。为了让 model 支持同步,相比常规的 Realm object 多了一些要求。
第一点,主键的名字必须叫_id
,类型也只能是ObjectId、String、Int
。所以如果是原来就使用 realm 数据库,想要支持同步还需要改动一下数据结构。
第二点,必须定义一个属性用作 Partition key 用来给数据分类(下面的例子中使用 _partition
)。比如有的数据是每个用户只可以自己访问的,有的是一个群体可以访问,有的是全部公开的。可以通过给数据设置对应的_partition
标记数据分类。后台最后可以根据 partition key 配置不同的访问权限。详细信息可以参照:Partition Atlas Data into Realms。
下面是一个基于 Swift 的对象定义:
class User: Object {
@objc dynamic var _id: String = ""
@objc dynamic var _partition: String = ""
@objc dynamic var name: String = ""
override static func primaryKey() -> String? {
return "_id"
}
}
服务端 data model 配置
首先需要 Realm 控制面板的 Sync 页面下启动同步功能。在配置选项里需要选择开启同步的 Atlas 集群,接着是 partition key 的名称,最后是指定读写权限。详细的配置说明可以参考 Define Sync Rules。
读写权限可以根据 partition key 配置。权限配置如果有更深入的要求可以查看这份文档:Define Sync Permissions。
服务端配置 data model 有两种方式:
- 在服务端配置 MongoDB 文档格式的 schema
- 在移动端定义好数据模型后,服务端开始开发者模式,Realm sync 自动同步数据模型 schema 到服务端
服务端配置
首先在 realm 控制面板选择 Schema 菜单,接着在 Collections 选择你要存储的集群点击创建的加号按钮
如果是一个新的数据模型,在 Collection name 配置的时候选择马上创建一个。因为也存在是服务端本来就有的数据,现在要开始进行同步,因此也存在现在有的数据。
创建完成后选中 collection 选择 Schema tab 下就可以在一个编辑框里配置:
下面是我的一个 Schema:
{
"title": "BaseLog",
"bsonType": "object",
"required": [
"_id",
"_partition",
"deviceId",
"timestamp"
],
"properties": {
"_id": {
"bsonType": "objectId"
},
"_partition": {
"bsonType": "string"
},
"deviceId": {
"bsonType": "string"
},
"timestamp": {
"bsonType": "double"
}
}
}
Schema 中还可以针对不同的数据类型做一些校验:如果是数字比如可以配置数字的最大值、最小值;如果是文本类型可以配置一个正则表达式,要符合这个表达式算合法。具体 Schema 的格式可以参考这份文档:Document Schemas。
配置完成后可以在 SDKs 菜单下选择 Data Models tab 下查看对应的 model 定义。如果是这种方式也可以在定义好 schema 后复制这些 model 到移动端中。
如果 data model 的属性比较多,手写这么多还是挺费劲的,而且也容易出错。因此我更推荐下面同步的方式。
移动端定义后同步
要开启移动端数据模型 schema 自动同步步骤简单,只要在服务端开启 Dev Mode,App 运行后(授权成功后)新的 schema 就会自动同步到服务端。
移动端用户授权
数据同步的前提是移动端正确获得授权。 第一步是初始化 MongoDB Realm 实例,在服务端创建了 realm app 后会得到一个 app Id,使用这个 Id 初始化:
let app = App(id: "REALM_APP_ID")
在 realm 控制台可以配置启用那些授权渠道:
初始化 app 实例用,实例化一个 credential 就可以调用 login 方法登录:
let cred = Credentials.userAPIKey("uDGDFuIZ0vW7nH7p5Y")
app.login(credentials: cred) { (result) in
switch result {
case .failure(let error):
// error
case .success(let user):
// login success
}
}
数据读写
写入的数据和常规存储 realm 没有区别,数据写入到本地数据库成功后会自动同步。
let task = Task(partition: self.partitionValue, name: textField.text ?? "New Task")
try! self.realm.write {
self.realm.add(task)
}
读数据的方式也和读取本地数据一致。只是需要留意一下订阅数据的变更回调,一个数据集如果变化了(服务端有新的数据同步到本地),会回调通知数据使用方:
let tasks = realm.objects(Task.self)
// Watch for changes. No special syntax required for synced realms.
// Retain notificationToken as long as you want to observe.
let notificationToken = tasks.observe { (changes) in
print("Changed: \(changes)")
}
// Later, when done observing
notificationToken.invalidate()
需要提醒一下要记得持有 notificationToken
,notificationToken
如果被释放,更新的闭包也会被回收。
总结
移动端继续使用 realm 数据库,云端数据存储在 MongoDB 里,极大的提升了在云端的数据处理方式,补齐了原来 Realm Platform 的短板。在我过去几个月的观察来看, realm 团队也在积极的维护,MongoDB 本身也乐于看到 realm 持续改进。
至于具体的稳定性问题上因为是一个新服务,还不好定论,但是我挺看好未来的发展。目前我们的日志服务已经在迁往 MongoDB Realm 的路上,如果一切顺利未来更多的数据同步也会通过 MongoDB Realm 进行。