realm-kotlin icon indicating copy to clipboard operation
realm-kotlin copied to clipboard

[RKOTLIN-612] MongoClient API

Open rorbech opened this issue 1 year ago • 0 comments

This PR adds MongoClient-APIs to query and update remote Atlas App Service data sources directly according to these docs

This overall flow is to obtain a MongoCollection though:

  • App->User->MongoClient->MongoDatabase->MongoCollection

with:

val app = App.create(APP_NAME)
val user = app.emailPasswordAuth.registerUser(email, password).run { app.logIn(email, password) }
val client = user.mongoClient(
    SERVICE_NAME,
    EJson(
        serializersModule = realmSerializerModule(
            setOf(
                ParentCollectionDataType::class,
                ChildCollectionDataType::class
            )
        )
    )
)
val database = client.database("databasename")
val collection: MongoCollection<BsonDocument>  = 
    database.collection("collectionName")
val primaryKey = 
    bsonTypedCollection.insertOne(BsonDocument("""{ "name" : "object-name, "_id" : 5} """)) as Int
val bsonDocument: BsonDocument? = 
    bsonTypedCollection.findOne(BsonDocument("""{ "name" : "object-name, "_id" : 5} """))

You can obtain typed collection instances that will automatically serialize arguments by specifying the object types of the collection.

// Typed collection with automatic serialization
val typedCollection = database.collection<CollectionDataType>("collectionName")

// Insert and object instance and get the primary key
val newDocumentId: Int = typedCollection.insertOne(CollectionDataType("object-name", _id = 6)) as Int

// Querying for object with BsonDocument-filter.
val document: CollectionDataType? = typedCollection.findOne(BsonDocument("name", "object-name"))

If sync is enabled and there is a schema defined for a specific RealmObject you can obtain the MongoCollection directly from the MongoClient. This allows obtaining a MongoCollection directly though:

  • App->User->MongoClient->MongoCollection

with:

val collection = client.collection<CollectionDataType>()

This will use the sync schema definition to map from the RealmObject to the collection of the corresponding type on the server.

Serialization of custom types and links between RealmObjects The typed MongoCollection<T> supports serialization to/from Kotlin Serialization's @Serializable types by default. Since MongoDB represents links only by their primary keys, special serializers needs to be configured if serializing links to/from existing RealmObject model classes. These can be added by supplying a SerializerModule to the ejson-serializer infrastructure when obtaining any of the relevant MongoDB API MongoClient, MongoDatabase or MongoCollection instances with:

// RealmObject-aware serializer module
val ejsonSerializer = EJson(
    serializersModule = realmSerializerModule(
        setOf( ... list of RealmObject model classes ...)
    )
)

// Above serializer can be injected into the hierarchy at the appropriate level and will be inherited from the parent entity if left unspecified
val client = user.mongoClient(<SERVICE_NAME>, ejson)
// or
val database = user.database(<DATABASE_NAME>, ejson)
// or
val collection = database.collection(COLLECTION_NAME, ejson)

The main entry points and main public APIs are:

Input to review One of the main issues with this PR is how to control serialization. Kotlins serialization framework is heavily dependant of compile time derivable information which has made it a bit tricky. Some of the concerns was:

  • Not require extensive annotations throughout the model definition
  • Chosen not to generate serializers from the compiler plugin as it is tedious to produce and difficult to debug
  • Allow usage of standard Kotlin serialization framework serializers for non-RealmObject types
  • Lack of reflection requires some upfront definition of known classes to be able to create object instances from typed links (links in mixed fields) where the schema information can't dictate which class instance to.

The above led me to write a generic serializer that reuses existing EJSON<->BsonDocument serializer from github.com/mongodb/kbson and control serialization from statically stored model metadata. This however needs runtime information of the KClass and cannot easily be added through class annotations. Alternative would have been to write a new decoder that could hold the set of className->RealmObjectCompanion needed to construct typed link instances, as there is no other way to maintain a runtime-context during serialization. Instead I continued the approach from AppConfiguration.ejson of allowing to inject the full StringFormat'er. To ease configuration all realm object serialializers must be added to the StringFormat'er as a speciel SerializerModule constructed from realmSerializerModule.

TODOs

  • [x] Dependent on https://github.com/mongodb/kbson/issues/18 - Await 0.4.0 release

Closes #972

rorbech avatar Dec 05 '23 18:12 rorbech