Defining the data model
Models
Metadata
xref:immutability.adoc |
ID and Indexes
A model may have one or more named indexes, to allow you to search and/or order by a specific value.
A model must have one unique ID, which can be of any type.
This ID defines the default ordering of the models inside the collections.
In essence, the ID works exactly like an index, except that it is unnamed.
You can use UUID.randomUUID()
if your model does not have a unique value.
Indexes and IDs can be composite, which means that they can contain multiple values. A composite index allows you to:
-
Get models ordered by first value, then second, then third, then…
-
Look for all models with the first value, then second, then third, then…
With annotations
When targeting only the JVM, you can simply use annotations:
data class User(
@Id val uid: String,
val firstName: String,
@Index("lastName") val lastName: String
)
When using @Id or @Index , Kodein-DB converts String values to byte array using the ASCII charset.
Therefore, only ASCII characters are allowed.
|
Using this configuration, when getting all users by index "lastName"
, they will be ordered first by lastName
, then by uid
.
If you want the results to be ordered by lastName
then firstName
(then uid
), you can use a composite index:
data class User(
@Id val uid: String,
val firstName: String,
val lastName: String
) {
@Index("name") fun nameIndex() = listOf(lastName, firstName)
}
With the model
The model itself can define its metadata by implementing either the Metadata
or HasMetadata
interface:
data class User(
override val id: String, (1)
val firstName: String,
val lastName: String
) : Metadata {
override fun indexes() = indexSet("lastName" to listOf(lastName, firstName)) (2)
}
1 | The id property override is mandatory |
2 | The indexes function override is optional (no index by default) |
data class User(
val id: String,
val firstName: String,
val lastName: String
) : HasMetadata {
override fun getMetadata(db: ModelDB, vararg options: Options.Write) =
Metadata(id, "lastName" to listOf(lastName, firstName))
}
With an extractor
If you don’t own the models, or if you don’t want to mark them for Kodein-DB, you can use register a MetadataExtractor
when you open the database:
val db = DB.open("path/to/db",
MetadataExtractor {
when (it) {
is User -> Metadata(it.id, "lastName" to listOf(it.lastName, it.firstName))
else -> error("Unknown model $it")
}
}
)
Using ID as an index
If we consider the User
model we have just defined, we have defined the ID to be a UUID, meaning that the order in which they will be stored and retrieved is completely random.
Because the ID must be unique, we cannot use the name to be the ID.
However, we can create a composite ID.
Consider this updated model:
data class User(
val uid: String,
val firstName: String,
val lastName: String
) : Metadata {
override val id get() = listOf(lastName, firstName, uid)
}
Because uid
is unique, the tuple (lastName, firstName, uid)
is unique (if only because it contains uid
).
Therefore, the id
property is always unique, but the order in which the models will be stored are defined first by lastName
, then by firstName
, then only by id
.
While using a composite ID can be very useful, it makes the creation of key from ID values more complex. |
Key & References
If a model contains another model, it will be serialized into the same document.
If you need to reference another document, then you need to store a Key
:
data class User(
override val id: String,
val name: Name, (1)
val address: Key<Address> (2)
) : Metadata {
override fun indexes() = indexSet("lastName" to listOf(name.last, name.first))
}
1 | Will be included as part of this model’s document. |
2 | References another model with its own document. |
Polymorphism
The problem
By default, Kodein-DB inserts each model in the document collection that corresponds to its real type.
Considering the following insertions:
open class Person(@Id val name: String)
class Child(name: String, val parents: List<Key<Person>>): Person(name)
val janeKey = db.put(Person("Jane"))
val johnKey = db.put(Person("John"))
val parents = listOf(janeKey, johnKey)
db.put(Child("Jill", parents))
db.put(Child("Jack", parents))
Using the preceding code, there will be two different collections, one Person
, one Child
, meaning if you were to look for all Person
models, you would only get Jane & John.
Children are person too (even when they keep asking you when’s the end of this documentation…) so, you probably want to put every Child
model into the Person
collection.
To do that, you need to enable polymorphism: the fact that a collection can hold multiple types of models.
JVM only annotation
The simpler way to define a polymorphic document is to use the @Polymorphic
annotation.
However, as usual for annotations, it only works for the JVM.
@Polymorphic(Person::class) (1)
class Child(name: String, val parents: List<Key<Person>>): Person(name)
1 | This @Polymorphic annotation instructs Kodein-DB to put Child models into the Person collection. |
Type Table
In Kodein-DB, the Type Table is responsible for defining which model type belongs to which collection.
Using a Type Table is compatible with multiplatform! |
You can define a TypeTable
when opening the database:
val db = DB.open("path/to/db",
TypeTable {
root<Person>() (1)
.sub<Child>() (2)
}
)
1 | Defines the root collection Person . |
2 | Defines that all Child models will be put in the Person collection. |