Kotlin SDK 1.4.4 adds experimental support for Couchbase transactions

Hi! Here’s a something fun you can try with Couchbase Kotlin SDK 1.4.4.

Caveat: The transaction API in Kotlin is still “volatile” (subject to change without notice).

In this contrived example, there are two documents with IDs A and B. The value of each document is an integer representing a balance. The example shows how to write a transaction that modifies both documents, transferring value from one balance to another.

Transactional functionality is exposed via Cluster.transactions, which is an instance of Transactions. We’ll start by writing an extension function on Transactions:

suspend fun Transactions.transfer(
    collection: Collection,
    sourceDocId: String,
    destDocId: String,
    value: Int
) {
    require(value >= 0) { "transfer value must be non-negative" }

    println("transferring $value from $sourceDocId to $destDocId")

    this.run { // not to be confused with kotlin.run()

        // This lambda may be executed multiple times
        // if there are conflicts between transactions.

        val source = get(collection, sourceDocId)
        val dest = get(collection, destDocId)

        val newSourceValue = source.contentAs<Int>() - value
        val newDestValue = dest.contentAs<Int>() + value

        replace(source, newSourceValue)

        // Throwing any exception triggers rollback.
        if (Random.nextBoolean()) throw RuntimeException("Simulated error")
        require(newSourceValue >= 0) { "transfer would result in negative source value" }
        require(newDestValue >= 0) { "transfer would result in dest value overflow" }

        replace(dest, newDestValue)
    }
}

(Alternatively, you could call cluster.transactions.run directly; using an extension function is just a nice way to signal that a method uses a transaction.)

Now here’s some code to exercise the transfer function. Plug in your own values for connectionString, username, password, and bucketName:

val cluster = Cluster.connect(connectionString, username, password)
try {
    val bucket = cluster.bucket(bucketName)
    val collection = bucket.defaultCollection()

    runBlocking {
        // initialize the balances
        collection.upsert("A", 1000)
        collection.upsert("B", 0)

        // simulate 100 concurrent balance transfers
        coroutineScope {
            repeat(100) {
                launch {
                    try {
                        cluster.transactions.transfer(
                            collection = collection,
                            sourceDocId = "A",
                            destDocId = "B",
                            value = Random.nextInt(10) + 1
                        )
                    } catch (e: Exception) {
                        println(e.message)
                    }
                }
            }
        }

        println("Balances after")
        println("A: " + collection.get("A").contentAs<Int>())
        println("B: " + collection.get("B").contentAs<Int>())
    }
} finally {
    runBlocking { cluster.disconnect() }
}

After running this code, you’ll see the final balances always add up to the original 1,000.

As mentioned at the start, it’s still early days for Couchbase transactions in the Kotlin SDK. We’ll share real documentation and more info in the future. In the meantime, we appreciate your feedback.

Thanks,
David

2 Likes