DSL
์ด๋ฒ ์ฑํฐ์์๋ ์์ญ ํนํ ์ธ์ด(Domain-Specific Language
)๋ฅผ ํตํด ๊ด์ฉ์ ์ธ API๋ฅผ ๋์์ธํ๋ ๋ฒ์ ๋ฐฐ์ธ ๊ฒ์ด๋ค. ์ ํต์ ์ธ API์ DSL ์คํ์ผ API์ ์ฐจ์ด์ ์ ๊ณต๋ถํ๊ณ , DSL ์คํ์ผ API๊ฐ ๋ค์ํ ์ค์ฉ์ ์ธ ๋ฌธ์ , ์ฆ DB ์ ๊ทผ, HTML ์์ฑ, ํ
์คํ
๋ฑ์ ์ฌ์ฉ๋๋ ๋ฐฉ๋ฒ์ ๋ฐฐ์ธ ๊ฒ์ด๋ค.
์ฝํ๋ฆฐ DSL ์ค๊ณ๋ ์ฝํ๋ฆฐ ์ธ์ด์ ์ฌ๋ฌ ํน์ฑ์ ํ์ฉํ๋๋ฐ ๊ทธ์ค ํ๋๊ฐ 5์ฅ์์ ์ดํด๋ณธ ์์ ๊ฐ์ฒด ์ง์ ๋๋ค์ด๋ค. ๋๋ค๋ฅธ ์๋ก์ด ํน์ง์ invoke
๋ก, ์ด๋ 10์ฅ์์ KFunction
์ call์ด๋ invoke๋ก ํธ์ถํ ์ ์๋ ๊ฒ์ ๊ดํด์ 11์ฅ์ ์ค๋ช
ํ ๊ฒ์ด๋ผ๊ณ ํ๊ณ ๋์ด๊ฐ๋ค. invoke
๋ฅผ ์ฌ์ฉํ๋ฉด DSL ์ฝ๋ ์์์ ๋๋ค์ ํ๋กํผํฐ ๋์
์ ๋ ์ ์ฐํ๊ฒ ์กฐํฉํ ์ ์๋ค.
APIs์์ DSLs๋ก
์ฐ๋ฆฌ์ ๋ชฉํ๋ ์ฝ๊ธฐ ์ฝ๊ณ ์ ์ง๋ณด์ํ๊ธฐ ์ฌ์ด ์ฝ๋๋ฅผ ๋ง๋๋ ๊ฒ์ด๋ค. ์ด ๋ชฉํ๋ฅผ ์ํด์๋ ๊ฐ๊ฐ์ ํด๋์ค์๋ง ์ง์คํ๋ ๊ฒ์ด ์๋๋ผ, ๊ฐ ํด๋์ค๊ฐ ์๋ก ์ํธ์์ฉํ๋ ๋ฐ๋ฅผ ์ดํด๋ด์ผ ํ๋ค. ์ฆ, ํด๋์ค APIs์ ์ง์คํด์ผ ํ๋ค.
์ด ์ฑ ์ ํตํด์ ์ฐ๋ฆฌ๋ clean APIs๋ฅผ ๋ง๋ค๊ธฐ ์ํ ์ฝํ๋ฆฐ์ ๋ค์ํ ๊ธฐ๋ฅ๋ค์ ์ดํด๋ณด์๋ค. clean APIs๋ ๋ฌด์์ผ๊น? ์ด๋ ๋ค์๊ณผ ๊ฐ์ ๋ง์ ๋ปํ๋ค.
์ฝ๋๋ฅผ ์ฝ๋ ๋ ์๊ฐ ์ด๋ค ์ผ์ด ๋ฒ์ด์ง์ง ๋ช ํํ๊ฒ ์ดํดํ ์ ์์ด์ผ ํ๋ค. ์ด๋ค ์ธ์ด๋ฅผ ์ฌ์ฉํ๊ฑด ์ด๋ฆ์ ์ ๋ถ์ด๊ณ ์ ์ ํ ๊ฐ๋ ์ ์ฌ์ฉํ๋ ๊ฒ์ ๋งค์ฐ ์ค์ํ๋ค.
์ฝ๋๊ฐ ๊ฐ๊ฒฐํด์ผ ํ๋ค. ๋ถํ์ํ ๊ตฌ๋ฌธ์ด๋ ๋ฒ์กํ ์ค๋น ์ฝ๋๊ฐ ๊ฐ๋ฅํ ํ ์ ์ด์ผ ํ๋ค. ์ด ์กฐ๊ฑด์ ๋ง์กฑ์ํค๋ ๊ฒ์ด ์ด๋ฒ ์ฑํฐ์ ์ฃผ์ ๋ชฉํ์ด๋ค.
์ฝํ๋ฆฐ์์ ๊น๋ํ API๋ฅผ ์์ฑํ๊ธฐ ์ํด ์ง์ํ๋ ๊ธฐ๋ฅ๋ค์ ํ๋ก ์ดํด๋ณด์.
์ผ๋ฐ ๊ตฌ๋ฌธ ๊ฐ๊ฒฐํ ๊ตฌ๋ฌธ ์ฌ์ฉํ ์ธ์ด ํน์ฑ
set.add(2)
set += 2
์ฐ์ฐ์ ์ค๋ฒ๋ก๋ฉ
map.get(โkeyโ)
map[โkeyโ]
get ๋ฉ์๋์ ๋ํ ๊ด๋ก
file.use({ f -> f.read() })
file.use{ it.read() }
๋๋ค๋ฅผ ๊ดํธ ๋ฐ์ผ๋ก ๋นผ๋ด๋ ๊ด๋ก
sb.append(โyesโ)
with(sb){append(โyesโ)}
์์ ๊ฐ์ฒด ์ง์ ๋๋ค
์ฝํ๋ฆฐ DSL์ clean-syntax๋ฅผ ์ ๊ณตํ๋ ๊ธฐ๋ฅ๊ณผ ๊ทธ๋ฐ ๊ตฌ๋ฌธ์ ํ์ฅํด ์ฌ๋ฌ ๋ฉ์๋ ํธ์ถ์ ์กฐํฉํจ์ผ๋ก์จ ๊ตฌ์กฐ๋ฅผ ๋ง๋ค์ด๋ด๋ ๊ธฐ๋ฅ์ ํ์ฉํ๋ค.
๊ทธ ๊ฒฐ๊ณผ๋ก DSL์ ๊ฐ๊ฐ์ ๋ฉ์๋ ํธ์ถ๋ง์ ์ ๊ณตํ๋ API์ ๋นํด ๋ ํํ๋ ฅ์ด ํ๋ถํด์ง๊ณ ์ฌ์ฉํ๊ธฐ ํธํด์ง๋ค.
Kotlin DSLs๋ ์ฝํ๋ฆฐ๊ณผ ๋ง์ฐฌ๊ฐ์ง๋ก, ์ปดํ์ผ ์์ ์ ํ์
์ด ์ ํด์ง๋ค(fully statically typed
). ๋ฐ๋ผ์ DSL ํจํด์ ์ฌ์ฉํ ๋๋ ์ฝํ๋ฆฐ์ ์ฅ์ ์ ๋๋ฆด ์ ์๋ค.
๋ค์์ DSLs์ด ํ ์ ์๋ ์ผ์ ์์์ด๋ค.
val yesterday = 1.days.ago
//์์ ์ฝ๋๋ ๋ค์๊ณผ ๊ฐ์ HTMl ์ฝ๋๋ฅผ ์์ฑํจ.
fun createSimpleTable() = createHTML().
table {
tr {
td { +"cell" }
}
}
์์ธํ ์ด์ผ๊ธฐ๋ฅผ ํ๊ธฐ ์ ์, ๋จผ์ DSLs์ด ๋ฌด์์ธ์ง ์ดํด๋ณด์.
์์ญ ํนํ ์ธ์ด(DSL)๋?
์์ญ ํนํ ์ธ์ด๋ผ๋ ๊ฒ ์์ฒด๋ ์ค๋๋ ๊ฐ๋
์ด๋ค. ๋ฒ์ฉ ํ๋ก๊ทธ๋๋ฐ ์ธ์ด(general-purpose programming language)๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ํ์ฌ ํ์ํ์ง ์์ ๊ธฐ๋ฅ์ ์์ค ์์ญ ํนํ ์ธ์ด๋ฅผ DSL
์ด๋ผ๊ณ ๋ถ๋ฅธ๋ค. ์ด๋ ํน์ ๋๋ฉ์ธ๊ณผ ๊ทธ ๋๋ฉ์ธ์ ์ฐ๊ด๋ ๊ธฐ๋ฅ์๋ง ์ง์คํ๋ ์ธ์ด๋ฅผ ์๋ฏธํ๋ฉฐ, SQL
์ด๋ ์ ๊ท์(regular expressions)
๋ฑ์ด ์ฌ๊ธฐ์ ์ํ๋ค. ์ด ์ธ์ด๋ค์ ํน์ task๋ฅผ ์ฒ๋ฆฌํ๋ ๋ฐ๋ ํผ๋ฅญํ๋, ์ด ์ธ์ด๋ง์ผ๋ก ์ ์ฒด ์ ํ๋ฆฌ์ผ์ด์
์ ๋ชจ๋ ์์ฑํ ์๋ ์๋ค.
๊ทธ๋ฌ๋ ์ด ์ธ์ด๋ค์ ์ ๊ณตํ๋ ๊ธฐ๋ฅ์ ์ถ์ํจ์ผ๋ก์จ ์์ ์ ๋ชฉํ๋ฅผ ํจ๊ณผ์ ์ผ๋ก ์ฑ์ทจํ ์ ์๋ค. SQL์ ์ฌ์ฉํ๊ธฐ ์ํด์๋ ํจ์๋ ํด๋์ค๋ฅผ ์์ฑํ ํ์๊ฐ ์๋ค. ์ด์ ๊ฐ์ด DSL
์ ๋ฒ์ฉ ํ๋ก๊ทธ๋๋ฐ ์ธ์ด์ ๋ฌ๋ฆฌ declarative(์ ์ธ์ )
ํ๋ค. ์ด์ ๋ฌ๋ฆฌ ๋ฒ์ฉ ํ๋ก๊ทธ๋๋ฐ ์ธ์ด๋ imperative
ํ๋ค.
declarative
๋ ๊ฒฐ๊ณผ๋ฅผ ๊ธฐ์ ํ๊ธฐ๋ง ํ๊ณ ๊ทธ ๊ฒฐ๊ณผ๋ฅผ ๋ฌ์ฑํ๊ธฐ ์ํ ์ธ๋ถ ์คํ์ ์ธ์ด๋ฅผ ํด์ํ๋ ์์ง์ ๋งก๊ธฐ๋๋ฐ, ์คํ ์์ง์ด ๊ฒฐ๊ณผ๋ฅผ ์ป๋ ๊ณผ์ ์ ์ต์ ํํ๊ธฐ ๋๋ฌธ์ declarative ์ธ์ด๊ฐ ๋ ํจ์จ์ ์ธ ๊ฒฝ์ฐ๊ฐ ์ข
์ข
์๋ค.
๊ทธ๋ฌ๋ DSLs์ ๋ํ ๋จ์ ๋ ๊ฐ์ง๊ณ ์๋๋ฐ, ๊ทธ๊ฑด ๋ฐ๋ก ๋ฒ์ฉ ์ธ์ด๋ก ๋ง๋ ์ ํ๋ฆฌ์ผ์ด์ ๊ณผ ์กฐํฉํ๊ธฐ ์ด๋ ต๋ค๋ ์ ์ด๋ค. DSLs์ ๊ณ ์ ํ ๋ฌธ๋ฒ์ ๊ฐ์ง๊ณ ์์ด ๋ค๋ฅธ ์ธ์ด๋ก ๋ง๋ ํ๋ก๊ทธ๋จ๊ณผ ํตํฉํ๊ธฐ ์ด๋ ต๋ค. ๋ฐ๋ผ์ DSL๋ก ์์ฑํ ํ๋ก๊ทธ๋จ์ ๋ค๋ฅธ ์ธ์ด์์ ํธ์ถํ๊ธฐ ์ํด์๋ DSL ํ๋ก๊ทธ๋จ์ ๋ณ๋์ ํ์ผ์ด๋ ๋ฌธ์์ด ๋ฆฌํฐ๋ด๋ก ์ ์ฅํด์ผ ํ๋ค.
ํ์ง๋ง ์ด๋ฐ ์์ผ๋ก DSL์ ์ ์ฅํ ๊ฒฝ์ฐ ํธ์คํธ ํ๋ก๊ทธ๋จ๊ณผ DSL์ ์ํธ์์ฉ์ ์ปดํ์ผ ์์ ์ ๊ฒ์ฆํ๊ฑฐ๋ DSL ํ๋ก๊ทธ๋จ์ ๋๋ฒ๊น
ํ๊ฑฐ๋ DSL ์ฝ๋ ์์ฑ์ ๋๋ IDE ๊ธฐ๋ฅ์ ์ ๊ณตํ๊ธฐ ์ด๋ ค์์ง๋ ๋ฌธ์ ๊ฐ ์๋ค. ์ด๋ฐ ๋ฌธ์ ๋ฅผ ๊ทน๋ณตํ๊ธฐ ์ํด internal DSL
์ด๋ผ๋ ๊ฐ๋
์ด ์ ๋ช
ํด์ง๊ณ ์๋ค.
internal DSL
๋
๋ฆฝ์ ์ธ ๋ฌธ๋ฒ ๊ตฌ์กฐ๋ฅผ ๊ฐ์ง external DSL
๊ณผ ๋ฌ๋ฆฌ internal DSL
์ ๋ฒ์ฉ ์ธ์ด๋ก ์์ฑ๋ ํ๋ก๊ทธ๋๋ฐ์ ์ผ๋ถ์ด๋ฉฐ ๋ฒ์ฉ ์ธ์ด์ ๋์ผํ ๋ฌธ๋ฒ์ ์ฌ์ฉํ๋ค. ์ฆ, internal DSL์ ๋
๋ฆฝ์ ์ธ ์ธ์ด๋ผ๊ธฐ๋ณด๋ค DSL์ ํต์ฌ ์ฅ์ ์ ์ ์งํ๋ฉด์ ์ฃผ ์ธ์ด๋ฅผ ํน๋ณํ๊ฒ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ด๋ค. ์ด ๋ ๊ฐ์ง ์ ๊ทผ๋ฒ์ ๋น๊ตํ๊ธฐ ์ํด external DSL
๊ณผ internal DSL
์์ ํ
์คํฌ๊ฐ ์ด๋ป๊ฒ ์๋ฃ๋๋์ง ํ์ธํด๋ณด์.
SELECT Country.name, COUNT(Customer.id)
FROM Country
JOIN Customer
ON Country.id = Customer.country_id
GROUP BY Country.name
ORDER BY COUNT(Customer.id) DESC
LIMIT 1
์ SQL์ ๊ฐ์ฅ ๋ง์ ๊ณ ๊ฐ์ด ์ด๊ณ ์๋ ๋๋ผ๋ฅผ ์์๋ด๋ ์ง์๋ฌธ์ผ๋ก, ์ง์ ์ธ์ด๊ฐ ์ฃผ ์ ํ๋ฆฌ์ผ์ด์ ์ธ์ด ์ฌ์ด์ ์ํธ ์์ฉํ๋ ๋ฐฉ๋ฒ์ ์ ๊ณตํด์ผ ํ๊ธฐ ๋๋ฌธ์ ๋ถํธํ ์๋ ์๋ค. ์๋๋ ์์ ๊ฐ์ ๊ธฐ๋ฅ์ ํ๋ ์ฝ๋์ง๋ง ์ฝํ๋ฆฐ์ผ๋ก ์์ฑ๋ ๊ฒ์ด๋ค.
(Country join Customer)
.slice(Country.name, Count(Customer.id))
.selectAll()
.groupBy(Country.name)
.orderBy(Count(Customer.id), isAsc = false)
.limit(1)
์ด ๋ ์ฝ๋๋ ๋์ผํ ํ๋ก๊ทธ๋จ์ ์์ฑํ๊ณ ์คํํ์ง๋ง, ๋ ๋ฒ์งธ ์ฝ๋๋ ์ผ๋ฐ ์ฝํ๋ฆฐ ์ฝ๋๋ก ์ผ๋ฐ ์ฝํ๋ฆฐ ๋ฉ์๋๋ฅผ ์ฌ์ฉํ๋ค. ์ด๋ฅผ internal DSL
์ด๋ผ๊ณ ๋ถ๋ฅธ๋ค.
DSL์ ๊ตฌ์กฐ
์ผ๋ฐ์ ์ธ API์ DSL ๊ฐ์ ๋ช ํํ ๊ตฌ๋ถ์ด๋ผ๋ ๊ฒ์ ์๋ค. โDSL์ ๋ณด๋ฉด ์ ์ ์๋คโ๋ผ๊ณ ๊ตฌ๋ถํ๊ณ ๋ ํ๋ค. DSLs์ ์ข ์ข ์ธ์ด์ ํน์ง์ ์์กดํ๋ฉฐ, ๋ค๋ฅธ ๋งฅ๋ฝ์์๋ ํญ๋๊ฒ ์ฌ์ฉ๋ ์ ์๋ค. ํ์ง๋ง ์์ฃผ ๊ฑฐ๋ก ๋๋ ํ๋์ ํน์ฑ์ ๋ฐ๋ก APIs์ ์กด์ฌํ์ง ์๋ ํน์ง, ์ฆ ๊ตฌ์กฐ(structure)์ ๋ฌธ๋ฒ(grammar)์ด๋ค.
์ผ๋ฐ์ ์ผ๋ก ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ๋ง์ ๋ฉ์๋๋ฅผ ๊ฐ์ง๊ณ ์์ผ๋ฉฐ, ํด๋ผ์ด์ธํธ๋ ์ด๋ฌํ ๋ฉ์๋๋ฅผ ํ๋์ฉ ํธ์ถํจ์ผ๋ก์จ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ ์ ์๋ค. ์ด๋ฌํ ์ผ๋ จ์ ํธ์ถ์ ๋ํ ๋ด๋ถ์ ์ธ ๊ตฌ์กฐ๋ ๋งฅ๋ฝ์ ์กด์ฌํ์ง ์๋๋ค. ์ด๋ฌํ API๋ฅผ command-query API๋ผ๊ณ ๋ถ๋ฅธ๋ค. ์ด์๋ ๋ฐ๋๋ก, DSL์์์ ๋ฉ์๋ ํธ์ถ์ ์ข ๋ ํฐ ๊ตฌ์กฐ๋ฅผ ๊ฐ์ง๊ณ ์๋๋ฐ, ์ด๋ DSL์ grammar๋ก ์ ์๋๋ค.
์ฝํ๋ฆฐ DSL์์๋ ๋ณดํต ๋๋ค๋ฅผ ์ค์ฒฉ์ํค๊ฑฐ๋ ๋ฉ์๋ ํธ์ถ์ ์ฐ์์ํค๋ ๋ฐฉ์์ผ๋ก ๊ตฌ์กฐ๋ฅผ ๋ง๋ ๋ค. ๊ทธ๋ฐ ๊ตฌ์กฐ๋ ์์์ ์ดํด๋ณธ SQL ์์ ์์ ํ์ธํ ์ ์์๋ค.
DSL์์๋ ์ง์๋ฅผ ์คํํ๊ธฐ ์ํด ํ์ํ ๋ฉ์๋๋ค์ ์กฐํฉํด์ผํ๋ฉฐ, ๊ทธ๋ ๊ฒ ๋ฉ์๋๋ฅผ ์กฐํฉํด์ ๋ง๋ ์ง์๋ ์ง์์ ํ์ํ ์ธ์๋ฅผ ๋ฉ์๋ ํธ์ถ ํ๋์ ๋ชจ๋ ๋๊ธฐ๋ ๊ฒ๋ณด๋ค ํจ์ฌ ๋ ๊ฐ๋ ์ฑ์ด ๋๋ค. DSL ๊ตฌ์กฐ์ ์ฅ์ ์ ๋งค๋ฒ ํจ์๊ฐ ํธ์ถ๋ ๋๋ง๋ค ๋ฐ๋ณตํ๋ ๊ฒ์ด ์๋, ์ฌ๋ฌ ๋ฒ์ ํจ์ ํธ์ถ์ ๋ํด ๊ฐ์ context๋ฅผ ๊ฐ์ง ์ ์๋ค๋ ๊ฒ์ด๋ค. ๋ค์ ์์ ๋ฅผ ์ดํด๋ณด์.
//Kotlin DSL
dependencies {
compile("junit:junit:4.11")
compile("com.google.inject:guice:4.1.0")
}
//command-query API
project.dependencies.add("compile", "junit:junit:4.11")
project.dependencies.add("compile", "com.google.inject:guice:4.1.0")
DSLs์ ๊ตฌ์กฐ๋ฅผ ๋ง๋ค๊ธฐ ์ํด ์ฌ์ฉํ๋ ๋ค๋ฅธ ๋ฐฉ๋ฒ์ Chained method calls(์ฐ์์ ์ธ ์ฝ๋ ์ค์์ ๊ฐ์ฒด์ Method๋ฅผ ๋ฐ๋ณต์ ์ผ๋ก ํธ์ถํ๋ ๊ฒ)์ด๋ค.
//kotlintest
str should startWith("kot")
//JUnit APIs
assertTrue(str.startsWith("kot"))
internal DSL๋ก HTML ๊ตฌ์ถ
์ด ์น์ ์์๋ internal DSL๋ก HTML ๊ตฌ์ถํ๋ ๋ฒ์ ์ข ๋ ์์ธํ๊ฒ ๋ฐฐ์ด๋ค.
//kotlinx.html library
fun createSimpleTable() = createHTML().
table {
tr {
td { +"cell" }
}
}
//HTML
<table>
<tr>
<td>cell</td>
</tr>
</table>
createSimpleTable
ํจ์๋ HTML ์กฐ๊ฐ์ ๊ฐ์ง๊ณ ์๋ string์ ๋ฐํํ๋ค. ๊ทธ๋ฐ๋ฐ ์ ์ง์ ์์ฑํ๋ ๊ฒ์ด ์๋๋ผ ์ฝํ๋ฆฐ์ ํตํด HTML์ ์์ฑํด์ผ ํ ๊น? ๊ทธ ์ด์ ๋ ์ฝํ๋ฆฐ์ type-safe
ํ๊ธฐ ๋๋ฌธ์ด๋ค. ์ฝํ๋ฆฐ์์๋ tr
ํ๊ทธ ์์๋ง td
ํ๊ทธ๋ฅผ ์์ฑํ ์ ์๋ค. ๋ ๋ค๋ฅธ ์ด์ ๋ ์ฝํ๋ฆฐ์ ๋ค๋ฅธ ์ด๋ค ์ธ์ด๋ ๋ด๋ถ์ ๊ตฌ์ถํ ์ ์๋ค๋ ์ ์ด๋ค. ์ด ๋ง์ ๋ชฉํํ๋ ๋ฐ์ดํฐ๋ฅผ ํฌํจํ๊ณ ์๋ HTML ์กฐ๊ฐ์ ๋์ ์ผ๋ก ์์ฑ ๊ฐ๋ฅํ๋ค๋ ๋ป์ด๋ค.
//kotlinx.html library
fun createAnotherTable() = createHTML().table {
val numbers = mapOf(1 to "one", 2 to "two")
for ((num, string) in numbers) {
tr {
td { +"$num" }
td { +string }
}
}
}
//HTML
<table>
<tr>
<td>1</td>
<td>one</td>
</tr>
<tr>
<td>2</td>
<td>two</td>
</tr>
</table>
์ด์ DSL์ด ๋ฌด์์ธ์ง, ๊ทธ๋ฆฌ๊ณ ์ ์ฌ์ฉํ๊ณ ์ถ์ ์ง๋ฅผ ์์์ผ๋ ์ฝํ๋ฆฐ์ด ์ด๋ฅผ ์ด๋ป๊ฒ ์ง์ํ๋ ์ง๋ฅผ ์ดํด๋ณด์. ๋จผ์ ์์ ๊ฐ์ฒด ์ง์ ๋๋ค
๋ฅผ ๋ด์ผ ํ๋ค.
๊ตฌ์กฐํ๋ API ๊ตฌ์ถ : DSL์์ ์์ ๊ฐ์ฒด ์ง์ ๋๋ค ์ฌ์ฉ
์์ ๊ฐ์ฒด ์ง์ ๋๋ค๋ ๊ตฌ์กฐํ๋ API๋ฅผ ๋ง๋ค ๋ ๋์์ด ๋๋ ๊ฐ๋ ฅํ ์ฝํ๋ฆฐ ๊ธฐ๋ฅ์ด๋ค.
์์ ๊ฐ์ฒด ์ง์ ๋๋ค์ ํ์ฅ ํจ์ ํ์
์ฐ๋ฆฌ๋ 5.5์์ with๋ apply๊ฐ์ scope function๋ฅผ ์๊ฐํ ๋ ์์ ๊ฐ์ฒด ์ง์ ๋๋ค์ ๋ํด ์ธ๊ธํ์๋ค. ์ด์ buildString ํจ์๋ฅผ ํตํด ์ฝํ๋ฆฐ์ด ์์ ๊ฐ์ฒด ์ง์ ๋๋ค๋ฅผ ์ด๋ป๊ฒ ๊ตฌํํ๋์ง ์ดํด๋ณด์.
buildString์ ํ StringBuilder ๊ฐ์ฒด์ ์ฌ๋ฌ ๋ด์ฉ์ ์ถ๊ฐํ ์ ์๋ค. ๋๋ค๋ฅผ ์ธ์๋ก ๋ฐ๋ buildString()
์ ์ ์ํด๋ณด์.
fun buildString(
builderAction: (StringBuilder) -> Unit
): String {
val sb = StringBuilder()
builderAction(sb)
return sb.toString()
}
>>> val s = buildString {
... it.append("Hello, ")
... it.append("World!")
... }
>>> println(s)
Hello, World!
์ด ์ฝ๋๋ ์ดํดํ๊ธฐ ์ฝ์ง๋ง ์ฌ์ฉํ๊ธฐ ํธ๋ฆฌํ์ง๋ ์๋๋ฐ, ๊ทธ ์ด์ ๋ ๋๋ค ๋ณธ๋ฌธ์์ ๋งค๋ฒ it์ ์ฌ์ฉํด StringBuilder๋ฅผ ์ฐธ์กฐํด์ผํ๊ธฐ ๋๋ฌธ์ด๋ค. ์์ ๊ฐ์ฒด ์ง์ ๋๋ค๋ฅผ ์ฌ์ฉํ์ฌ it
์ด๋ผ๋ ์ด๋ฆ์ ์ฌ์ฉํ์ง ์๋ ๋๋ค๋ฅผ ์ธ์๋ก ๋๊ฒจ๋ณด์.
fun buildString(
builderAction: StringBuilder.() -> Unit
) : String {
val sb = StringBuilder()
sb.builderAction()
return sb.toString()
}
>>> val s = buildString {
... this.append("Hello, ")
... append("World!")
... }
>>> println(s)
Hello, World!
๋ ์ฝ๋๋ฅผ ๋น๊ตํด ๋ณด๋ฉด, ์ฐ์ builderAction
๊ฐ ํ์ฅ ํจ์ ํ์
์ ์ฌ์ฉํ๋ค.
//ํ์ฅ ํจ์ ํ์
์ ์ธ ๊ณผ์
(StringBuilder) -> StringBuilder() -> StringBuilder.()
์ด์ ๋๋ค๋ฅผ ์์ ์ธ์๋ก ๋ณด๋ผ ์ ์๊ธฐ ๋๋ฌธ์, it
์ ์ ๊ฑฐํด๋ ๋๋ค. ๋ฐ๋ผ์ this.append()
์ ํํ๋ก ์ฌ์ฉ ๊ฐ๋ฅํ๋ค.
๋ ๋ฒ์งธ๋ก๋ buildString
ํจ์์ ์ ์ธ์ด ๋ฌ๋ผ์ก๋ค. (StringBuilder) -> Unit
๋ฅผ StringBuilder.() -> Unit
๋ก ๋์ฒดํ์๋ค.

