For several years we have witnessed a small revolution in the world
of network applications. The concept of returning ready HTML documents
is being replaced by dynamic JS applications generating client-side
views. This does not mean, however, that the traditional approach is
completely obsolete. In fact, it is still a huge part of web services.
Today we will compare two such solutions.
A few years ago, when generating views on the server side was the
most common practice, the template mechanisms were considered the best
and most modern solution. In the world of JVM we have two classic and
official standards - JSP and JSF. JSP was widely disliked and
criticized mainly due to its mess of HTML and Java code. Their
successor - JSF - was tightly integrated with the rest of JEE, which
was replaced on the market by Spring, so that the JSF itself did not
gain much popularity. It seems to me that the architectural specificity
also contributed to this. In SpringMVC, each HTTP request corresponds
to one controller method (request-based MVC), which makes it easy for
the developer to manage the authentication and logic of the request and
to debug it - everything is in one place. JSF is a solution based on
distributed ManagedBeans components (component-based MVC) - interesting
but also much less convenient. Few know that Oracle in JEE 8 (2017)
also presented the request-based approach (JSR 371 - JEE MVC), but it
did not gain much popularity. These solutions, although they can still
be found, have been quite effectively replaced by unofficial
technologies such as FreeMarker or Thymeleaf - especially in
combination with Spring MVC. Although generating views on the server
side is losing popularity, they are still common standards.
Templates have been the standard for creating server-side views for
several years. Nobody is going to concat strings in the controller,
right? Solutions generating HTML code using objects representing HTML
elements have always had marginal popularity due to the low convenience
of their use and dissimilarity to HTML code. Or rather it was so until
the introduction of the Kotlin DSL.
If someone does not know what Kotlin DSL is, I will explain it
briefly. In general, Domain-Specific-Language is a language specific to
a given, narrow use - as opposed to general purpose languages (GPL).
Examples include AWK or even the REGEX. HTML itself is also a DSL as
well as template languages. Kotlin DSL is, however, internal DSL, i.e.
the domain language within the general-purpose language which is
Kotlin. Therefore, it is not a separate language, but rather a subset
of it for a specific application. Or at least that's what its creators
define it, because in my opinion they are simply interesting syntax
sugars that allow to convenient creation of objects: extension
functions, lambdas with receivers and lambdas outside of method
parentheses. As a result, many different "DSL" can be created for
specific applications. Anyway, look at this banal and simplified
example:
data class Document(var attribute: String? = null, var subElement: SubElement? = null)
data class SubElement(var subattribute: String? = null)
fun document(block: Document.() -> Unit): Document {
val document = Document()
block(document)
return document
}
fun Document.subElement(block: SubElement.() -> Unit): Unit {
subElement = SubElement().apply(block)
}
// example of use
document {
attribute = "A"
subElement {
subattribute = "B"
}
}
The key elements of this solution are, of course, the "block"
parameters of the Document.() -> Unit
and SubElement.() -> Unit
types. They are simply lambdas with receivers that can be called on
Document
and SubElement
objects (in fact taking such an object as a
parameter). Thanks to this simple trick, composing objects (e.g. HTML
page representatives) looks almost as good as a template (it resembles
JSON syntax) and we stay in the programming language
with all its benefits. Well, let's see some real example. The home page
of Hex.log is currently created using such a template:
<!DOCTYPE html>The equivalent of this in the Kotlin DSL is the following code:
<html th:lang="${#locale}">
<head th:replace="head.html :: head"></head>
<body onresize="resize_posts()">
<header th:replace="header.html :: header"></header>
<div class="main-body">
<br>
<!--/*@thymesVar id="posts" type="java.util.List<domain.Post>"*/-->
<div class="post" th:each="post, stat: ${posts}">
<!--/*@thymesVar id="post" type="domain.Post"*/-->
<button th:id="${post.id.value}" class="post-header openable" onclick="toggle_collapse(this)" th:onauxclick="|window.open('${#locale}/post/${post.id.value}')|">
<p>
<span th:text="${#temporals.format(post.createDate, 'dd.MM.yyyy')}" class="post-date"></span>
<span th:text="${post.title}" class="post-title"></span>
</p>
<p th:text="${post.shortcut}"></p>
</button>
<div class="post-panel"></div>
</div>
<br>
</div>
</body>
</html>
fun mainPage(language: Language, posts: Array<Post>): HTML.() -> Unit = page(language) {
br()
posts.forEach { post ->
div("post") {
button(classes = "post-header openable") {
id = "${post.id?.value}"
onClick = "toggle_collapse(this)"
attributes["onAuxClick"] = "window.open('/${language}/post/${post.id?.value}')"
p {
span("post-date") { +post.createDate.formatted() }
span("post-title") { +post.title }
}
p { +post.shortcut }
}
div("post-panel")
}
}
}
And how it looks like to you? In my opinion, it is quite bearable, although it also has its cons so let's compare both solutions:
<!--/*@thymesVar id="posts" type="java.util.List<domain.Post>"*/-->
<head th:replace="head.html :: head"></head>
attributes["onAuxClick"] = "window.open('www.example.com')"
So how does the comparison of both solutions looks like? I
personally prefer KDSL. Although it is usually a bit uglier, it gives
me more freedom to compose and better flow control. I am glad that the
developers of ktor-html-builder have made it possible to insert custom
attributes. Without it, it would not be possible on Hex.log to
conveniently implement opening a post in a new window by clicking the
middle button on the title bar (did you notice this functionality?)
Should we announce the end of templates then? Contrary to Betteridge's law of headlines, I believe so. Not only for HTML generators, but in fact all such documents - XML, JSON, YAML, etc. Of course it will not happen immediately - templates are not a horrible solution, but there are better alternatives. I think they will become obsolete over time, and programming with XML and templates will be seen on a par with the GOTO instruction.
I am also making this change on Hex.log. I make a pull request of the Thymeleaf exchange for ktor-html-builder on Github. Review it and let me know what do you think. I would also like to thank my friends from the Hex.log Reviewers group: Gosia and Grzesiek for their help. If you want to join them and help in the development of this blog, write to me!