0
\$\begingroup\$

I've implemented a quick and dirty repository to save and insert users to a MongoDB. As you can see, it just inserts one and finds one. Not a rocket.

The thing is I don't really know what are the best practices when implementing a repository layer, how to really arquitect this, etc...

Furthermore, even if it sounds too simple, I'm not sure which variables should be instance properties or not (for example, the database, or the client). Assumed those where convenient, but I'm not sure on the choice.

Any tips are welcome :)

User.kt

data class User (val chatId: String, val username: String, val email: String)

BaseRepository.kt

import com.mongodb.MongoClient
import com.mongodb.MongoClientURI
import com.mongodb.client.MongoDatabase

abstract class BaseRepository<Entity>(databaseUri: String) {
    protected var database: MongoDatabase? = null
    protected var mongoClient: MongoClient? = null

    init {
        val connectionString = MongoClientURI(databaseUri)
        mongoClient = MongoClient(connectionString)
        database = mongoClient?.getDatabase("paybotDB")
    }

    abstract fun insertOne(e: Entity);

    abstract fun findOne(username: String) : Entity;
}

UserRepository.kt

import org.bson.Document
import org.jgmanzano.paybot.entities.User

class UserRepository(databaseUri: String) : BaseRepository<User>(databaseUri) {

    override fun insertOne(user: User){
        val document = Document("username", user.username)
                .append("chatId", user.chatId)
                .append("email", user.email)

        database?.getCollection("users")?.insertOne(document)
    }

    override fun findOne(username: String): User {
        val collection = database?.getCollection("users")
        val userDocument =  collection?.find(Document("username", username))?.first()

        return User(
                userDocument?.get("chatId") as String,
                userDocument?.get("username") as String,
                userDocument?.get("email") as String
        )
    }
}
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

Your properties are nullable even tough they are never null and assigned in the constructor. Declare them as

protected val database: MongoDatabase

This way, the child classes can access them without needing the safe-call operator and also you prevent them from overwriting the property.

Also, both of your methods call database.getCollection("users"). This is a sign that you need to extract the collection to a property rather than the database or client.

Also, it's better to pass a MongoClient or even a MongoDatabase to the constructor because it can be shared between multiple repositories and follows the dependency injection pattern.

Because you now have only one property, you can drop the init block.

abstract class BaseRepository<Entity>(db: MongoDatabase, collectionName: String) {
    protected val collection = db.getCollection(collectionName)

    abstract fun insertOne(e: Entity)

    abstract fun findOne(username: String) : Entity
}

I also got rid of the unnecessary semicolons.

Now to the child class. val userDocument = collection?.find(Document("username", username))?.first() returns a nullable Document?. But in the line below you cast the members to String which will throw an exception if the value was null. To get proper null safety, declare the return type of the method as nullable

abstract fun findOne(username: String) : Entity?

and rewrite the implementation, e.g. like so

override fun findOne(username: String): User? {
    val userDocument = collection.find(Document("username", username)).first() ?: return null

    return User(
            userDocument.get("chatId") as String,
            userDocument.get("username") as String,
            userDocument.get("email") as String
    )
}
\$\endgroup\$
0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.