๊ทธ๋ฐ๋ฐ ์ ๊ตณ์ด ํ์ฅ ํจ์ ํ์ ์ ์ฌ์ฉํ ๊น? ํ์ฅ ํจ์์ ๋ณธ๋ฌธ์์๋ ํ์ฅ ๋์ ํด๋์ค์ ์ ์๋ ๋ฉ์๋๋ฅผ ๋ง์น ๊ทธ ํด๋์ค ๋ด๋ถ์์ ํธ์ถํ๋ฏ์ด ์ฌ์ฉํ ์ ์๋ค.
์ฆ, sb.builderAction()์์ builderAction์ StringBuilder ํด๋์ค ์์ ์ ์๋ ํจ์๊ฐ ์๋๋ฉฐ StringBuilder ์ธ์คํด์ค์ธ sb๋ ํ์ฅ ํจ์๋ฅผ ํธ์ถํ ๋์ ๋์ผํ ๊ตฌ๋ฌธ์ผ๋ก ํธ์ถํ ์ ์๋ ํจ์ ํ์ ์ ์ธ์์ผ ๋ฟ์ด๋ค.
HTML builder์์ ์์ ๊ฐ์ฒด ์ง์ ๋๋ค ์ฌ์ฉ
HTML์ ์ํ Kotlin DSL์ ๋ณดํต HTML builder
๋ผ๊ณ ๋ถ๋ฆฐ๋ค. ์ด๋ type-safe builders
๋ผ๋ ์ปจ์
์ ๋ํํ๋ค. Builder๋ ๊ฐ์ฒด ๊ณ์ธต์ ๋ช
์์ ์ผ๋ก ํํํ๊ธฐ ์ํด ์ฌ์ฉ๋๋๋ฐ, ์ด๋ XML ๋๋ UI ์์๋ฅผ ํธ๋ฆฌํ๊ฒ ์์ฑํ ์ ์๋ค.
์ฝํ๋ฆฐ๋ ์์ ๊ฐ์ ์์ด๋์ด๋ฅผ ์ฌ์ฉํ์ง๋ง, ์ฝํ๋ฆฐ builder๋ type-safeํ๋ค.
fun createSimpleTable() = createHTML().
table {
tr {
td { +"cell" }
}
}
์์ ์ฝํ๋ฆฐ ์ฝ๋์์ table
, tr
, td
๋ ๊ฐ๊ฐ ํจ์์ด๋ค. ์ด๋ค์ ๋ชจ๋ ๊ณ ์ฐจ ํจ์๋ก, ๋๋ค๋ฅผ ์์ ๊ฐ์ฒด๋ก ๋ฐ์ ์ ์๋ค. ์ด๋ฌํ ๋๋ค๋ *์ด๋ฆ ํ์ด ๊ท์น(name-resolution rules)*์ ๋ฐ๊ฟ ์ ์๋ค. table
ํจ์์ ์ ๋ฌ๋ ๋๋ค ์์์๋ tr
ํจ์๋ฅผ ์ฌ์ฉํ ์ ์๋ค. ๋๋ค ๋ฐ์์๋ tr
ํจ์๋ unresolved๋๋ค.
์๋๋ HTML builder๋ฅผ ์์ ํ๊ฒ ๊ตฌํํ ๊ฒ์ด๋ค.
open class Tag(val name: String) {
private val children = mutableListOf<Tag>() //์ค์ฒฉ ํ๊ทธ ์ ์ฅ
protected fun <T : Tag> doInit(child: T, init: T.() -> Unit) {
child.init() //์์ ํ๊ทธ ์ด๊ธฐํ
children.add(child) //์์ ํ๊ทธ์ ๋ ํผ๋ฐ์ค ์ ์ฅ
}
override fun toString() =
"<$name>${children.joinToString("")}</$name>"
}
fun table(init: TABLE.() -> Unit) = TABLE().apply(init)
class TABLE : Tag("table") {
fun tr(init: TR.() -> Unit) = doInit(TR(), init) //TR ํ๊ทธ์ ์๋ก์ด ์ธ์คํด์ค ์ถ๊ฐ
}
class TR : Tag("tr") {
fun td(init: TD.() -> Unit) = doInit(TD(), init) //TD ํ๊ทธ์ ์๋ก์ด ์ธ์คํด์ค ์ถ๊ฐ
}
class TD : Tag("td")
fun createTable() =
table {
tr {
td {
}
}
}
>>> println(createTable())
<table><tr><td></td></tr></table>
๋ชจ๋ ํ๊ทธ๋ ์ค์ฒฉ ํ๊ทธ์ ๋ฆฌ์คํธ๋ฅผ ๊ฐ์ง๊ณ ์๊ณ , ์ด๋ฅผ ๋ฐ๋ผ ์์ฐจ์ ์ผ๋ก ํ๊ทธ๋ฅผ ๋ง๋ค์ด๊ฐ๋ค.
Kotlin builders: ์ถ์ํ์ ์ฌ์ฌ์ฉ ๊ฐ๋ฅ
๋ง์ฝ ํ๋ก๊ทธ๋จ์์ ์ผ๋ฐ์ ์ธ ์ฝ๋๋ฅผ ์์ฑํ๊ณ ์ ํ๋ค๋ฉด, ์ค๋ณต์ ํผํ๊ณ ์ฝ๋๋ฅผ ๋ณด๊ธฐ ์ข๊ฒ ๋ง๋ค๊ธฐ ์ํ ๋ค์ํ ๋๊ตฌ๋ค์ด ์๋ค. ๊ทธ ์ค ํ๋๋ ๋ฐ๋ณต์ ์ธ ์ฝ๋๋ฅผ ์๋ก์ด ํจ์๋ก ๋ง๋๋ ๊ฒ์ด๋ค. ๊ทธ๋ฌ๋ ์ด๋ฌํ ์ผ์ SQL์ด๋ HTML์์ ๊ต์ฅํ ์ด๋ ต๊ฑฐ๋ ์ฌ์ง์ด ๋ถ๊ฐ๋ฅํ ์๋ ์๋ค. ์ด๋ internal DSLs์ ์ฌ์ฉํ๋ฉด ์ถ์ํ๋ ๊ธฐ๋ฅ์ ๊ฐ์ง๋ ์๋ก์ด ํจ์๋ฅผ ๋ง๋ค์ด ์ฌ์ฌ์ฉํ ์ ์๋ค.
<div class="dropdown">
<button class="btn dropdown-toggle">
Dropdown
<span class="caret"></span>
</button>
<ul class="dropdown-menu">
<li><a href="#">Action</a></li>
<li><a href="#">Another action</a></li>
<li role="separator" class="divider"></li>
<li class="dropdown-header">Header</li>
<li><a href="#">Separated link</a></li>
</ul>
</div>
์์ ์ฝ๋์์๋ div
, button
, ul
, li
์ ๊ฐ์ ํจ์๋ฅผ ํตํด ๊ฐ์ ๊ตฌ์กฐ๋ฅผ ๋ฐ๋ณต์ ์ผ๋ก ๋ง๋ค ์ ์๋ค. ํ์ง๋ง ์ด๊ฒ๋ณด๋ค ๋ ์ ํ ์ ์๋ ๋ฐฉ๋ฒ๋ ์กด์ฌํ๋ค.
fun dropdownExample() = createHTML().dropdown {
dropdownButton { +"Dropdown" }
dropdownMenu {
item("#", "Action")
item("#", "Another action")
divider()
dropdownHeader("Header")
item("#", "Separated link")
}
}
์์ ์ฝ๋๋ ํจ์ํ๋ฅผ ํ์ผ๋ฉด์๋ ๋ถํ์ํ ๋ํ ์ผ์ ์จ๊ธฐ๊ณ ์์ด ๋ ๋ณด๊ธฐ ์ข์ ์ฝ๋๋ฅผ ๋ง๋ ๋ค.
invoke convention์ ์ฌ์ฉํ ๋ ์ ์ฐํ ๋ธ๋ก ์ค์ฒฉ
invoke convention์ ์ฌ์ฉํ๋ฉด ํจ์์ฒ๋ผ ํธ์ถํ ์ ์๋ ๊ฐ์ฒด๋ฅผ ๋ง๋๋ ํด๋์ค๋ฅผ ์ ์ํ ์ ์๋ค. ํ์ง๋ง ์ด ๊ธฐ๋ฅ์ ์ผ์์ ์ผ๋ก ์ฌ์ฉํ๊ธฐ ์ํด์ ๋ง๋ค์ด์ง ๊ธฐ๋ฅ์ ์๋๋ค. invoke convention์ ๋จ์ฉํ๋ฉด 1()
๊ณผ ๊ฐ์ด ์ดํดํ๊ธฐ ์ด๋ ค์ด ์ฝ๋๊ฐ ์๊ธธ ์ ์๋ค. ๊ทธ๋ฌ๋ DSL์์๋ invoke convention์ด ์ ์ฉํ ๊ฒฝ์ฐ๊ฐ ์์ฃผ ์๋ค.
invoke convention : ํจ์์ฒ๋ผ ํธ์ถํ ์ ์๋ ๊ฐ์ฒด
์ฐ๋ฆฌ๋ ์ด๋ฏธ 7์ฅ์์ ์ฝํ๋ฆฐ์ convention์ ๋ํ์ฌ ํ์ตํ์๋ค. ๊ฐ๋ น foo
๋ผ๋ ๋ณ์๊ฐ ์๊ณ foo[bar]
๋ผ๋ ์์ ์ฌ์ฉํ๋ฉด ์ด๋ foo.get(bar)
๋ก ๋ณํ๋๋ค. ์ด๋ get์ Foo๋ผ๋ ํด๋์ค ์์ ์ ์๋ ํจ์์ด๊ฑฐ๋ ํ์ฅ ํจ์์ฌ์ผ ํ๋ค.
invoke convention ์ญ์ ๊ฐ์ ์ญํ ์ ์ํํ๋๋ฐ, get๊ณผ๋ ๋ค๋ฅด๊ฒ ๊ดํธ()๋ฅผ ์ฌ์ฉํ๋ค. operator
๋ณ๊ฒฝ์๊ฐ ๋ถ์ invoke
๋ฉ์๋ ์ ์๊ฐ ๋ค์ด์๋ ํด๋์ค์ ๊ฐ์ฒด๋ ํจ์์ฒ๋ผ ํธ์ถํ ์ ์๋ค. ๋ค์์ ์ด์ ๋ํ ์์์ด๋ค.
class Greeter(val greeting: String) {
operator fun invoke(name: String) { //invoke ๋ฉ์๋ ์ ์
println("$greeting, $name!")
}
}
>>> val bavarianGreeter = Greeter("Servus") //ํจ์๋ก์จ Greeter ์ธ์คํด์ค ํธ์ถ
>>> bavarianGreeter("Dmitry")
Servus, Dmitry!
์์ ์ฝ๋์์๋ bavarianGreeter ๊ฐ์ฒด๊ฐ ๋ง์น ํจ์์ฒ๋ผ ํธ์ถ๋๋ ๊ฒ์ ํ์ธํ ์ ์๋ค. ์ด ๋ bavarianGreeter(โDmitryโ)
๋ ๋ด๋ถ์ ์ผ๋ก bavarianGreeter.invoke(โDmitryโ)
๋ก ์ปดํ์ผ๋๋ค.
invoke convention : convention๊ณผ ํจ์ํ ํ์
invoke convention์์ ๋ฐฐ์ ๊ธฐ ๋๋ฌธ์, ์ฐ๋ฆฌ๋ ์ผ๋ฐ์ ์ธ ๋๋ค ํธ์ถ ๋ฐฉ์(๋๋ค ๋ค์ ๊ดํธ๋ฅผ ๋ถ์ด๋ ๋ฐฉ์: lambda()
)์ด ์ค์ ๋ก๋ invoke convention์ ์ฌ์ฉํ๋ ๊ฒ์ ์ง๋์ง ์์์ ์ถฉ๋ถํ ์ ์ ์๋ค.
์ธ๋ผ์ธ๋ ๋๋ค๋ฅผ ์ ์ธํ ๋ชจ๋ ๋๋ค๋ ํจ์ํ ์ธํฐํ์ด์ค(Function1 ๋ฑ)์ ๊ตฌํํ๋ ํด๋์ค๋ก ์ปดํ์ผ๋๋ค. ๊ฐ ํจ์ํ ์ธํฐํ์ด์ค ์์๋ ๊ทธ ์ธํฐํ์ด์ค ์ด๋ฆ์ด ๊ฐ๋ฆฌํค๋ ๊ฐ์๋งํผ(์๋ฅผ ๋ค์ด, Function1์ด๋ผ๋ฉด 1๊ฐ**) ํ๋ผ๋ฏธํฐ๋ฅผ ๋ฐ๋ invoke ๋ฉ์๋**๊ฐ ๋ค์ด์๋ค.
๋๋ค๋ฅผ ํจ์์ฒ๋ผ ํธ์ถํ ์ ์์ผ๋ฉด ์ด๋ค ์ ์ด ์ข์๊น? ์ฐ์ ๋ณต์กํ ๋๋ค๋ฅผ ์ฌ๋ฌ ๋ฉ์๋๋ก ๋ถ๋ฆฌํ๋ฉด์๋ ์ฌ์ ํ ๋ถ๋ฆฌ ์ ์ ๋๋ค์ฒ๋ผ ์ธ๋ถ์์ ํธ์ถํ ์ ์๋ ๊ฐ์ฒด๋ฅผ ๋ง๋ค ์ ์๋ค.
DSL์ invoke convention : Gradle์์ ์์กด๊ด๊ณ ์ ์
dependencies.compile("junit:junit:4.11")// ์ฒซ ๋ฒ์งธ๋ฐฉ์
dependencies {// ๋ ๋ฒ์งธ ๋ฐฉ์
compile("junit:junit:4.11")
}
์ ์ฝ๋์์, ์ฒซ ๋ฒ์งธ ๊ฒฝ์ฐ๋ dependenices ๋ณ์์ ๋ํด compile ๋ฉ์๋๋ฅผ ํธ์ถํ๊ณ , ๋ ๋ฒ์งธ ๊ฒฝ์ฐ์๋ dependenices ์์ ๋๋ค๋ฅผ ๋ฐ๋ invoke ๋ฉ์๋๋ฅผ ์ ์ํ๋ฉด ๋ ๋ฒ์งธ ๋ฐฉ์์ ํธ์ถ์ ์ฌ์ฉํ ์ ์๋ค.
์ด๋ฌํ invoke convention์ผ๋ก ์ธํด DSL API์ ์ ์ฐ์ฑ์ด ์ปค์ง๊ฒ ๋๋ค.
์ค์ ์ฝํ๋ฆฐ DSL
์ด์ ๋ถํฐ๋ ์ค์ฉ์ ์ธ DSL ์์ , ์ฆ ํ ์คํธ ํ๋ ์์ํฌ, ๋ ์ง ๋ฆฌํฐ๋ด, ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ง์ ๋ฑ์ ๋ํด ์ดํด๋ณด๋๋ก ํ์.
์ค์ ํธ์ถ ์ฐ์ : ํ
์คํธ ํ๋ ์์ํฌ์ should
์์ ์ดํด๋ณธ kotlintest DSL์์ ์ค์ ํธ์ถ์ ์ด๋ป๊ฒ ํ์ฉํ๋์ง ์ดํด๋ณด์.
s should startWith("kot")
์ ๊ฐ์ ์ฝ๋์์, s์ ๋ค์ด๊ฐ ๊ฐ์ด kot๋ก ์์ํ์ง ์์ผ๋ฉด ์ด ๋จ์ธ์ ์๋ฌ๊ฐ ๋๊ฒ ๋๋ค. ์ด ์ฝ๋๋ ๋ง์น The s string should start with this constant์ฒ๋ผ ์ฝํ๊ฒ ๋๋ค. ์ด ๋ชฉ์ ์ ๋ฌ์ฑํ๊ธฐ ์ํด์๋ should ํจ์ ์ ์ธ ์์ infix
๋ณ๊ฒฝ์๋ฅผ ๋ถ์ฌ์ผ ํ๋ค(๊ทธ๋์ผ ์ค์ ํจ์๊ฐ ๋๋ฏ๋ก).
infix fun <T> T.should(matcher: Matcher<T>) = matcher.test(this)
์์ ๊ฐ์ด should ํจ์๋ฅผ ๊ตฌํํ ๋, should ํจ์๋ Matcher์ ์ธ์คํด์ค๋ฅผ ์๊ตฌํ๋ค. Matcher๋ ๊ฐ์ ๋ํ ๋จ์ธ๋ฌธ์ ํํํ๋ ์ ๋ค๋ฆญ ์ธํฐํ์ด์ค๋ก, startWith
๋ก ๊ตฌํ๋๋ฉฐ, ์ด๋ ์ด๋ค ๋ฌธ์์ด์ด ์ฃผ์ด์ง ๋ฌธ์์ด๋ก ์์ํ๋์ง ๊ฒ์ฌํ๋ค.
kotlintest DSL์์ ์ฐ์์ ์ธ ํธ์ถ์ ์ฌ์ฉํ๋ฉด, ๋ค์๊ณผ ๊ฐ์ด ๋ง๋ค ์๋ ์๋ค.
"kotlin" should start with "kot"
์ด ๋ฌธ์ฅ์ ์ฝํ๋ฆฐ์ฒ๋ผ ๋ณด์ด์ง ์๋๋ค. ์ด ๋ฌธ์ฅ์ด ์ด๋ป๊ฒ ์๋ํ๋์ง ๋ณด๊ธฐ ์ํด์๋ ์ค์ ํธ์ถ์ ์ผ๋ฐ์ ์ธ ๊ฒ์ผ๋ก ๊ณ ์ณ์ผ ํ๋ค.
"kotlin".should(start).with("kot")
์์ ์ฝ๋๋ฅผ ํตํด์ ์ฐ๋ฆฌ๋ should
์ with
๊ฐ ์ฐ์์ ์ผ๋ก ์ค์ ํธ์ถ์ ํ๋ค๋ ์ฌ์ค์ ์ ์ ์๋ค.
์์ ํ์
์ ๋ํ ํ์ฅ ํจ์ ์ ์: ๋ ์ง ์ฒ๋ฆฌ
์ฑํฐ๊ฐ ์์ํ ๋ ๋ณด์๋ ์์ ์ค ๋ ์ง์ ๋ํด์ ์ดํด๋ณด์.
**val** yesterday = 1.days.ago
**val** tomorrow = 1.days.fromNow
์ด DSL์ java.time API์ ์ฝํ๋ฆฐ์ ํตํด ๊ตฌํํ๊ธฐ ์ํด์๋ ๋ช ์ค์ ์ฝ๋๋ง ์์ผ๋ฉด ๋๋ค.
import java.time.Period
import java.time.LocalDate
val Int.days: Period
get() = Period.ofDays(this)
val Period.ago: LocalDate
get() = LocalDate.now() - this
val Period.fromNow: LocalDate
get() = LocalDate.now() + this
println(1.days.ago)
// 2020-05-15
println(1.days.fromNow)
// 2020-05-17
๋ฉค๋ฒ ํ์ฅ ํจ์ : SQL์ ์ํ ๋ด๋ถ DSL
์ด์ ๋ฉค๋ฒ ํ์ฅ์ ์ฌ์ฉํ๋ ์์ ๋ฅผ ์ดํด๋ณด์. ๋ค์์ ์ต์คํฌ์ฆ๋ ํ๋ ์์ํฌ์์ ์ ๊ณตํ SQL์ ์ํ internal DSL์์ ๊ฐ์ ธ์จ ์์ ์ด๋ค. ์ต์คํฌ์ฆ๋ ํ๋ ์์ํฌ์์ SQL๋ก ํ ์ด๋ธ์ ๋ค๋ฃจ๊ธฐ ์ํด์๋ Table ํด๋์ค๋ฅผ ํ์ฅํ ๊ฐ์ฒด๋ก ๋์ ํ ์ด๋ธ์ ์ ์ํด์ผ ํ๋ค.
object Country: Table() {
val id = integer("id").autoIncrement().primaryKey()
val name = varchar("name", 50)
}
์์ ์ ์ธ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ํ
์ด๋ธ๊ณผ ์ผ์นํ๋ค. ์ด ํ
์ด๋ธ์ ์์ฑํ๊ธฐ ์ํด์๋ SchemaUtils.create(Country)
๋ฉ์๋๋ฅผ ํตํด ๋ค์๊ณผ ๊ฐ์ SQL์ ๊ตฌํํด์ผ ํ๋ค.
CREATE TABLE IF NOT EXISTS Country (
id INT AUTO_INCREMENT NOT NULL,
name VARCHAR(50) NOT NULL,
CONSTRAINT pk_Country PRIMARY KEY (id)
)
Last updated