# TypeのデータをRealmからFirestoreに移行しました
2017年の頭ぐらいから運営しているTypeはiOSDC 2017のLTでも発表した様にRealm Object Serverを利用していました。
他にもいくつかRealmやRealm Mobile Platformについて発表した事もあったぐらいRealmが好きだったのですが、最近リリースしたTypeのバージョン2.1.2よりRealmからFirebaseにデータを移行する実装を入れました。
何故Realmからデータを移行する必要があったのか
元々Realm Object Serverを利用していたのは有料会員向けの複数端末での同期機能だけでした。利用していたRealm Object Serverのバージョンは1系です(今の最新は3系)。Realm Object Serverの1系を利用する為にはクライアントのバージョンを2系にしておく必要があり(今の最新は3系)、クライアントの2系はSwift 3で書かれていたのでした。何故Swiftのバージョンが関係あるのかと言うと、Swift(というかXcode)は大抵2バージョンまでサポートされるので、2019年の早い段階でリリース予定とされているSwift 5が出てしまうと、Swift 3がサポートから外れてしまい、Typeのアップデートが出来ないというリスクを抱えていたのでした。
何故Firebase Firestoreに移行したのか
勿論選択肢がFirebase Firestoreだけだったわけではありません。既存で利用しているRealm Object ServerとクライアントのRealmのバージョンを上げれば今後も利用し続ける事は出来たと思います。しかし、サーバーとクライアントのバージョンを同時にアップデートする必要がありました。これがなかなか難しく、クライアントサイドには事前にメンテナンスモードと強制アップロード機能の実装が必要でした(念の為これは既に実装済み)。しかし、サーバーサイドのデータベースのアップデートの経験は無く、アップデートに失敗したりなど下手をしたら死ぬなと思うとなかなか踏み切れなかったのが正直な所です。また、現在さくらのVPSを利用しているので毎月お金が掛かってしまっていました。これも正直辞めたいなと思っていた要因でもあります。
Firebaseもお金が掛からないわけでは無いですが、現時点ではさくらより安く済むはずなのと、自分でサーバーを運用しないと行けないのはやはり大変なので、全てをGoogleに任せてしまえるのはとても安心です。また、データもコンソール上から管理出来るので何かあっても救済方法が存在するというのも安心出来る点かなと思います。
Realmのライブラリ自体が容量も大きいのでアプリの容量が削減出来るのも嬉しいですね。
移行する為にした事
データをFirebase Firestoreに移行するぞと決めてからした事がいくつかあります。
• アプリの起動時の画面をコントロール出来る様にApplication Coordinatorを導入(iOSアプリ設計パターン入門の11章参照)
• 移行中はデータの操作をして欲しくないのでデータの移行中の専用の画面を作成
• Test Flightで外部の人に触ってもらった
の3点です。
Application Coordinator
先述した様にiOSアプリ設計パターン入門の11章に掲載されているApplication Coordinatorを参考にしてアプリ起動直後の画面をユーザーの状態によって柔軟に差し替えが出来る様にしました。(実コードではないですがこんなイメージ)
enum RootState {
case migration // マイグレーションが必要
case register // 会員登録が必要(新規ユーザー)
case index // 通常のトップ
}
final class ApplicationCoordinator {
private weak var window: UIWindow?
init(window: UIWindow) {
self.window = window
}
func handle() -> RootState {
// ユーザーの状態をハンドリングする
}
func start() {
let state = handle()
let rootViewController: UIViewController
switch state {
case .migration:
rootViewController = MigrationViewController()
case .register:
rootViewController = RegisterViewController()
case .index:
rootViewController = IndexViewController()
}
self.window?.rootViewController = rootViewController
}
}
データ移行
移行の流れはこんな感じです。
データ移行にはOperationQueueを利用して各データ毎にOperationを作成して移行する様にしました。(実コードではないですがこんなイメージ)
final class MarkdownOperation: Operation {
private let markdown: Markdown
private let interactor: MarkdownInteractorProtocol
init(markdown: Markdown, interactor: MarkdownInteractorProtocol) {
self.markdown = markdown
self.interactor = interactor
}
var error: Error?
override func main() {
super.main()
interactor.add(markdown) { (result) in
switch result {
case .success:
// finish
case .failed(let error):
self.error = error
// finish with failed
}
}
}
}
var makeOperation: (Markdown, MarkdownInteractorProtocol) -> MarkdownOperation = { (markdown, interactor) in
return MarkdownOperation(markdown, interactor)
}
func migrate() {
let markdowns = try! Realm().objects(Markdown.self)
let operationQueue = OperationQueue()
let dispatchGroup = DispatchGroup()
markdowns.forEach { (markdown) in
dispatchGroup.enter()
let operation = self.makeOperation(markdown, interactor)
operation.completionBlock = {
dispatchGroup.leave()
}
operationQueue.addOperation(operation)
}
dispatchGroup.notify(queue: .main) {
// Finish!!
// restart ApplicationCoordinator!!
}
}
Test Flight Public Link
ここまでやったは良いもの影響範囲が広く不安も大きかったので、Twitterとbosyuを利用してTest Flightでアプリを事前に触ってくれる人を募集してみました。
そうすると一人触ってみたいという方から応募があったので触って頂いた所、見事不具合を発見して頂き、修正してリリースする事が出来ました。本当にありがとうございました🙇🏻♂️
まとめ
こんな感じでTypeのデータをRealmからFirestoreに移行する事が出来ました。ただ、移行といってもTypeが起動された時にクライアントで行うので、Typeを起動せずにRealmを削除したバージョンまで放置してしまうとデータが消えてしまう恐れがあります。データを消したくない人はなるべく早めに使って欲しいです。(プッシュ通知やお知らせ機能を実装してなかったのを少し後悔しています)
また、データがFirestoreにあるという事で今後の横展開のしやすさも随分変わるかなと思います。Webやmacアプリ、androidからも利用出来るので今後の展開も随分やりやすくなる気がします。
正月休みがほぼこの作業で潰れてしまいましたが、Typeが死なずに済んで良かったです。移行がある程度落ち着いたら他のプラットフォーム展開もやりたいですねー