Introduction
Node.js is the best when it comes to community support and
simplicity.
But, when it comes to the language itself and the builtin
features, Yuk!
I was used to a lot of libraries in Node.js and that what
held me from
going all in server-side Kotlin.
After the introduction of ktor and kgraphql, it was mongoose
the only
library left to have an alternative in kotlin!
So, I made mongoose in Kotlin
Link GitHub - cufyorg/mangaka: A mongoose equivalent for kotlin
I am seeking attention because I need you guys to help me
with it with your ideas and to test the library.
Thank you <3
Starting the connection
The following is how to do the mangaka connection on the
default mangaka instance.
suspend fun foo() {
Mangaka.connect("mongodb://localhost:27017", "Mangaka")
}
Schema Definition
Mangaka was made to mimic the style of mongoose as possible.
Obviously a real mongoose in kotlin is impossible to be maid
since type-unions are not a supported thing in kotlin. But,
the thing that kotlin has instead is extension functions.
So, mangaka took a good advantage of that.
For example, the following with mongoose:
export interface Entity extends Document {
value?: string;
friendId: ObjectId;
list: string[];
}
export const EntitySchema = new Schema<Entity>({
value: {
type: SchemaTypes.String,
default: () => "Initialized",
validate: value => value === "Invalid",
immutable: value => value === "Immutable"
},
friendId: {
type: SchemaTypes.ObjectId,
ref: () => "Entity",
exists: true // using module 'mongoose-extra-validators'
},
list: {
type: [SchemaTypes.String],
default: () => ["FirstElement"]
}
});
export const EntityModel = model("Entity", EntitySchema, "EntityCol");
Has the following equivalent with mangaka:
@Serializable
data class Entity(
var value: String? = null,
var friendId: Id<Entity> = Id("62a49540988ff286898c46b5"),
var list: MutableList<String?> = mutableListOf()
) : Document
val EntitySchema = DocumentSchema(Entity::class) {
field(Entity::value) {
this extends StringSchema()
default { "Initialized" }
validate { it != "Invalid" }
immutable { it == "Frozen" }
}
field(Entity::friendId) {
this extends ObjectIdSchema()
exists { this.model }
}
field(Entity::list) {
this extends ArraySchema()
default { listOf("FirstElement") }
items {
this extends StringSchema()
}
}
}
val EntityModel = Model("Entity", EntitySchema, "EntityCol")
Model Usage
The model is a tricky one, since in mongoose the model has
mongoose internal stuff with javascript specific wizardry
which is not even needed in mangaka. But, still the
developer experience the first priority here in mangaka.
So, the code appears the same but internal it is not.
For example, the following code with mangaka:
async function foo() {
const entity = new EntityModel()
await EntityModel.create({
value: "SomeValue"
})
await EntityModel.findOne({
value: "SomeValue"
})
}
Has the following equivalent with mangaka:
suspend fun foo() {
val entity = EntityModel()
EntityModel.create(
Entity::value eq "SomeValue"
)
EntityModel.findOne(
Entity::value eq "SomeValue"
)
}
Document Usage
One of the best things about mongoose is the interface
Document
that has shortcuts for saving, deleting and
validating values without the need to know the client,
database or collection they came from.
This is one of the easiest to mimic features that was
implemented in mangaka.
For example, the following with mongoose:
async function foo(document: Document) {
await document.validate()
await document.save()
await document.remove()
}
Has the following equivalent with mangaka:
suspend fun foo(document: Document) {
document.validate()
document.save()
document.remove()
}
Static and Member functions
This feature doesn’t event need to be implemented in
mangaka.
The best thing about kotlin extension functions is that
anyone can write their own extension function.
The following is an example for extension functions:
// example virtual value
val Entity.firstElement: String?
get() = list.firstOrNull()
// example member function
suspend fun Entity.findFriend(): Entity? {
return model.findOne(Filters.eq("_id", friendId))
}
// example static function
suspend fun Model<Entity>.findByValue(value: String): Entity? {
return findOne(Entity::value eq value)
}
Extension Order
When applying an extension to a schema. The applying order
is very important.
The following are examples of the
importance of applying order:
- The
ignore
(andimmutable
) extension is expected to be
before therequired
anddefault
extensions. Since the
ignore
extension will emitnull
which therequired
anddefault
extensions will receive thenull
and think
the value is missing. - The
extends
extension is expected to be the first
extension applied. Since, theextends
extension
will override ALL the schema functions.