Firestore良さそうですよね。ローカルで動作を試せるLocal Emulatorがあるので気軽に試せてさらによさそうです。JVM環境で接続する例があんまり見当たらなかったのでメモします。
firestore local emulatorの準備をする
とりあえずインストールして、
firebase setup:emulators:firestore
次のコマンドで起動できればOKです。
firebase serve --only firestore
firebaseのバージョンとかでいろいろエラー出たりするのでググったりしてなんとか入れてください。
firestore local emulatorをポートを指定して起動する
firebase serve --only firestore
ではポートが8080でしか起動できません、そこで次のコマンドでLocal Emulatorを起動します。
gcloud beta emulators firestore start --host-port=localhost:8812
次のような出力が得られれば成功です。
[firestore] API endpoint: http://localhost:8812
[firestore] If you are using a library that supports the FIRESTORE_EMULATOR_HOST environment variable, run:
[firestore]
[firestore] export FIRESTORE_EMULATOR_HOST=localhost:8812
[firestore]
[firestore] Dev App Server is now running.
[firestore]
application.confにlocal emulatorの設定を書く
Ktorからlocalhost:8812のLocal Emulatorに接続するために、application.confに設定を書きます。
特にfirebaseのための書き方などはないので、適当に書きます。
ktor {
firebase {
project_id = $PROJECT_ID
firestore {
emulator {
host = localhost
port = 8812
}
}
}
}
Ktorから設定を読み込む
application.confの内容はApplication.environment
から読み込めます。
fun Application.module() {
val firestoreEmulatorHost =
environment.config.propertyOrNull("ktor.firebase.firestore.emulator.host")?.getString()
val firestoreEmulatorPort =
environment.config.propertyOrNull("ktor.firebase.firestore.emulator.port")?.getString()?.toInt()
val projectId =
environment.config.propertyOrNull("ktor.firebase.project_id")?.getString() ?: throw IllegalStateException(
"firebase project id not found"
)
}
Firestoreを初期化する
FirestoreでLocal Emulatorに接続する場合はFirestoreOptionsを使って直接Firestoreのインスタンスを作ります。
val firestore = FirestoreOptions
.newBuilder()
.setHost("$firestoreEmulatorHost:$firestoreEmulatorPort")
.build()
.service
本物につなぐ場合も考慮して次のようなobjectを作りました。
import com.google.auth.oauth2.GoogleCredentials
import com.google.cloud.firestore.Firestore
import com.google.cloud.firestore.FirestoreOptions
import com.google.firebase.FirebaseApp
import com.google.firebase.FirebaseOptions
import com.google.firebase.cloud.FirestoreClient
object FirebaseInitializer {
data class FirestoreEmulator(val firestoreEmulatorHost: String, val firestoreEmulatorPort: Int) {
fun toUrl() = "$firestoreEmulatorHost:$firestoreEmulatorPort"
}
fun firestore(
projectId: String,
emulator: FirestoreEmulator?
): Firestore {
return if (emulator != null) {
FirestoreOptions
.newBuilder()
.setHost(emulator.toUrl())
.build()
.service
} else {
val credentials = GoogleCredentials.getApplicationDefault()
val options = FirebaseOptions.Builder()
.setCredentials(credentials)
.setProjectId(projectId)
.build()
FirebaseApp.initializeApp(options)
FirestoreClient.getFirestore()
}
}
}
こんな感じでFirestoreを取り出せます。
val firestore = FirebaseInitializer.firestore(
projectId,
if (firestoreEmulatorHost != null && firestoreEmulatorPort != null) {
FirebaseInitializer.FirestoreEmulator(firestoreEmulatorHost, firestoreEmulatorPort)
} else {
null
}
)
やったね。
おまけ: KoinでInjection
せっかくなのでKoinでFirestoreインスタンスをinjectできるようにします。
とりあえずKoinをdependenciesに追加し、
dependencies {
implementation "org.koin:koin-core:2.0.1"
implementation "org.koin:koin-ktor:2.0.1"
}
モジュールを実装します。Applicationの拡張関数にしとくと楽です。
import io.ktor.application.Application
import org.koin.dsl.module
fun Application.applicationModule() = module {
single {
val firestoreEmulatorHost =
environment.config.propertyOrNull("ktor.firebase.firestore.emulator.host")?.getString()
val firestoreEmulatorPort =
environment.config.propertyOrNull("ktor.firebase.firestore.emulator.port")?.getString()?.toInt()
val projectId =
environment.config.propertyOrNull("ktor.firebase.project_id")?.getString() ?: throw IllegalStateException(
"firebase project id not found"
)
FirebaseInitializer.firestore(
projectId,
if (firestoreEmulatorHost != null && firestoreEmulatorPort != null) {
FirebaseInitializer.FirestoreEmulator(firestoreEmulatorHost, firestoreEmulatorPort)
} else {
null
}
)
}
}
あとはアプリケーションの初期化時にKoinをinstallしてモジュールを設定すれば、適当に取り出せるようになります。
fun Application.module() {
install(Koin) {
modules(
applicationModule()
)
}
routing {
get("/firestore") {
val firestore: Firestore by this@routing.inject()
}
}
まとめ
Firestore Local Emulator良さそう。admin的な画面って内蔵されてないんだろうか?ある気がする。わからない。