{"id":13621,"date":"2019-02-12T12:28:37","date_gmt":"2019-02-12T11:28:37","guid":{"rendered":"https:\/\/medium.com\/p\/eacaae4565b5"},"modified":"2023-03-20T16:28:40","modified_gmt":"2023-03-20T15:28:40","slug":"how-we-use-kotlin-with-exposed-at-touk","status":"publish","type":"post","link":"https:\/\/touk.pl\/blog\/2019\/02\/12\/how-we-use-kotlin-with-exposed-at-touk\/","title":{"rendered":"How we use Kotlin with Exposed at TouK"},"content":{"rendered":"<h3 id=\"why-kotlin\">Why Kotlin?<\/h3>\n<p>At <a href=\"https:\/\/touk.pl\/\">TouK<\/a>, we try to early adopt technologies. We don\u2019t have a starter project skeleton reused in every new project; we want to try something that fits the project&#8217;s needs, even if it\u2019s not that popular yet. We tried Kotlin first in mid-2016, right after reaching the 1.0.2 version. It was getting really popular in Android development, but almost nobody used it on the backend, especially\u200a\u2014\u200awith production deployment. After reading some \u201chello world\u201d examples, including this <a href=\"https:\/\/spring.io\/blog\/2016\/03\/20\/a-geospatial-messenger-with-kotlin-spring-boot-and-postgresql\">great article by Sebastien Deleuze<\/a> we decided to try Kotlin as main language for a new MVNO project. The project was mainly a backend for a mobile app, with some integrations with external services (chat, sms, payments) and background tasks regarding customer subscriptions. We felt that Kotlin would be something fresh and more pleasant for developers, but we also liked the \u201cnot reinventing the wheel\u201d approach\u200a\u2014\u200areusing large parts of the Java\/JVM ecosystem we were happy with for existing projects (Spring, Gradle, JUnit, Mockito).<\/p>\n<h3 id=\"why-exposed\">Why Exposed?<\/h3>\n<p>We initially felt that Kotlin + JPA\/Hibernate is not a perfect match. Kotlin\u2019s functional nature with first-class immutability support was not something that could seemly integrate with full-blown ORM started in the pre-Java8 era. But <a href=\"https:\/\/spring.io\/blog\/2016\/03\/20\/a-geospatial-messenger-with-kotlin-spring-boot-and-postgresql\">Sebastien\u2019s article<\/a> led us to try Exposed\u200a\u2014\u200aa SQL access library maintained by JetBrains. From the beginning, we really liked the main assumptions of\u00a0Exposed:<\/p>\n<ul>\n<li>not trying to be full ORM framework<\/li>\n<li>two flavors\u200a\u2014\u200atypesafe SQL DSL and DAO\/ActiveRecord style<\/li>\n<li>lightweight, no reflection<\/li>\n<li>no code generation<\/li>\n<li>Spring integration<\/li>\n<li>no annotations on your domain classes (in SQL DSL\u00a0flavor)<\/li>\n<li>open for extension (e.g. PostGIS and new DB dialects)<\/li>\n<\/ul>\n<h3 id=\"tldr\">TL;DR<\/h3>\n<p>If you want to see how we use Kotlin + Exposed duo in our projects, check out <a href=\"https:\/\/github.com\/TouK\/kotlin-exposed-realworld\">this Github repo<\/a>. It\u2019s a Spring Boot app exposing REST API with the implementation of Medium clone as specified in <a href=\"http:\/\/realworld.io\/\">http:\/\/realworld.io<\/a> (\u201cThe mother of all demo\u00a0apps\u201d).<\/p>\n<p>Another nice example is <a href=\"https:\/\/github.com\/bastman\/spring-kotlin-exposed\">this repo<\/a> by Seb\u00a0Schmidt.<\/p>\n<h3 id=\"sql-dsl\">SQL DSL<\/h3>\n<p>In our projects we decided to try the \u201ctypesafe SQL DSL\u201d flavor of Exposed. In this approach you don\u2019t have to add anything to your domain classes, just need to write a simple schema mapping using Kotlin in configuration-as-code manner:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"kotlin\">\r\ndata class User(\r\n  val username: Username,\r\n  val password: String,\r\n  val email: String\r\n)\r\n\r\nobject UserTable : Table(\"users\") {\r\n    val username = text(\"username\")\r\n    val email = text(\"email\")\r\n    val password = text(\"password\")\r\n}\r\n<\/pre>\n<p>And then you can write type\/null-safe queries with direct mapping to your domain\u00a0classes:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"kotlin\">\r\nUserTable.select { UserTable.username eq username }?.toUser()\r\n\r\n\/\/ or \r\n\r\nUserTable.select { UserTable.username like username }.map { it.toUser() }\r\n\r\nfun ResultRow.toUser() = User(\r\n       username = this[UserTable.username],\r\n       email = this[UserTable.email],\r\n       password = this[UserTable.password]\r\n)\r\n<\/pre>\n<h3 id=\"refids\">RefIds<\/h3>\n<p>We like type-safe RefIds in our domain code. This is particularly useful in DDD-ish architectures, where you can keep those RefIds in a shared domain and use them to communicate between contexts.<\/p>\n<p>So we wrap plan ids (longs, strings) into simple wrapper classes (e.g. UserId, ArticleId, Username, Slug). Exposed allows to easily register your own column types or even generic <a href=\"https:\/\/github.com\/TouK\/kotlin-exposed-realworld\/blob\/378bf2083299fdcdf17cef1839b783c6789884e8\/src\/main\/kotlin\/io\/realworld\/shared\/infrastructure\/WrapperColumn.kt\">WrapperColumnType<\/a> implementation that you can find in our\u00a0repo.<\/p>\n<p>Using this technique you can rewrite this mapping to something like\u00a0this:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"kotlin\">\r\nsealed class UserId : RefId&lt;Long&gt;() {\r\n  object New : UserId() {\r\n    override val value: Long by IdNotPersistedDelegate&lt;Long&gt;()\r\n  }\r\n  data class Persisted(override val value: Long) : UserId() {\r\n    override fun toString() = \"UserId(value=$value)\"\r\n  }\r\n}\r\n\r\ndata class User(\r\n   val id: UserId = UserId.New,\r\n   \/\/...\r\n)\r\n\r\nfun Table.userId(name: String) = longWrapper&lt;UserId&gt;(name, UserId::Persisted, UserId::value)\r\n\r\nobject UserTable : Table(\"users\") {\r\n  val id = userId(\"id\").primaryKey().autoIncrement()\r\n\/\/...\r\n}\r\n<\/pre>\n<p>And now we can query by type-safe RefIds:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"kotlin\">\r\noverride fun findBy(userId: UserId) =\r\n  UserTable.select { UserTable.id eq userId }?.toUser()\r\n<\/pre>\n<h3 id=\"relationship-mapping\">Relationship mapping<\/h3>\n<p>One of the most significant selling points of ORMs is how easy it is to deal with relations. You just annotate the related field\/collection with OneToOne or OneToMany and then can fetch the whole graph of objects at once. In theory\u200a\u2014\u200aquite a nice idea, but in practice, things often go wrong. I\u2019m not going to dig into details, instead, I recommend you read e.g. these fragments of \u201c<a href=\"https:\/\/leanpub.com\/opinionatedjpa\">Opinionated JPA with Querydsl\u201d book<\/a>:<\/p>\n<ul>\n<li><a href=\"https:\/\/leanpub.com\/opinionatedjpa\/read#ch-questionable-parts\">https:\/\/leanpub.com\/opinionatedjpa\/read#ch-questionable-parts<\/a> and<\/li>\n<li><a href=\"https:\/\/leanpub.com\/opinionatedjpa\/read#ch-to-one-troubles\">https:\/\/leanpub.com\/opinionatedjpa\/read#ch-to-one-troubles<\/a>.<\/li>\n<\/ul>\n<p>In Exposed SQL DSL approach you have to do relationship mapping by yourself\u200a\u2014\u200aif you need to. Let\u2019s consider Article and Tag case from our project\u2019s domain. We have a many-to-many relation here, so we need additional \u201c<strong>article_tags<\/strong>\u201d table:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"kotlin\">\r\nobject ArticleTagTable : Table(\"article_tags\") {\r\n   val tagId = tagId(\"tag_id\").references(TagTable.id)\r\n   val articleId = articleId(\"article_id\").references(ArticleTable.id)\r\n}\r\n<\/pre>\n<p>When creating an Article, we have to attach all the associated Tags by populating Article\u2019s generated id into ArticleTabTable entries:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"kotlin\">\r\noverride fun create(article: Article): Article {\r\n   val savedArticle = ArticleTable.insert { it.from(article) }\r\n           .getOrThrow(ArticleTable.id)\r\n           .let { article.copy(id = it) }\r\n   savedArticle.tags.forEach { tag -&gt;\r\n       ArticleTagTable.insert {\r\n           it[ArticleTagTable.tagId] = tag.id\r\n           it[ArticleTagTable.articleId] = savedArticle.id\r\n       }\r\n   }\r\n   return savedArticle\r\n}\r\n<\/pre>\n<p>The funny part is the mapping of Article with Tags in query methods\u200a\u2014\u200ain API specification Tags are always returned with the Article\u200a\u2014\u200aso we need to eagerly fetch tags by using leftJoin:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"kotlin\">\r\nval ArticleWithTags = (ArticleTable leftJoin ArticleTagTable leftJoin TagTable)\r\n\r\noverride fun findBy(articleId: ArticleId) =\r\n  ArticleWithTags\r\n    .select { ArticleTable.id eq articleId }\r\n    .toArticles()\r\n    .singleOrNull()\r\n<\/pre>\n<p>After joining, we have then one ResultRow per one Article-Tag pair, so we have to group them by ArticleId, and build the correct Article object by adding Tags for each matching resultRow:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"kotlin\">\r\nfun Iterable&lt;ResultRow&gt;.toArticles(): List&lt;ResultRow&gt; {\r\n   return fold(mutableMapOf&lt;ArticleId, Article&gt;()) { map, resultRow -&gt;\r\n       val article = resultRow.toArticle()\r\n       val tagId = resultRow.tryGet(ArticleTagTable.tagId)\r\n       val tag = tagId?.let { resultRow.toTag() }\r\n       val current = map.getOrDefault(article.id, article)\r\n       map[article.id] = current.copy(tags = current.tags + listOfNotNull(tag))\r\n       map\r\n   }.values.toList()\r\n}\r\n<\/pre>\n<p>This implementation allows us to solve all the possible\u00a0cases:<\/p>\n<ul>\n<li>no articles (fold just returns empty\u00a0map)<\/li>\n<li>articles with no tags (tag is null, so <em>listOfNotNull<\/em>(tag) is\u00a0empty)<\/li>\n<li>articles with many tags (an article with a single tag is inserted into the map, then other tags are added in copy\u00a0method)<\/li>\n<\/ul>\n<p>However, consider when you need to fetch the dependent structure with the root object? For tags it makes sense since you always want the tags with the article, and the count of tags for any article should not be that huge. What about the comments? You definitely don\u2019t want all the comments each time you fetch the article, instead you\u2019ll need some kind of paging or even making a parent-child hierarchy for comments for the article. That\u2019s why we recommend having this relationship mapped indirectly\u200a\u2014\u200aevery Comment should have ArticleId property, and the CommentRepository could have methods\u00a0like:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"kotlin\">\r\nfun findAllBy(articleId: ArticleId): List&lt;Comment&gt;\r\n\/\/ or\r\nfun findAllByPaged(articleId: ArticleId, pageRequest: PageRequest): Page&lt;Comment&gt;\r\n<\/pre>\n<h3 id=\"extendibility\">Extendibility<\/h3>\n<p>Exposed is by design open for extension, making it even easier with Kotlin\u2019s support for extension methods. You can define your own column type or expressions, e.g. for PostGIS point type support as <a href=\"https:\/\/github.com\/sdeleuze\/geospatial-messenger\/blob\/142f3e5af2ecdd00f6409b9cdf4d63e0d0faaf38\/src\/main\/kotlin\/io\/spring\/messenger\/Database.kt#L23\">Sebastian showed in his article<\/a>. We used similar PostGIS extension in our project too. We were also able to implement a simple support for Java8 DateTime column type\u200a\u2014\u200afor now, Exposed has Joda-time support, a generic approach for various date\/time libraries is planned in the\u00a0<a href=\"https:\/\/github.com\/JetBrains\/Exposed\/blob\/master\/ROADMAP.md\">roadmap<\/a>.<\/p>\n<p>The bigger thing was Oracle DB dialect\u200a\u2014\u200awe were forced to migrate to Oracle at some time in our project. We submitted <a href=\"https:\/\/github.com\/JetBrains\/Exposed\/pull\/62\">a pull-request<\/a> with foundations of Oracle 12 support, being tested in production for a while (then, we moved back to PostgreSQL\u2026). The implementation was rather straightforward, with DataType- and FunctionProvider interfaces to provide and just a few tweaks in batch insert\u00a0support.<\/p>\n<h3 id=\"final-thoughts\">Final thoughts<\/h3>\n<p>Our developer experience with Kotlin+Exposed duo was really pleasant. If you don\u2019t plan to map many relations directly, just use simple data classes, connected by RefIds, it works really well. The Exposed library itself may need more exhaustive documentation and removing some annoying details (e.g. transaction management via thread-local\u200a\u2014\u200awhich is already on the <a href=\"https:\/\/github.com\/JetBrains\/Exposed\/blob\/master\/ROADMAP.md\">roadmap<\/a>), but we definitely recommend you give it a try in your\u00a0project!<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/medium.com\/_\/stat?event=post.clientViewed&amp;referrerSource=full_rss&amp;postId=eacaae4565b5\" width=\"1\" height=\"1\" \/><\/p>\n","protected":false},"excerpt":{"rendered":"Why Kotlin? At TouK, we try to early adopt technologies. We don\u2019t have a starter project skeleton that is reused in every new project, we want to try something that fits the project needs, even if it\u2019s not that popular yet. We tried Kotlin first it mid 2016, right after reaching 1.0.2 version\n","protected":false},"author":5,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[11],"tags":[655,556,42],"_links":{"self":[{"href":"https:\/\/touk.pl\/blog\/wp-json\/wp\/v2\/posts\/13621"}],"collection":[{"href":"https:\/\/touk.pl\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/touk.pl\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/touk.pl\/blog\/wp-json\/wp\/v2\/users\/5"}],"replies":[{"embeddable":true,"href":"https:\/\/touk.pl\/blog\/wp-json\/wp\/v2\/comments?post=13621"}],"version-history":[{"count":22,"href":"https:\/\/touk.pl\/blog\/wp-json\/wp\/v2\/posts\/13621\/revisions"}],"predecessor-version":[{"id":15452,"href":"https:\/\/touk.pl\/blog\/wp-json\/wp\/v2\/posts\/13621\/revisions\/15452"}],"wp:attachment":[{"href":"https:\/\/touk.pl\/blog\/wp-json\/wp\/v2\/media?parent=13621"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/touk.pl\/blog\/wp-json\/wp\/v2\/categories?post=13621"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/touk.pl\/blog\/wp-json\/wp\/v2\/tags?post=13621"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}