{"id":13174,"date":"2022-05-03T09:49:42","date_gmt":"2022-05-03T16:49:42","guid":{"rendered":"https:\/\/www.couchbase.com\/blog\/?p=13174"},"modified":"2023-05-22T08:01:08","modified_gmt":"2023-05-22T15:01:08","slug":"couchbase-kotlin-api-released","status":"publish","type":"post","link":"https:\/\/www.couchbase.com\/blog\/couchbase-kotlin-api-released\/","title":{"rendered":"Designing the Couchbase Kotlin API\u00a0"},"content":{"rendered":"<p><span style=\"font-weight: 400\">I\u2019m pleased to announce the GA release of Couchbase Kotlin SDK version 1.0. In truth, I\u2019m over the moon. This project has been a labor of love. After working with Java for decades, I have a new favorite language.<\/span><\/p>\n<p><span style=\"font-weight: 400\">In this article, I\u2019ll say some nice things about Kotlin, and then show how to use the Couchbase Kotlin SDK to connect to the Capella Database-as-a-Service. Finally, I\u2019ll share some design decisions that shaped the SDK\u2019s public API. I hope you\u2019ll stick around for that last part, especially if you\u2019re designing the API of your own Kotlin library.<\/span><\/p>\n<h2><span style=\"font-weight: 400\">Why Kotlin?<\/span><\/h2>\n<p><span style=\"font-weight: 400\">Kotlin changed the way we think about asynchronous programming on the JVM. Kotlin\u2019s coroutines and suspending functions are evidence that reactive programming might be a stepping stone to something better, something that doesn\u2019t require sacrificing readable code at the altar of scalability. Kotlin has shown there\u2019s a better way to write high-performance asynchronous code, and we don\u2019t need to wait for Project Loom\u2019s fibers and continuations.<\/span><\/p>\n<h2><span style=\"font-weight: 400\">Capella + Kotlin<\/span><\/h2>\n<p><a href=\"https:\/\/cloud.couchbase.com\" target=\"_blank\" rel=\"noopener\"><span style=\"font-weight: 400\">Couchbase Capella<\/span><\/a><span style=\"font-weight: 400\"> is the Database-as-a-Service (DBaaS) for Couchbase Server. It\u2019s solid tech, and when I signed up for a free trial a few weeks ago, the process was completely painless.<\/span><\/p>\n<p><span style=\"font-weight: 400\">Let\u2019s say you have a Capella trial cluster and you\u2019ve added your IP to the Allow List, and you\u2019ve created a database user who can read the pre-installed <em>travel-sample<\/em> bucket. Here\u2019s how to connect to your cluster using the Kotlin SDK:\u00a0<\/span><\/p>\n<pre class=\"decode-attributes:false lang:kotlin decode:true\"> \u00a0\u00a0\u00a0val address = \"--your-cluster--.cloud.couchbase.com\"\r\n\u00a0\u00a0\u00a0\u00a0val cluster = Cluster.connect(\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0connectionString = \"couchbases:\/\/$address\",\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0username = \"harpo\",\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0password = \"swordfish\",\r\n\u00a0\u00a0\u00a0\u00a0)<\/pre>\n<p><span style=\"font-weight: 400\">Once you have a Cluster object, you can execute a N1QL query:<\/span><\/p>\n<pre class=\"decode-attributes:false lang:kotlin decode:true\"> \u00a0\u00a0\u00a0val queryResult = cluster\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0.query(\"SELECT * FROM `travel-sample` LIMIT 3\")\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0.execute()\r\n\u00a0\u00a0\u00a0\u00a0queryResult.rows.forEach { println(it) }\r\n\u00a0\u00a0\u00a0\u00a0println(queryResult.metadata)<\/pre>\n<p><span style=\"font-weight: 400\">Or get a reference to a Collection and read a specific document:<\/span><\/p>\n<pre class=\"decode-attributes:false lang:kotlin decode:true\"> \u00a0\u00a0\u00a0val collection = cluster\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0.bucket(\"travel-sample\")\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0.defaultCollection()\r\n\r\n\u00a0\u00a0\u00a0\u00a0val getResult = collection.get(\"airline_10\")\r\n\u00a0\u00a0\u00a0\u00a0println(getResult)\r\n\u00a0\u00a0\u00a0\u00a0println(getResult.contentAs&lt;Map&lt;String, Any?&gt;&gt;())<\/pre>\n<p><span style=\"font-weight: 400\">The complete version of this example is included in the <\/span><a href=\"https:\/\/docs.couchbase.com\/kotlin-sdk\/current\/hello-world\/overview.html\" target=\"_blank\" rel=\"noopener\"><span style=\"font-weight: 400\">Kotlin SDK documentation<\/span><\/a><span style=\"font-weight: 400\">, along with several others.<\/span><\/p>\n<h2><span style=\"font-weight: 400\">SDK API design decisions<\/span><\/h2>\n<p><span style=\"font-weight: 400\">The rest of this article is devoted to sharing some notes on the decisions we made while designing the public API of the Couchbase Kotlin SDK. In some cases, I\u2019ll be comparing the Kotlin SDK to its older sibling, the Java SDK.<\/span><\/p>\n<h3><span style=\"font-weight: 400\">Extension vs independent SDK<\/span><\/h3>\n<p><span style=\"font-weight: 400\">The Couchbase Kotlin SDK depends on the same <em>core-io<\/em> library as the Java SDK but does not depend on the Java SDK.<\/span><\/p>\n<h4><span style=\"font-weight: 400\">Rejected alternatives<\/span><\/h4>\n<p><span style=\"font-weight: 400\">We considered supporting Kotlin by supplying extension functions for classes from the Java SDK. Unfortunately, some design decisions we made for the Java SDK did not translate well to Kotlin, and could not be compensated for just with extension functions.<\/span><\/p>\n<p><span style=\"font-weight: 400\">We also considered providing a full native Kotlin API wrapper that simply delegated to the Java SDK, but we were concerned that having two versions of all the classes (one for Kotlin, one for Java) would be confusing for users.<\/span><\/p>\n<h3><span style=\"font-weight: 400\">Suspend or bust!<\/span><\/h3>\n<p><span style=\"font-weight: 400\">The Kotlin SDK does not provide a blocking API; the methods that do network I\/O are all <em>suspend<\/em> functions.<\/span><\/p>\n<h4><span style=\"font-weight: 400\">Rejected alternatives<\/span><\/h4>\n<p><span style=\"font-weight: 400\">We considered adding &#8220;blocking&#8221; variants of Cluster, Bucket, Scope, Collection, etc. but this seems like something users can do themselves with very little effort just by wrapping calls to the suspend functions with <em>runBlocking<\/em>.<\/span><\/p>\n<h3><span style=\"font-weight: 400\">Optional parameters<\/span><\/h3>\n<p><span style=\"font-weight: 400\">Since Java does not have optional parameters, the Couchbase Java SDK emulates them with an &#8220;options block&#8221; constructed using the builder pattern.\u00a0<\/span><\/p>\n<p><span style=\"font-weight: 400\">In the following example, <em>withExpiry<\/em>\u00a0is an optional boolean parameter that defaults to false. The code snippets show a call site where the developer wants to pass true instead.<\/span><\/p>\n<p><span style=\"font-weight: 400\">Java:<\/span><\/p>\n<pre class=\"decode-attributes:false lang:java decode:true\"> \u00a0\u00a0\u00a0GetOptions options = GetOptions.getOptions().withExpiry(true);\r\n\u00a0\u00a0\u00a0\u00a0collection.get(documentId, options);<\/pre>\n<p><span style=\"font-weight: 400\">The Kotlin SDK takes advantage of Kotlin&#8217;s native support for default parameters:<\/span><\/p>\n<p><span style=\"font-weight: 400\">Kotlin:<\/span><\/p>\n<pre class=\"decode-attributes:false lang:kotlin decode:true \">\u00a0\u00a0\u00a0\u00a0collection.get(documentId, withExpiry = true)<\/pre>\n<h4><span style=\"font-weight: 400\">Rejected alternatives<\/span><\/h4>\n<p><span style=\"font-weight: 400\">We considered using method-specific option blocks for Kotlin as well, which would have looked something like:<\/span><\/p>\n<pre class=\"decode-attributes:false lang:kotlin decode:true \">\u00a0\u00a0\u00a0\u00a0collection.get(documentId, GetOptions(withExpiry = true))<\/pre>\n<p><span style=\"font-weight: 400\">This was rejected because it was clunky for users and difficult to maintain for SDK developers (consider the impact of adding a new option common to all methods).<\/span><\/p>\n<p><span style=\"font-weight: 400\">We also considered using a lambda\/mini-DSL for options, which would have looked something like:<\/span><\/p>\n<pre class=\"decode-attributes:false lang:kotlin decode:true\"> \u00a0\u00a0\u00a0collection.get(documentId) {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0withExpiry = true\r\n\u00a0\u00a0\u00a0\u00a0}<\/pre>\n<p><span style=\"font-weight: 400\">This was the most tempting of the rejected alternatives because it would have been excellent for binary compatibility (method signatures wouldn&#8217;t change as new optional parameters are added). It also &#8220;feels like Kotlin.&#8221; It was rejected because:<\/span><\/p>\n<ul>\n<li style=\"list-style-type: none\">\n<ul>\n<li style=\"font-weight: 400\"><span style=\"font-weight: 400\">IDE code completion did not provide the same level of guidance for DSLs as for method parameters (although IDEs will likely improve over time).<\/span><\/li>\n<li style=\"font-weight: 400\"><span style=\"font-weight: 400\">We wanted to reserve the final lambda parameter for other purposes.<\/span><\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<h3><span style=\"font-weight: 400\">Common parameters<\/span><\/h3>\n<p><span style=\"font-weight: 400\">Some optional parameters are common to many methods in the Couchbase SDK API. Examples include timeout duration, retry strategy, and tracing span.<\/span><\/p>\n<p><span style=\"font-weight: 400\">In Java, these common options are properties of a CommonOptions base class which all the method-specific option blocks extend; to the user, they look no different than other parameters:<\/span><\/p>\n<p><span style=\"font-weight: 400\">Java:<\/span><\/p>\n<pre class=\"lang:java decode:true\">   GetOptions options = GetOptions.getOptions()\r\n        .withExpiry(true)\r\n        .timeout(Duration.ofSeconds(10));\r\n    collection.get(documentId, options);<\/pre>\n<p><span style=\"font-weight: 400\">In Kotlin, we take a different approach that balances the convenience of default parameters with some pragmatic concessions for maintainability and binary compatibility. The common parameters are represented by an option block called <em>CommonOptions<\/em>. Methods accept an optional parameter whose default value is a <em>CommonOptions<\/em> instance representing the default options. Overriding the defaults looks like this:<\/span><\/p>\n<p><span style=\"font-weight: 400\">Kotlin:<\/span><\/p>\n<pre class=\"decode-attributes:false lang:kotlin decode:true\"> \u00a0\u00a0\u00a0collection.get(\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0documentId,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0common = CommonOptions(timeout = 10.seconds),\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0withExpiry = true,\r\n\u00a0\u00a0\u00a0\u00a0)<\/pre>\n<h4>Rejected alternatives<\/h4>\n<p><span style=\"font-weight: 400\">We considered treating the common options as normal parameters, like this:<\/span><\/p>\n<pre class=\"decode-attributes:false lang:kotlin decode:true\"> \u00a0\u00a0\u00a0collection.get(\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0documentId,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0timeout = 10.seconds,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0withExpiry = true,\r\n\u00a0\u00a0\u00a0\u00a0)<\/pre>\n<p><span style=\"font-weight: 400\">While this would certainly be pleasant for users, it was rejected because adding or removing a common parameter would require changing the signature of nearly every public method in the code base, and would make maintaining binary compatibility quite a chore. We looked into automating this process using code generation, but the complexity of that approach seemed to outweigh the value.<\/span><\/p>\n<p><span style=\"font-weight: 400\">In the end, we opted to use the <em>CommonOptions<\/em> class as a kind of API bulkhead for isolating maintenance issues related to common options.<\/span><\/p>\n<h3><span style=\"font-weight: 400\">Binary compatibility<\/span><\/h3>\n<p><span style=\"font-weight: 400\">These decisions about common and optional parameters have the following implications for binary compatibility:<\/span><\/p>\n<p style=\"padding-left: 40px\"><span style=\"font-weight: 400\">Adding an optional parameter to a method breaks binary compatibility only for that method. Compatibility can be restored by adding a method with the old signature, annotated as <em>Deprecated(level=HIDDEN)<\/em>. The upshot is that the maintenance impact is isolated to a single method, and the code changes for retaining compatibility are likewise restricted in scope.<\/span><\/p>\n<p style=\"padding-left: 40px\"><span style=\"font-weight: 400\">Adding a common parameter breaks binary compatibility only for the <em>CommonOptions<\/em> class. Compatibility can be restored by adding a constructor with the old signature, annotated as <em>Deprecated(level=HIDDEN)<\/em>. Significantly, we don&#8217;t have to change the signature of the methods that take <em>CommonOptions<\/em> as a parameter.<\/span><\/p>\n<h3><span style=\"font-weight: 400\">Mutually exclusive parameters<\/span><\/h3>\n<p><span style=\"font-weight: 400\">Sometimes a method might have two different ways of specifying a parameter value. For example, several methods take an <em>expiry<\/em>\u00a0argument which can be specified as either a <em>Duration<\/em> or an <em>Instant<\/em>. In the Java API, there&#8217;s nothing preventing you from writing this code:<\/span><\/p>\n<pre class=\"decode-attributes:false lang:kotlin decode:true\"> \u00a0\u00a0\u00a0UpsertOptions options = UpsertOptions.upsertOptions()\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0.expiry(Instant.now().plus(Duration.ofMinutes(15)))\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0.expiry(Duration.ofMinutes(10));\r\n\u00a0\u00a0\u00a0\u00a0collection.upsert(\"foo\", \"bar\", options);<\/pre>\n<p><span style=\"font-weight: 400\">These two ways of specifying the expiry are mutually exclusive, but Java lets you write the code anyway. If there&#8217;s a validity check, it has to happen at runtime. (In this specific example, the second call to <em>expiry<\/em>\u00a0clobbers the value set by the earlier call.)<\/span><\/p>\n<p><span style=\"font-weight: 400\">In Kotlin the upsert method has a single expiry parameter of type <em>Expiry<\/em>, where <em>Expiry<\/em>\u00a0is a sealed class:<\/span><\/p>\n<pre class=\"decode-attributes:false lang:kotlin decode:true \">\u00a0\u00a0\u00a0\u00a0collection.upsert(\"foo\", \"bar\", expiry = Expiry.of(10.minutes))<\/pre>\n<p><span style=\"font-weight: 400\">or<\/span><\/p>\n<pre class=\"decode-attributes:false lang:kotlin decode:true\"> \u00a0\u00a0\u00a0val instant = Instant.now().plus(Duration.ofMinutes(15))\r\n\u00a0\u00a0\u00a0\u00a0collection.upsert(\"foo\", \"bar\", expiry = Expiry.of(instant))<\/pre>\n<p><span style=\"font-weight: 400\">This pattern is applied across the API; mutually exclusive options are always represented as a single parameter that takes an instance of a sealed class whose instances represent the different ways of specifying the value.<\/span><\/p>\n<h3><span style=\"font-weight: 400\">Streaming results<\/span><\/h3>\n<p><span style=\"font-weight: 400\">The Couchbase Query, Analytics, View, and Full-Text Search services can all return very large results sets. In order to process these results efficiently without exhausting the heap, the query methods for these services return their results as a Kotlin Flow.<\/span><\/p>\n<p><span style=\"font-weight: 400\">We provide two <em>execute<\/em> extension methods on this flow. One method buffers the result rows into memory before returning the whole result set (to be used only when the result set is known to be small). The other method lets the user provide a lambda to apply to each result row as it is received from the server. Both versions take advantage of the backpressure\/flow control provided by the core-io library.<\/span><\/p>\n<h4><span style=\"font-weight: 400\">Rejected alternatives<\/span><\/h4>\n<p><span style=\"font-weight: 400\">We considered exposing the Project Reactor Flux\/Mono objects used by the <em>core-io<\/em> library but decided that after getting a taste of coroutines we don\u2019t miss Reactor at all, and we believe most users will feel the same way.<\/span><\/p>\n<h3><span style=\"font-weight: 400\">DSL vs. hierarchical builder<\/span><\/h3>\n<p><span style=\"font-weight: 400\">The Couchbase SDKs have many configuration options grouped into separate categories. For the JVM SDKs, these options are properties of the <em>ClusterEnvironment<\/em>. In Java, these options are configured using a <em>ClusterEnvironment<\/em> builder. Here&#8217;s an example in Java that disables compression, DNS SRV, and the Key\/Value service circuit breaker:<\/span><\/p>\n<pre class=\"decode-attributes:false lang:kotlin decode:true\"> \u00a0\u00a0\u00a0ClusterEnvironment env = ClusterEnvironment.builder()\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0.ioConfig(IoConfig\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0.enableDnsSrv(false)\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0.kvCircuitBreakerConfig(CircuitBreakerConfig\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0.enabled(false)))\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0.compressionConfig(CompressionConfig\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0.enable(false))\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0.build();<\/pre>\n<p><span style=\"font-weight: 400\">The Kotlin API takes advantage of Kotlin&#8217;s DSL support, allowing the same configuration to be expressed as:<\/span><\/p>\n<pre class=\"decode-attributes:false lang:kotlin decode:true\"> \u00a0\u00a0\u00a0val env = ClusterEnvironment.builder {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0io {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0enableDnsSrv = false\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0kvCircuitBreaker { enabled = false }\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0}\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0compression { enable = false }\r\n\u00a0\u00a0\u00a0\u00a0}<\/pre>\n<p><span style=\"font-weight: 400\">The Kotlin API also allows configuring the environment inline with the connect method:<\/span><\/p>\n<pre class=\"decode-attributes:false lang:kotlin decode:true\"> \u00a0\u00a0\u00a0val cluster = Cluster.connect(host, username, password) {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0io {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0enableDnsSrv = false\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0}\r\n\u00a0\u00a0\u00a0\u00a0}<\/pre>\n<p><span style=\"font-weight: 400\">Users who prefer the traditional cluster environment builder over the DSL can continue to use the builder if they wish.<\/span><\/p>\n<h4><span style=\"font-weight: 400\">Rejected alternatives<\/span><\/h4>\n<p><span style=\"font-weight: 400\">We could just as easily have used data classes instead of a DSL:<\/span><\/p>\n<pre class=\"decode-attributes:false lang:kotlin decode:true\"> \u00a0\u00a0\u00a0val env = ClusterEnvironment(\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0io = IoConfig(\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0enableDnsSrv = false,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0kvCircuitBreaker = CircuitBreakerConfig(enabled = false),\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0),\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0compression = CompressionConfig(enable = false),\r\n\u00a0\u00a0\u00a0\u00a0)<\/pre>\n<p><span style=\"font-weight: 400\">That would have been okay, but the DSL is more concise and feels more like we&#8217;re playing to Kotlin\u2019s strength.<\/span><\/p>\n<h2><span style=\"font-weight: 400\">Summary<\/span><\/h2>\n<p><span style=\"font-weight: 400\">We put a lot of careful thought into designing the Couchbase Kotlin SDK\u2019s public API. I can\u2019t promise we got everything right, but hopefully, the result feels like something that respects Kotlin idioms and best practices.<\/span><\/p>\n<p><span style=\"font-weight: 400\">The Couchbase Kotlin SDK is finally ready to use in production, whether you\u2019re using the Capella DBaaS or managing your own Couchbase Server cluster. Everything not annotated as <em>volatile<\/em> or <em>uncommitted<\/em> is now officially part of the stable public API. A huge <strong>Thank You!<\/strong>\u00a0goes out to everyone in the community who shared their feedback along the way.<\/span><\/p>\n<ul>\n<li style=\"list-style-type: none\">\n<ul>\n<li><span style=\"font-weight: 400\">Learn more in the <a href=\"https:\/\/docs.couchbase.com\/kotlin-sdk\/current\/hello-world\/overview.html\" target=\"_blank\" rel=\"noopener\">Couchbase Kotlin SDK documentation<\/a>.<\/span><\/li>\n<li><span style=\"font-weight: 400\">Have a question or comment? Find us in the:<\/span>\n<ul>\n<li><span style=\"font-weight: 400\">the <\/span><a href=\"https:\/\/www.couchbase.com\/forums\/c\/kotlin-sdk\/40\/\" target=\"_blank\" rel=\"noopener\"><span style=\"font-weight: 400\">Couchbase Forum<\/span><\/a><\/li>\n<li><span style=\"font-weight: 400\">the <\/span><a href=\"https:\/\/www.couchbase.com\/blog\/couchbase-on-discord\/\" target=\"_blank\" rel=\"noopener\"><span style=\"font-weight: 400\">Couchbase Discord server<\/span><\/a><span style=\"font-weight: 400\">, or<\/span><\/li>\n<li><span style=\"font-weight: 400\">the #couchbase channel in the <\/span><a href=\"https:\/\/kotlinlang.org\/community\/\" target=\"_blank\" rel=\"noopener\"><span style=\"font-weight: 400\">Kotlin Slack workspace<\/span><\/a><span style=\"font-weight: 400\">.<\/span><\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>I\u2019m pleased to announce the GA release of Couchbase Kotlin SDK version 1.0. In truth, I\u2019m over the moon. This project has been a labor of love. After working with Java for decades, I have a new favorite language. In [&hellip;]<\/p>\n","protected":false},"author":14341,"featured_media":13175,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"inline_featured_image":false,"footnotes":""},"categories":[1814,1816,9593,2201],"tags":[9337,1439,1349],"ppma_author":[9070],"class_list":["post-13174","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-application-design","category-couchbase-server","category-kotlin","category-tools-sdks","tag-application-development","tag-asynchronous","tag-development"],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v27.3 (Yoast SEO v27.3) - https:\/\/yoast.com\/product\/yoast-seo-premium-wordpress\/ -->\n<title>Designing the Couchbase Kotlin API\u00a0 - The Couchbase Blog<\/title>\n<meta name=\"description\" content=\"The Couchbase Kotlin SDK is ready to use in production, whether you\u2019re using the Capella DBaaS or managing your own Couchbase Server cluster.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.couchbase.com\/blog\/couchbase-kotlin-api-released\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Designing the Couchbase Kotlin API\u00a0\" \/>\n<meta property=\"og:description\" content=\"The Couchbase Kotlin SDK is ready to use in production, whether you\u2019re using the Capella DBaaS or managing your own Couchbase Server cluster.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.couchbase.com\/blog\/couchbase-kotlin-api-released\/\" \/>\n<meta property=\"og:site_name\" content=\"The Couchbase Blog\" \/>\n<meta property=\"article:author\" content=\"https:\/\/duckduckgo.com\/?q=delete+facebook\" \/>\n<meta property=\"article:published_time\" content=\"2022-05-03T16:49:42+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2023-05-22T15:01:08+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2022\/05\/kotlin-sdk-couchbase-released-scaled.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"2560\" \/>\n\t<meta property=\"og:image:height\" content=\"1707\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"David Nault\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@orzobat\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"David Nault\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"8 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/couchbase-kotlin-api-released\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/couchbase-kotlin-api-released\\\/\"},\"author\":{\"name\":\"David Nault\",\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/#\\\/schema\\\/person\\\/ec7e18bcee3337b77367560e6aeb75ef\"},\"headline\":\"Designing the Couchbase Kotlin API\u00a0\",\"datePublished\":\"2022-05-03T16:49:42+00:00\",\"dateModified\":\"2023-05-22T15:01:08+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/couchbase-kotlin-api-released\\\/\"},\"wordCount\":1624,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/#organization\"},\"image\":{\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/couchbase-kotlin-api-released\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/wp-content\\\/uploads\\\/sites\\\/1\\\/2022\\\/05\\\/kotlin-sdk-couchbase-released-scaled.jpg\",\"keywords\":[\"application development\",\"asynchronous\",\"Development\"],\"articleSection\":[\"Application Design\",\"Couchbase Server\",\"Kotlin\",\"Tools &amp; SDKs\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/couchbase-kotlin-api-released\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/couchbase-kotlin-api-released\\\/\",\"url\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/couchbase-kotlin-api-released\\\/\",\"name\":\"Designing the Couchbase Kotlin API\u00a0 - The Couchbase Blog\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/couchbase-kotlin-api-released\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/couchbase-kotlin-api-released\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/wp-content\\\/uploads\\\/sites\\\/1\\\/2022\\\/05\\\/kotlin-sdk-couchbase-released-scaled.jpg\",\"datePublished\":\"2022-05-03T16:49:42+00:00\",\"dateModified\":\"2023-05-22T15:01:08+00:00\",\"description\":\"The Couchbase Kotlin SDK is ready to use in production, whether you\u2019re using the Capella DBaaS or managing your own Couchbase Server cluster.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/couchbase-kotlin-api-released\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/couchbase-kotlin-api-released\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/couchbase-kotlin-api-released\\\/#primaryimage\",\"url\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/wp-content\\\/uploads\\\/sites\\\/1\\\/2022\\\/05\\\/kotlin-sdk-couchbase-released-scaled.jpg\",\"contentUrl\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/wp-content\\\/uploads\\\/sites\\\/1\\\/2022\\\/05\\\/kotlin-sdk-couchbase-released-scaled.jpg\",\"width\":2560,\"height\":1707,\"caption\":\"Designing a Kotlin SDK for Couchbase\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/couchbase-kotlin-api-released\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Designing the Couchbase Kotlin API\u00a0\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/#website\",\"url\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/\",\"name\":\"The Couchbase Blog\",\"description\":\"Couchbase, the NoSQL Database\",\"publisher\":{\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/#organization\",\"name\":\"The Couchbase Blog\",\"url\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/wp-content\\\/uploads\\\/2023\\\/04\\\/admin-logo.png\",\"contentUrl\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/wp-content\\\/uploads\\\/2023\\\/04\\\/admin-logo.png\",\"width\":218,\"height\":34,\"caption\":\"The Couchbase Blog\"},\"image\":{\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/#\\\/schema\\\/logo\\\/image\\\/\"}},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/#\\\/schema\\\/person\\\/ec7e18bcee3337b77367560e6aeb75ef\",\"name\":\"David Nault\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/9be1cd3e3c55df4d11b191172d382965de29da3a98d341167b33a8ad00546057?s=96&d=mm&r=gfbceda752a7ba278f83b24641d607212\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/9be1cd3e3c55df4d11b191172d382965de29da3a98d341167b33a8ad00546057?s=96&d=mm&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/9be1cd3e3c55df4d11b191172d382965de29da3a98d341167b33a8ad00546057?s=96&d=mm&r=g\",\"caption\":\"David Nault\"},\"description\":\"David Nault is a Database Connector Developer for Couchbase. He spent the greater part of two decades writing back end web services in Java, and thinks JSON-RPC is really cool. He lives in California.\",\"sameAs\":[\"https:\\\/\\\/github.com\\\/dnault\",\"https:\\\/\\\/duckduckgo.com\\\/?q=delete+facebook\",\"https:\\\/\\\/x.com\\\/orzobat\"],\"url\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/author\\\/dnault\\\/\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Designing the Couchbase Kotlin API\u00a0 - The Couchbase Blog","description":"The Couchbase Kotlin SDK is ready to use in production, whether you\u2019re using the Capella DBaaS or managing your own Couchbase Server cluster.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.couchbase.com\/blog\/couchbase-kotlin-api-released\/","og_locale":"en_US","og_type":"article","og_title":"Designing the Couchbase Kotlin API\u00a0","og_description":"The Couchbase Kotlin SDK is ready to use in production, whether you\u2019re using the Capella DBaaS or managing your own Couchbase Server cluster.","og_url":"https:\/\/www.couchbase.com\/blog\/couchbase-kotlin-api-released\/","og_site_name":"The Couchbase Blog","article_author":"https:\/\/duckduckgo.com\/?q=delete+facebook","article_published_time":"2022-05-03T16:49:42+00:00","article_modified_time":"2023-05-22T15:01:08+00:00","og_image":[{"width":2560,"height":1707,"url":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2022\/05\/kotlin-sdk-couchbase-released-scaled.jpg","type":"image\/jpeg"}],"author":"David Nault","twitter_card":"summary_large_image","twitter_creator":"@orzobat","twitter_misc":{"Written by":"David Nault","Est. reading time":"8 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.couchbase.com\/blog\/couchbase-kotlin-api-released\/#article","isPartOf":{"@id":"https:\/\/www.couchbase.com\/blog\/couchbase-kotlin-api-released\/"},"author":{"name":"David Nault","@id":"https:\/\/www.couchbase.com\/blog\/#\/schema\/person\/ec7e18bcee3337b77367560e6aeb75ef"},"headline":"Designing the Couchbase Kotlin API\u00a0","datePublished":"2022-05-03T16:49:42+00:00","dateModified":"2023-05-22T15:01:08+00:00","mainEntityOfPage":{"@id":"https:\/\/www.couchbase.com\/blog\/couchbase-kotlin-api-released\/"},"wordCount":1624,"commentCount":0,"publisher":{"@id":"https:\/\/www.couchbase.com\/blog\/#organization"},"image":{"@id":"https:\/\/www.couchbase.com\/blog\/couchbase-kotlin-api-released\/#primaryimage"},"thumbnailUrl":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2022\/05\/kotlin-sdk-couchbase-released-scaled.jpg","keywords":["application development","asynchronous","Development"],"articleSection":["Application Design","Couchbase Server","Kotlin","Tools &amp; SDKs"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.couchbase.com\/blog\/couchbase-kotlin-api-released\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.couchbase.com\/blog\/couchbase-kotlin-api-released\/","url":"https:\/\/www.couchbase.com\/blog\/couchbase-kotlin-api-released\/","name":"Designing the Couchbase Kotlin API\u00a0 - The Couchbase Blog","isPartOf":{"@id":"https:\/\/www.couchbase.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.couchbase.com\/blog\/couchbase-kotlin-api-released\/#primaryimage"},"image":{"@id":"https:\/\/www.couchbase.com\/blog\/couchbase-kotlin-api-released\/#primaryimage"},"thumbnailUrl":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2022\/05\/kotlin-sdk-couchbase-released-scaled.jpg","datePublished":"2022-05-03T16:49:42+00:00","dateModified":"2023-05-22T15:01:08+00:00","description":"The Couchbase Kotlin SDK is ready to use in production, whether you\u2019re using the Capella DBaaS or managing your own Couchbase Server cluster.","breadcrumb":{"@id":"https:\/\/www.couchbase.com\/blog\/couchbase-kotlin-api-released\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.couchbase.com\/blog\/couchbase-kotlin-api-released\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.couchbase.com\/blog\/couchbase-kotlin-api-released\/#primaryimage","url":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2022\/05\/kotlin-sdk-couchbase-released-scaled.jpg","contentUrl":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2022\/05\/kotlin-sdk-couchbase-released-scaled.jpg","width":2560,"height":1707,"caption":"Designing a Kotlin SDK for Couchbase"},{"@type":"BreadcrumbList","@id":"https:\/\/www.couchbase.com\/blog\/couchbase-kotlin-api-released\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.couchbase.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Designing the Couchbase Kotlin API\u00a0"}]},{"@type":"WebSite","@id":"https:\/\/www.couchbase.com\/blog\/#website","url":"https:\/\/www.couchbase.com\/blog\/","name":"The Couchbase Blog","description":"Couchbase, the NoSQL Database","publisher":{"@id":"https:\/\/www.couchbase.com\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.couchbase.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/www.couchbase.com\/blog\/#organization","name":"The Couchbase Blog","url":"https:\/\/www.couchbase.com\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.couchbase.com\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2023\/04\/admin-logo.png","contentUrl":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2023\/04\/admin-logo.png","width":218,"height":34,"caption":"The Couchbase Blog"},"image":{"@id":"https:\/\/www.couchbase.com\/blog\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":"https:\/\/www.couchbase.com\/blog\/#\/schema\/person\/ec7e18bcee3337b77367560e6aeb75ef","name":"David Nault","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/secure.gravatar.com\/avatar\/9be1cd3e3c55df4d11b191172d382965de29da3a98d341167b33a8ad00546057?s=96&d=mm&r=gfbceda752a7ba278f83b24641d607212","url":"https:\/\/secure.gravatar.com\/avatar\/9be1cd3e3c55df4d11b191172d382965de29da3a98d341167b33a8ad00546057?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/9be1cd3e3c55df4d11b191172d382965de29da3a98d341167b33a8ad00546057?s=96&d=mm&r=g","caption":"David Nault"},"description":"David Nault is a Database Connector Developer for Couchbase. He spent the greater part of two decades writing back end web services in Java, and thinks JSON-RPC is really cool. He lives in California.","sameAs":["https:\/\/github.com\/dnault","https:\/\/duckduckgo.com\/?q=delete+facebook","https:\/\/x.com\/orzobat"],"url":"https:\/\/www.couchbase.com\/blog\/author\/dnault\/"}]}},"acf":[],"authors":[{"term_id":9070,"user_id":14341,"is_guest":0,"slug":"dnault","display_name":"David Nault","avatar_url":"https:\/\/secure.gravatar.com\/avatar\/9be1cd3e3c55df4d11b191172d382965de29da3a98d341167b33a8ad00546057?s=96&d=mm&r=g","0":null,"1":"","2":"","3":"","4":"","5":"","6":"","7":"","8":""}],"_links":{"self":[{"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/posts\/13174","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/users\/14341"}],"replies":[{"embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/comments?post=13174"}],"version-history":[{"count":0,"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/posts\/13174\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/media\/13175"}],"wp:attachment":[{"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/media?parent=13174"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/categories?post=13174"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/tags?post=13174"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/ppma_author?post=13174"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}