This is the article of LINE Advent Calendar 2018. My name is Freddie Wang, I am a software engineer at LINE KYOTO office. In this article, I will demonstrate how to use the Kotlin scripts + DSL to make flexible system and use a simple Clova Extension project as the example.
DSL (Domain Specific Language)
First of all, what is DSL? Let's take a look at Wikipedia's definition on DSL.
A domain-specific language (DSL) is a computer language specialized to a particular application domain. This is in contrast to a general-purpose language (GPL), which is broadly applicable across domains.</em >
So a DSL is a language which focuses on the particular part of the application. For example, SQL is one of the well-known DSL. We can use SQL to flexibly manipulate data.
DSL with Kotlin
Function literals with receiver</a > is a cool feature of Kotlin, which provides the ability to call an instance of function type with receiver providing the receive object</em >. With this feature, we can create Kotlin-based DSL very easily. For example, The Clova CEK SDK for Kotlin</a > already supports simple DSL like this
clovaClient("application.id") {
launchHandler { launchRequest, session ->
//Add your implementation
}
intentHandler { intentRequest, session ->
//Add your implementation
}
sessionEndedHandler { sessionEndedRequest, session ->
//Add your implementation
}
}
You can use this Clova Extension DSL to create a Clova Extension very easily.
The Java Scripting API (JSR-223)
The Java Scripting API is a tool for using scripting engines from Java code (such as Nashorn). It enables users to write customizable scripting code that can be picked up by the Java application at runtime. After Kotlin 1.1, JSR-223 is supported for Kotlin Scripts too. That means that it's possible to run the Kotlin scripts from Kotlin programs. The best thing is that it also supports to run the DSL from Kotlin programs!
How to create the custom Kotlin DSL
Creating the custom Kotlin DSL is very very simple, it is just like to create a function and use function literals with receiveras final parameter. For example, if we want to define clovaClient("application.id")
, we can declare a function like this
fun clovaClient(applicationId: String,
verifier: RequestVerifier = RequestVerifierImpl(),
init: ClovaClient.() -> Unit
): ClovaClient {
val client = ClovaClient(applicationId, verifier)
client.init()
return client
}
Here the init
would become a function of ClovaClient
. The body of init
would be defined by the DSL users.
Run the DSL via the Kotlin script engine
To enable the Kotlin script engine, we need to include the dependencies first. There are 3 necessary packages which should be included in the build.gradle
.
dependencies {
implementation "org.jetbrains.kotlin:kotlin-script-util:$kotlinVersion"
implementation "org.jetbrains.kotlin:kotlin-script-runtime:$kotlinVersion"
implementation "org.jetbrains.kotlin:kotlin-compiler-embeddable:$kotlinVersion"
}
Then a file called javax.script.ScriptEngineFactory
has to be placed in the folder META-INF/services
. It contains this entry org.jetbrains.kotlin.script.jsr223.KotlinJsr223JvmLocalScriptEngineFactory
. Or alternatively you can define a custom scripting engine factory. After that you can initilize the script engine like this
val scriptEngine: ScriptEngine = ScriptEngineManager().getEngineByExtension("kts")!!
Once the script engine is initilized, we should be able to run the Clova DSL as below.
val clovaClient = scriptEngine.eval(dsl) as ClovaClient
Very simple, isn't it?
Running with Spring Boot
If you are using Spring Boot, the Spring Boot gradle plugin would pack all dependencies into single executable jar. It is call fat jar</a >. In most cases it can work well. However certain libraries may have problem and kotlin-compiler-embeddable
is one of them. To deal with the problematic libraries, the executable jar should be configurated to unpack specific jar files. So we need to add configurations like this.
bootJar {
requiresUnpack "**/kotlin*.jar", "**/clova-extension-*.jar"
}
Unfortunatelly, you can't just run the DSL directly like above in Spring Boot. This is because the classpath in the Kotlin script engine would be different from the application. So you need to add the import packages before the DSL. The full code snippets would look like this
import com.linecorp.clova.extension.client.*
import com.linecorp.clova.extension.model.util.*
import com.linecorp.clova.extension.converter.jackson.JacksonObjectMapper
clovaClient("application.id") {
objectMapper = JacksonObjectMapper()
launchHandler { launchRequest, session ->
//Add your implementation
}
intentHandler { intentRequest, session ->
//Add your implementation
}
sessionEndedHandler { sessionEndedRequest, session ->
//Add your implementation
}
}
Now, we can run the Kotlin DSL inside the application. Even if we can update the DSL from above in an external editor, we can change the behavior of Clova Extension without re-deploying the application again!
I've created a simple online Clova Extension editor by using Kotlin DSL, Kotlin script engine and The Clova CEK SDK for Kotlin. You can use this online editor to create the Clova Extension directly. the source code is here</a >
Conclusion
Now Kotlin become more and more popular in the world. Relatively, Kotlin script engine is not famous as Kotlin language. By the power of Kotlin DSL and Kotlin script engine, you can create a flexible system very easily. If you have any question or idea, please feel free to contact with me @wangyung</a > on Twitter. I am looking forward to see any exciting idea created by Kotlin.
Tomorrow (12/16) is Jun's Tensorflow.jsの話, If you'are interested in machine learning, please don't miss it!