Я с удовольствием работал и получал отличную поддержку по созданию DSL на Kotlin Language .
Эта функция теперь используется для создания файлов сборки gradle , для определения маршрутов в Spring Webflux , для создания html-шаблонов с использованием библиотеки kotlinx.html .
Здесь я собираюсь продемонстрировать создание DSL на основе kotlin для представления содержимого манифеста приложения Cloud Foundry .
Пример манифеста выглядит следующим образом, если он представлен в виде файла yaml:
|
01
02
03
04
05
06
07
08
09
10
11
|
applications: - name: myapp memory: 512M instances: 1 path: target/someapp.jar routes: - somehost.com - antother.com/path envs: ENV_NAME1: VALUE1 ENV_NAME2: VALUE2 |
И вот тип DSL, к которому я стремлюсь:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
cf { name = "myapp" memory = 512(M) instances = 1 path = "target/someapp.jar" routes { +"somehost.com" +"another.com/path" } envs { env["ENV_NAME1"] = "VALUE1" env["ENV_NAME2"] = "VALUE2" }} |
Получение базовой структуры
Позвольте мне начать с более простой структуры, которая выглядит следующим образом:
|
1
2
3
4
5
|
cf { name = "myapp" instances = 1 path = "target/someapp.jar"} |
и хотим, чтобы этот вид DSL отображался на структуру, которая выглядит следующим образом:
|
1
2
3
4
5
|
data class CfManifest( var name: String = "", var instances: Int? = 0, var path: String? = null) |
Это будет переводиться в функцию Kotlin, которая принимает лямбда-выражение :
|
1
2
3
|
fun cf(init: CfManifest.() -> Unit) { ...} |
Параметр, который выглядит так:
|
1
|
() -> Unit |
является довольно очевидным, лямбда-выражение, которое не принимает никаких параметров и ничего не возвращает.
Часть, которая заняла какое-то время, чтобы проникнуть в мои мысли, — это модифицированное лямбда-выражение, называемое лямбда-выражением с получателем
|
1
|
CfManifest.() -> Unit |
Это делает две вещи, как я понял:
1. Он определяет в области обернутой функции функцию расширения для типа получателя — в моем случае
CfManifest класс
2. this в лямбда-выражении теперь относится к функции приемника.
Учитывая это, функция cf переводится в:
|
1
2
3
4
5
|
fun cf(init: CfManifest.() -> Unit): CfManifest { val manifest = CfManifest() manifest.init() return manifest} |
который может быть кратко выражен как:
|
1
|
fun cf(init: CfManifest.() -> Unit) = CfManifest().apply(init) |
так что теперь, когда я звоню:
|
1
2
3
4
5
|
cf { name = "myapp" instances = 1 path = "target/someapp.jar"} |
Это переводится как:
|
1
2
3
4
5
|
CFManifest().apply { this.name = "myapp" this.instances = 1 this.path = "target/someapp.jar"} |
Больше DSL
Расширяя базовую структуру:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
cf { name = "myapp" memory = 512(M) instances = 1 path = "target/someapp.jar" routes { +"somehost.com" +"another.com/path" } envs { env["ENV_NAME1"] = "VALUE1" env["ENV_NAME2"] = "VALUE2" }} |
Маршруты и envs, в свою очередь, становятся методами класса CfManifest и выглядят так:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
data class CfManifest( var name: String = "", var path: String? = null, var memory: MEM? = null, ... var routes: ROUTES? = null, var envs: ENVS = ENVS()) { fun envs(block: ENVS.() -> Unit) { this.envs = ENVS().apply(block) } ... fun routes(block: ROUTES.() -> Unit) { this.routes = ROUTES().apply(block) }}data class ENVS( var env: MutableMap<String, String> = mutableMapOf())data class ROUTES( private val routes: MutableList<String> = mutableListOf()) { operator fun String.unaryPlus() { routes.add(this) }} |
Посмотрите, как метод routes принимает лямбда-выражение с типом получателя ROUTES , это позволяет мне определить выражение следующим образом:
|
1
2
3
4
5
6
7
8
|
cf { ... routes { +"somehost.com" +"another.com/path" } ...} |
Еще один трюк здесь — это способ добавления маршрута:
|
1
|
+"somehost.com" |
который включается с использованием соглашения Kotlin, которое переводит конкретные имена методов в операторы , здесь метод unaryPlus. Круто для меня то, что этот оператор виден только в области действия ROUTES!
Еще одна особенность DSL, использующего возможности Kotlin, — это способ указания памяти, который состоит из двух частей — числа и модификатора, 2G, 500M и т. Д.
Это указывается в слегка измененном виде через DSL как 2 (G) и 500 (M).
Он реализован с использованием другого соглашения Kotlin, где, если у класса есть метод invoke экземпляры могут вызывать его следующим образом:
|
1
2
3
4
5
|
class ClassWithInvoke() { operator fun invoke(n: Int): String = "" + n}val c = ClassWithInvoke()c(10) |
Таким образом, реализация метода invoke в качестве функции расширения для Int в рамках класса CFManifest позволяет использовать этот тип DSL:
|
1
2
3
4
5
6
7
|
data class CfManifest( var name: String = "", ...) { ... operator fun Int.invoke(m: MemModifier): MEM = MEM(this, m)} |
Это чисто эксперимент с моей стороны, я новичок в Kotlin, а также в Kotlin DSL, так что очень вероятно, что есть много вещей, которые можно улучшить в этой реализации, любые отзывы и предложения приветствуются. Вы можете поиграть с этим примером кода в моем репозитории github здесь
| Ссылка: | Приложение Cloud Foundry манифестируется с использованием Kotlin DSL от нашего партнера по JCG Биджу Кунджуммена в блоге all and sundry. |