Skip to content

Commit

Permalink
DOCSP-36217: polymorphic serialization (#163)
Browse files Browse the repository at this point in the history
* DOCSP-36217: polymorphic serialization

* wip

* wip

* wip

* fix error in codectest

* MM PR fixes 1
  • Loading branch information
rustagir authored Jul 10, 2024
1 parent cbc3f1a commit b5a5af9
Show file tree
Hide file tree
Showing 10 changed files with 171 additions and 58 deletions.
2 changes: 1 addition & 1 deletion examples/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ dependencies {
implementation("com.github.luben:zstd-jni:1.5.5-4")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:1.5.1")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0")
implementation("org.mongodb:bson-kotlinx:4.10.0-alpha1")
implementation("org.mongodb:bson-kotlinx:5.1.1")
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0")
}

Expand Down
2 changes: 1 addition & 1 deletion examples/src/test/kotlin/CodecTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ internal class CodecTest {

init {
powerStatusCodec = registry[PowerStatus::class.java]
integerCodec = registry.get(Int::class.java)
integerCodec = IntegerCodec()
}

override fun encode(writer: BsonWriter, value: Monolight, encoderContext: EncoderContext) {
Expand Down
64 changes: 64 additions & 0 deletions examples/src/test/kotlin/KotlinXSerializationTest.kt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@

import com.mongodb.client.model.Filters
import com.mongodb.client.model.Filters.eq
import com.mongodb.kotlin.client.coroutine.MongoClient
import config.getConfig
Expand Down Expand Up @@ -35,6 +36,7 @@ import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import java.util.Date
import kotlin.test.assertTrue

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
internal class KotlinXSerializationTest {
Expand Down Expand Up @@ -178,5 +180,67 @@ internal class KotlinXSerializationTest {
collection.drop()
}

// :snippet-start: polymorphic-dataclasses
@Serializable
sealed interface Person {
val name: String
}

@Serializable
data class Student(
@Contextual
@SerialName("_id")
val id: ObjectId,
override val name: String,
val grade: Int,
) : Person

@Serializable
data class Teacher(
@Contextual
@SerialName("_id")
val id: ObjectId,
override val name: String,
val department: String,
) : Person
// :snippet-end:

@Test
fun polymorphicSerializationTest() = runBlocking {

// :snippet-start: polymorphic-example
val collection = database.getCollection<Person>("school")

val teacherDoc = Teacher(ObjectId(), "Vivian Lee", "History")
val studentDoc = Student(ObjectId(), "Kate Parker", 10)

collection.insertOne(teacherDoc)
collection.insertOne(studentDoc)

println("Retrieving by using data classes")
collection.withDocumentClass<Teacher>()
.find(Filters.exists("department"))
.first().also { println(it) }

collection.withDocumentClass<Student>()
.find(Filters.exists("grade"))
.first().also { println(it) }

println("\nRetrieving by using Person interface")
val resultsFlow = collection.withDocumentClass<Person>().find()
resultsFlow.collect { println(it) }

println("\nRetrieving as Document type")
val resultsDocFlow = collection.withDocumentClass<Document>().find()
resultsDocFlow.collect { println(it) }
// :snippet-end:

if (resultsFlow != null) {
assertTrue(resultsFlow.firstOrNull() is Teacher)
}

collection.drop()
}

}

21 changes: 0 additions & 21 deletions examples/src/test/kotlin/TlsTest.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@

import com.mongodb.ConnectionString
import com.mongodb.MongoClientSettings
import com.mongodb.connection.netty.NettyStreamFactoryFactory
import com.mongodb.kotlin.client.coroutine.MongoClient
import config.getConfig
import io.netty.handler.ssl.SslContextBuilder
Expand Down Expand Up @@ -87,25 +86,5 @@ internal class TlsTest {
assertEquals(sslContext, settings.sslSettings.context)
}

@Test
fun nettyTlsConfigurationTest() {
// :snippet-start: netty-tls-configuration
val sslContext = SslContextBuilder.forClient()
.sslProvider(SslProvider.OPENSSL)
.build()
val settings = MongoClientSettings.builder()
.applyToSslSettings { builder -> builder.enabled(true) }
.streamFactoryFactory(
NettyStreamFactoryFactory.builder()
.sslContext(sslContext)
.build()
)
.build()
val mongoClient = MongoClient.create(settings)
// :snippet-end:
mongoClient.close()
assertEquals(true, settings.sslSettings.isEnabled)
assertEquals(true, settings.streamFactoryFactory is NettyStreamFactoryFactory)
}
}
// :replace-end:
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ class MonolightCodec(registry: CodecRegistry) : Codec<Monolight> {

init {
powerStatusCodec = registry[PowerStatus::class.java]
integerCodec = registry.get(Int::class.java)
integerCodec = IntegerCodec()
}

override fun encode(writer: BsonWriter, value: Monolight, encoderContext: EncoderContext) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
@Serializable
sealed interface Person {
val name: String
}

@Serializable
data class Student(
@Contextual
@SerialName("_id")
val id: ObjectId,
override val name: String,
val grade: Int,
) : Person

@Serializable
data class Teacher(
@Contextual
@SerialName("_id")
val id: ObjectId,
override val name: String,
val department: String,
) : Person
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
val collection = database.getCollection<Person>("school")

val teacherDoc = Teacher(ObjectId(), "Vivian Lee", "History")
val studentDoc = Student(ObjectId(), "Kate Parker", 10)

collection.insertOne(teacherDoc)
collection.insertOne(studentDoc)

println("Retrieving by using data classes")
collection.withDocumentClass<Teacher>()
.find(Filters.exists("department"))
.first().also { println(it) }

collection.withDocumentClass<Student>()
.find(Filters.exists("grade"))
.first().also { println(it) }

println("\nRetrieving by using Person interface")
val resultsFlow = collection.withDocumentClass<Person>().find()
resultsFlow.collect { println(it) }

println("\nRetrieving as Document type")
val resultsDocFlow = collection.withDocumentClass<Document>().find()
resultsDocFlow.collect { println(it) }
30 changes: 0 additions & 30 deletions source/fundamentals/connection/tls.txt
Original file line number Diff line number Diff line change
Expand Up @@ -239,36 +239,6 @@ object to the builder in the ``applyToSslSettings()`` lambda:
.. literalinclude:: /examples/generated/TlsTest.snippet.custom-tls-configuration.kt
:language: kotlin

Customize TLS/SSL Configuration through the Netty SslContext
------------------------------------------------------------

If you use the driver with `Netty <https://netty.io/>`__ for network IO,
you have an option to plug an alternative TLS/SSL protocol implementation
provided by Netty.

.. code-block:: kotlin
:copyable: true

import com.mongodb.MongoClientSettings
import com.mongodb.client.MongoClient
import com.mongodb.connection.netty.NettyStreamFactoryFactory
import io.netty.handler.ssl.SslContext
import io.netty.handler.ssl.SslContextBuilder
import io.netty.handler.ssl.SslProvider

.. note::

The driver tests with Netty version ``{+nettyVersion+}``

To instruct the driver to use `io.netty.handler.ssl.SslContext <https://netty.io/4.1/api/io/netty/handler/ssl/SslContext.html>`__,
use the `NettyStreamFactoryFactory.Builder.sslContext <{+api+}/apidocs/mongodb-driver-core/com/mongodb/connection/netty/NettyStreamFactoryFactory.Builder.html#sslContext(io.netty.handler.ssl.SslContext)>`__
method. See the method documentation for details about the different `io.netty.handler.ssl.SslProvider <https://netty.io/4.1/api/io/netty/handler/ssl/SslProvider.html>`__
variants the driver supports and the implications of using them.

.. literalinclude:: /examples/generated/TlsTest.snippet.netty-tls-configuration.kt
:language: kotlin


Online Certificate Status Protocol (OCSP)
-----------------------------------------

Expand Down
57 changes: 56 additions & 1 deletion source/fundamentals/data-formats/serialization.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Kotlin Serialization
:values: reference

.. meta::
:keywords: code example, data model, conversion
:keywords: code example, data model, conversion, polymorphism

.. contents:: On this page
:local:
Expand Down Expand Up @@ -98,6 +98,8 @@ dependencies to your project by using the :guilabel:`Gradle` and
<version>{+full-version+}</version>
</dependency>

.. _kotlin-data-class-annotation:

Annotate Data Classes
---------------------

Expand Down Expand Up @@ -246,3 +248,56 @@ see the following API documentation:
- `KotlinSerializerCodec.create() <{+api+}/apidocs/bson-kotlinx/bson-kotlinx/org.bson.codecs.kotlinx/-kotlin-serializer-codec/-companion/create.html>`__
- `BsonConfiguration <{+api+}/apidocs/bson-kotlinx/bson-kotlinx/org.bson.codecs.kotlinx/-bson-configuration/index.html>`__

.. _kotlin-polymorphic:

Polymorphic Serialization
-------------------------

The {+driver-short+} natively supports serialization and deserialization
of polymorphic classes. When you mark a sealed interface and data
classes that inherit that interface with the ``@Serializable``
annotation, the driver uses a ``KSerializer`` implementation to handle
conversion of your types to and from BSON.

When you insert an instance of a polymorphic data class into MongoDB,
the driver adds the field ``_t``, the
discriminator field. The value of this field is the data class name.

Polymorphic Data Classes Example
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The following example creates an interface and two data classes that
inherit that interface. In the data classes, the ``id`` field is marked
with the annotations described in the
:ref:`kotlin-data-class-annotation` section:

.. literalinclude:: /examples/generated/KotlinXSerializationTest.snippet.polymorphic-dataclasses.kt
:language: kotlin

Then, you can perform operations with data classes as usual. The
following example parametrizes the collection with the ``Person``
interface, then performs operations with the polymorphic classes
``Teacher`` and ``Student``. When you retrieve documents, the driver
automatically detects the type based on the discriminator value and
deserializes them accordingly.

.. io-code-block::
:copyable: true

.. input:: /examples/generated/KotlinXSerializationTest.snippet.polymorphic-example.kt
:language: kotlin

.. output::
:language: console

Retrieving by using data classes
Teacher(id=..., name=Vivian Lee, department=History)
Student(id=..., name=Kate Parker, grade=10)

Retrieving by using Person interface
Teacher(id=..., name=Vivian Lee, department=History)
Student(id=..., name=Kate Parker, grade=10)

Retrieving as Document type
Document{{_id=..., _t=Teacher, name=Vivian Lee, department=History}}
Document{{_id=..., _t=Student, name=Kate Parker, grade=10}}
5 changes: 2 additions & 3 deletions source/whats-new.txt
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,8 @@ Improvements in 5.1
New Features in 5.1
~~~~~~~~~~~~~~~~~~~

- Support for polymorphic serialization.

.. TODO add polymorphic serialization content
- Support for polymorphic serialization. To learn more, see the
:ref:`kotlin-polymorphic` section of the Kotlin Serialization guide.

- Introduces the ``serverMonitoringMode`` connection URI option. To
learn more, see the :ref:`connection-options` guide.
Expand Down

0 comments on commit b5a5af9

Please sign in to comment.