One Source to Rule Them All

Kotlin DSLs as a Single Source of Truth for Multiple Tasks

Ivan Ponomarev

never
never1
never2
ivan

Ivan Ponomarev

  • Staff Engineer @ Synthesized.io

  • Teaching Java @ Northeastern University London

What do we need DSLs for?

  1. Code can be written by a subject-matter expert, not a programmer

What do we need DSLs for (in real life)?

  1. Sometimes code can be written by a subject-matter expert, not a programmer

  2. Often code can be read by a subject-matter expert

What do we need DSLs for (in real life)?

  1. Sometimes code can be written by a subject-matter expert, not a programmer

  2. Often code can be read by a subject-matter expert

  3. DSL code is the single source of truth for many tasks/aspects of the project

DSLs external and internal

diag 2f884f4bf3a141e4a79c07eb6fd54e49

Martin Fowler, Rebecca Parsons. Domain-Specific Languages

fowler dsl

Types of external DSLs

diag eaa6dfd764fa2a9ecc5a8edd652285ee
Designed from scratch
  • DOT (GraphViz), PlantUML, gnuplot…​

  • HCL (Hashicorp Configuration Language)

  • Gherkin (Cucumber framework’s language)

Types of external DSLs

diag 17dbda94afd6aac77b02414e411f27d4
Based on general purpose markup language
  • YAML: OpenAPI, Ansible, GithubActions, k8s definitions…​

  • JSON: Vue i18n files

  • XML: XSLT, XSD

Internal DSLs

diag c5da4989c64d62143f70377e22fd3259
Subset of a dynamically typed language
  • Lisp (historically first): Emacs Lisp, Symbolic Mathematics etc.

  • Ruby: Rails, RSpec, Chef…​

  • Groovy: Spock, Ratpack, Grails, Gradle, Jenkinsfile…​

Internal DSLs

diag 42e954557e49882f726f989d19a6bfa6
Subset of a statically typed language
  • Scala: Scalatest, Akka HTTP…​

  • Haskell: Parsec

  • Kotlin: Kotlinx.html, Ktor, Gradle,…​

Restrictions

diag 35ae79a6e9dd3f88c3466a005d0defca

Restrictions

diag a313240be4a6e55ebfcae925c1dab8a4

Restrictions

diag f8050584a36a6a789f8e5e33d69bb997

Restrictions

diag 449c6f8db66520cbb3ba78c5ed6a1c43

IDE support

diag 142a942a0ae0a35775f2195c7ba485d0

IDE support

diag 37bb363e368a1594a6f2b566ca83b7d4

IDE support

diag ecdf9e99b10557a75addce5b506f4321

IDE support

diag 71afa472d48e7c9f06e137f74da738b4

Security Concerns

diag 80780faad40296ac136da68bcae83065

* Does not mean that you are safe, e.g. google for "Billion_laughs_attack"

Kotlin language features for DSL building

Tool

DSL syntax

General syntax

Extension functions

mylist.first();
/* there isn’t first() method
in mylist collection*/
ListUtlis.first(mylist)

Infix functions

1 to "one"
1.to("one")

Operators overloading

collection += element
collection.add(element)

Type aliases

typealias Point = Pair

Creating empty inheritors classes and other duct tapes

Kotlin language features for DSL building (continued)

Tool

DSL syntax

General syntax

get/set methods convention

map["key"] = "value"
map.put("key", "value")

Destructuring declaration

val (x, y) = Point(0, 0)
val p = Point(0, 0)
val x = p.first
val y = p.second

Lambda out of parentheses

list.forEach { ... }
list.forEach({...})

Lambda with receiver

Person().apply { name = «John» }

N/A

Context control

@DslMarker

N/A

Demo time!

One source to be used in

  1. Execution of rules

  2. Documentation

  3. Visualization

  4. Validation

  5. Serialization ("free" JSON/YAML based DSL version for our Kotlin DSL)

Mixing DSL and code

We can leave extension points in our builder:

//In our example
customCondition { Random.nextDouble() < .88} invokes TransformationC


//Or in some other DSL
customBusinessRule { checkSmthProgramatically() }

Great for describing business rules (state-transition model, for example), especially if DSL defines only part of rules, which is usually the case.

Infix functions don’t work on this

    brightonKotlin {
        //Cannot get rid of paretheses (unlike Groovy)
        talk ("Talk 1") deliveredBy {
            speaker ("Speaker 1")
            speaker ("Speaker 2")
        }
        talk ("Talk 2") deliveredBy {
            speaker("ssd")
            speaker ("Speaker 3")
        }
    }

Infix functions don’t work on this: a workaround

    brightonKotlin {
        //Cannot get rid of paretheses
        + "Talk 1" deliveredBy {
            + "Speaker 1"
            + "Speaker 2"
        }
        + "Talk 2" deliveredBy {
            + "ssd"
            + "Speaker 3"
        }
    }

@DslMarker

    talk ("Talk 1") deliveredBy {
        talk (...) // ???
    }

@DslMarker

@DslMarker
annotation class MeetupDsl

@MeetupDsl
class MeetupBuilder { ... }

@MeetupDsl
class SpeakersBuilder { ... }

Reading/watching suggestions

jemerov kotlin

Examples for your inspiration

Conclusions

  • DSL combined with designed patterns is a powerful tool for solving multiple tasks

  • Creating DSLs in Kotlin is not scary. You can improve parts of your existing internal APIs today making them "DSL-like"

  • Internal Kotlin DSLs are not the only way to implement DSLs, but definitely not the worst one in many scenarious.

Thanks for listening!

qr

Code and slides are available on GitHub https://github.com/inponomarev/dsl-talk

@inponomarev