Saturday, December 27, 2014

Trying Thymeleaf for one day: Why is it so hard?

In a project that uses fully stateless server-side frontend, I tried to use Spring MVC with Thymeleaf, using Spring Boot conventions. I've been using Apache Wicket for 2-3 years already so I'm definitely new with Thymeleaf ways, but I have some experience with Spring MVC. The reason why I wanted to use Spring MVC instead of Wicket is because if the app is entirely stateless, I won't be using like 90% of what Wicket is good for (statefulness and component-based framework).

My feelings? Since it's just one day there are definitely lots of things I might be missing out... but at this point I'm somewhat demotivated by Thymeleaf. Let me explain why.

Thymeleaf is a view layer with the goals of having great HTML5 support and local previews (without server). Note that I won't be describing Thymeleaf in detail, there's introduction tutorial to Thymeleaf if you like.

Basics

The first hours are good, there's good support for string manipulation, SpEL expressions, URI manipulations, and dynamic lists are easy:

<ul>
   <li th:each="place : ${places}">
       <a th:href="|/${localePrefId}/${placeSlugs[place.id]}|" th:text="${place.name}">Baso Enggal Malang</a>
   </li>
</ul>

The ${} @{} #{} and |...| (in some cases nested) syntax looks a bit arcane to me, but I can live with them, since I can't think of a better syntax anyway.

Layouts

This is my first uneasyness: layouts aren't built-in. However, you can either integrate with Apache Tiles 2 (why Tiles 2, not Tiles 3?) or use the Layout Dialect.

I'm not familiar with Tiles, although it looks great but I went with Layout Dialect, with the gut feeling that it's more "Thymeleaf native" than the Tiles integration. Sample templates for Gigastic:

The layout - layout.html

<div layout:fragment="content">
    Hello world!
</div>

The content - city.html


<div layout:fragment="content" class="about">
    <nav>
        <ol class="breadcrumb">
...
</div>

Ok, that's great! I'm happy. :)

Amazing Surprise: Automatic <head> elements

I was trying to find out how to propagate the HTML metadata tags (<title>, <meta name="description">, etc.) from the main template through the layout so they appear on the final rendered page. Imagine my delight when it's automatic! I haven't read anything about this feature on the Thymeleaf guide, but it works out of the box and I'm excited about it!

In Wicket you'd typically do something like:

<title wicket:id="title">Gigastic: Exciting Times Together!</title>
<meta wicket:id="metaDescription" name="description" content="Wow"/>

then back it in the superclass WebPage with:

add(new Label("title", getTitleModel()));
add(new MetaTag("metaDescription", getMetaDescriptionModel()));
...
protected abstract IModel<String> getTitleModel();
protected abstract IModel<String> getMetaDescriptionModel();

Nothing hard, but in Thymeleaf I can just do:

<title>Bandung, Jawa Barat, Indonesia</title>
<meta name="description" content="Informasi daftar tempat wisata di Bandung"/>

And they'll automatically replace the values from the layout. Neat!

Note that Wicket also has <wicket:head> but it only works for adding stuff into head, not for overriding them.

The Bad: Pagination

After that it goes downwards, at least in my opinion. For pagination, Spring MVC is nice, which can resolve the pagination URI parameters (page=, size= and sort=) as Pageable object which is directly usable:

@RequestMapping(Array("id/bandung"))
def bandung(model: ModelMap, pageable: Pageable) = {
  model.put("localePrefId", "id")
  model.put("pageable", pageable)
  val places = placeRepo.findAll(pageable)
  val placeSlugs = mapAsJavaMap(places.asScala
    .map { it: Place => it.getId() -> SlugUtils.generateSegment(it.getName()) }.toMap)
  model.put("places", places)
  model.put("placeSlugs", placeSlugs)
  log.debug("placeSlugs={}", placeSlugs)
  "city"
}

The Thymeleaf template, umm... I'm quite disappointed. First I wanted to know if there's already Thymeleaf-Bootstrap library project out there, which provides ready-to-use components like Bootstrap pagination. Nope, the first hit for "thymeleaf bootstrap pagination" is Yuan Ji's March 2013 article, which feels quite recent to me, and made me sad. Because at that time Wicket-Bootstrap would already be running circles around Thymeleaf (oh, with AJAX support too)! Even Wicket's built-in PagingNavigator is way ahead of this. But here it goes:

<div class="pagination">
    <ul>
        <li>
            <span th:if="${!places.hasPrevious()}" class="disabled">Prev</span>
            <a th:if="${places.hasPrevious()}" th:href="@{${#httpServletRequest.requestURI}(page=${places.number-1})}">Prev</a>
        </li>
        <li><span th:text="${places.number+1}">1</span></li>
        <li>
            <span th:if="${!places.hasNext()}" class="disabled">Next</span>
            <a th:if="${places.hasNext()}" th:href="@{${#httpServletRequest.requestURI}(page=${places.number+1})}">Next</a>
        </li>
    </ul>
</div>

As you can see I'm too lazy to implement all of Yuan Ji's code. Also, you can see some "logic" up there in th:if, but since it's simple boolean evaluation it's a simple logic that shouldn't be a problem in a template. In AngularJS I'd have done it the same way.

One thing not in my taste is the verbosity in getting the current page URI: ${#httpServletRequest.requestURI}. But again I can live with it, since it's easy to make my own variable anyway, but I wonder why they don't make this commonly used variable use shorter alias.

Now if I'm going to write that much code just for pagination, I'd better find a way to keep a library of them and reuse it in many pages, in various ways...

Custom Reusable Components/Tag Libraries? How about Extensibility?


You wish.

Good programmers know what to write. Great ones know what to rewrite (and reuse).  - Eric S. Raymond
Please, PLEASE, PLEASE prove me wrong, but I've come to the hypothesis that in practice it's not feasible.

The Thymeleaf way of reusability/extensibility is via dialects and processors.

The terms may sound foreign, but wait until you see how to dynamically change a tag's CSS class by using a Thymeleaf processor: (I try to be nice and reduce the lines of code visually, so I remove the comments)

public class ClassForPositionAttrProcessor
        extends AbstractAttributeModifierAttrProcessor {

    public ClassForPositionAttrProcessor() {
        super("classforposition");
    }

    public int getPrecedence() {
        return 12000;
    }

    @Override
    protected Map<String, String> getModifiedAttributeValues(
            final Arguments arguments, final Element element, final String attributeName) {
        final Configuration configuration = arguments.getConfiguration();
        final String attributeValue = element.getAttributeValue(attributeName);
        final IStandardExpressionParser parser =
                StandardExpressions.getExpressionParser(configuration);
        final IStandardExpression expression =
                parser.parseExpression(configuration, arguments, attributeValue);
        final Integer position =
                (Integer) expression.execute(configuration, arguments);
        final Remark remark = RemarkUtil.getRemarkForPosition(position);
        final Map<String,String> values = new HashMap<String, String>();
        if (remark != null) {
            switch (remark) {
                case WORLD_CHAMPIONS_LEAGUE:
                    values.put("class", "wcl");
                    break;
                case CONTINENTAL_PLAYOFFS:
                    values.put("class", "cpo");
                    break;
                case RELEGATION:
                    values.put("class", "rel");
                    break;
            }
        }
        return values;
    }

    @Override
    protected ModificationType getModificationType(final Arguments arguments, 
            final Element element, final String attributeName, 
            final String newAttributeName) {
        return ModificationType.APPEND_WITH_SPACE;
    }

    @Override
    protected boolean removeAttributeIfEmpty(final Arguments arguments,
            final Element element, final String attributeName, 
            final String newAttributeName) {
        return true;
    }

    @Override
    protected boolean recomputeProcessorsAfterExecution(final Arguments arguments,
            final Element element, final String attributeName) {
        return false;
    }
}

That's not including the dialect code, and the code needed to register the dialect into SpringTemplateEngine. (which isn't much, but I'll explain later why it could be problematic)

That seems way too much ceremony and low-level just to dynamically change the CSS class. Thymeleaf definitely could use a major improvement in this area. Compare this with how we do it Wicket style: (oh yeah, baby!)

final Remark remark = RemarkUtil.getRemarkForPosition(position);
final Map<String,String> values = new HashMap<String, String>();
if (remark != null) {
    String cssClass = null;
    switch (remark) {
        case WORLD_CHAMPIONS_LEAGUE:
            cssClass = "wcl";
            break;
        case CONTINENTAL_PLAYOFFS:
            cssClass = "cpo";
            break;
        case RELEGATION:
            cssClass = "rel";
            break;
    }
    
item.add(new CssClassNameAppender(cssClass));
}

The Wicket code is just one line, unobtrusive and doesn't distract you from the main logic (which form the bulk of the code). Oh and Wicket Behaviors are crazily reusable and extensible, too!

Also of note is that Thymeleaf processors doesn't seem to support Spring dependency injection but only @Resource injection, so you have to call appCtx.getBean(...) thing. Not a dealbreaker, but it's quite inconvenient (Wicket supports Spring dependency injection in many places, and pretty much anywhere with Injector.get.inject()).

The important thing is that by using dialects/processors, you lose what Thymeleaf prides in the first place: local previewability. Not that I care about this particular feature. In Bippo, after the initial mockup phase our designers always work with the development server (since 95% things that can look wrong only happens when you put in dynamic data and behavior anyway) and close to 0% of our templates are locally previewable, even though you can make Wicket templates locally previewable if you want to. But hey, if you boast so much about local previewability but then it's gone when you try to reuse stuff (which should be common use in software development), then the feature is as good as gone in the first place.

It was scary to me at this point, then I wondered if this really how it's done in the real-world... and the horror: it is. An evidence is from popular open e-commerce software Broadleaf Commerce's ContentProcessor.java. Broadleaf uses Thymeleaf templating and they write their own dialect using "blc" prefix, and lots of processors.

In Wicket-speak this is akin to having only onComponentTag() and no behaviors, no (Generic)Panels, and no subclassing of components. Sounds cumbersome to me.

But consider that I can make a custom dialect and all the processors I want, and assuming they're quite reusable to be put in a separate library to reuse across projects... are they extensible? By this I mean is possible to subclass a processor, override a method to change its behavior, and use it easily on a few pages?

It seems not. The whole processor architecture isn't designed to be re-extensible, by that I mean it's designed to extend Thymeleaf, but not to extend a processor further. Even if you try to extend it, the primitives are too low-level to make extensibility practical. This is apparent in the "flatness" of Broadleaf Commerce's processor hierarchy: it doesn't have a class hierarchy of processor, what it has are a few base processor classes and many separate processors.

A Thymeleaf processor is so primitive it has no concept of a model (the M in MVC), let alone a typesafe version of it. Also there's no way to easily extend a processor from an imported dialect and then use it. You have to create a custom dialect which lists the extended processor and then use it. Again, such ceremony on top of ceremony. And that's still HTML, it'd be even harder to add in AJAX behaviors in a reusable and extensible way with Thymeleaf.

Wicket has Behaviors, which are way ahead than this. And it's easy to have AJAX functionality as well, extensible, and pretty much the only ceremony is .add() and new SomethingBehavior() normal Java instantiation. Behaviors aren't the only way to have reusability, the primary reusability mechanism of Wicket is its entire fleet of component hierarchy, infinitely subclassable/extensible, with probably the most common (but in no way limited to) superclass is (Generic)Panel. And you can put them in a reusable and extensible library such as Soluvas Web.

Dandelion DataTables's Thymeleaf integration is another evidence:


Not only that the Thymeleaf version still forces the developer to add required HTML markup (which probably will be duplicated in many pages), but it's actually more verbose than the older JSP taglib-based technology it's supposed to supersede. Where's the DRY?

This is the most painful revelation for me against Thymeleaf, and it's the primary reason why then I'm looking to find an alternative to Thymeleaf, but before that there are some other things.

Bootstrap (and Bootswatch, and Font-Awesome, and...) Integration... and Taglibs

Formerly I tried to find any kind of Bootstrap-Thymeleaf integration, including the usual companions like Bootswatch, Font-Awesome, and the likes. I didn't find any worth including, for reasons that was unknown to me until I found out the reusability issue (I wanted to call it design flaw but it's probably by design) above.

Thymeleaf doesn't support JSP tag libraries (JSTL), since it breaks local previewability but in JSTL's place it has processors which can do some things JSTL can do plus some but in Thymeleaf-specific way and still breaks local previewability.

Having been spoiled by Wicket Bootstrap and Wicket Stuff, this is a huge letdown. :(

So I added a bunch of webjars to my build.gradle file and links to them in my Thymeleaf templates and that's when I get to the next point...

The CDN, URI References, Webjars, JavaScript and CSS Dependencies, <head> and </body> scripts, LESS, minification, aggregation...

How do I replace these <link rel="stylesheet"> and <script src=> URIs with CDN ones in production? To be fair, this isn't Thymeleaf's fault.

A StackOverflow answer recommends using a Properties file, which is okay I guess. I prefer Wicket Bootstrap's dedicated-class-per-JS-library and typesafe-way of doing this, but not a big deal.

The bigger deal is with dependencies. In Wicket, a Component/Behavior implements renderHead() which declares external JavaScript or CSS or custom inline JavaScript. These in turn may depend on other JavaScript/CSS files that will be merged in the rendered page. No JavaScript or CSS will be included twice. And the page doesn't need to care about them.

In Thymeleaf, these are just tags. Even if I implement Thymeleaf processors, I'm not sure whether it's possible to add JavaScript/CSS files automatically, let alone manage them. wro4spring-thymeleaf-dialect seems promising, but: it's a community project (Wicket's JavaScript/CSS management is built in core), the last commit was April 2013.

Also other things like LESS, minification, aggregation. To be fair, Wicket doesn't have these LESS and minification out of the box as well, but the foundation is there including resources bundles for aggregation and the rest are provided by Wicket Bootstrap which fortunately is presently well maintained.

Conclusion: Thymeleaf, FreeMarker, or...?

As I mentioned, after using Thymeleaf for one day and hitting a roadblock on reusability and extensibility I tried to find alternatives.

Struts 2: It's probably good, but I'm content with Spring MVC and Struts doesn't solve the view layer problem.

JSF / Tapestry / Vaadin: Having used JSF 2.1 in the past, I'm absolutely sure Wicket is superior. I've used Vaadin a bit, and heard good things about Tapestry. But the thing is, if I'm looking for a component-based framework I'd just use Wicket: I've invested there, both code and knowledge.

FreeMarker / Velocity / JSP: I've heard good things about FreeMarker as a template language, and I'd love an opportunity to use it. We at Bippo have been using Mustache for hypertext templating and it's cool too. Now FreeMarker with JSP taglibs would be a step up in the reusability front compared to Thymeleaf, but I doubt it will solve the other peculiarities like resource management I mentioned above. In a way it also feels backwards because we're back to JSP-land, since actually I liked how Thymeleaf does most things (except dialects/processors), although I definitely prefer older-but-working-tech to newer-but-less-functionality any day.

The surprising (for me) conclusion is that I think Wicket is ideal for this "stateless web app" use case:
  1. I'm well-versed in Wicket
  2. Wicket and all its components and custom components are straightforward reusable, no ceremony required
  3. Extensible, again no ceremony just good old "class A extends B" + @Override elementary OO thing
  4. Has excellent out-of-the-box coverage of commonly needed functionality, such as JavaScript and CSS resource management
  5. This guy Martin Grigorov is awesome, I have 27 Wicket JIRA issues and 22 of them are resolved, usually within a few days. :)
  6. Excellent ecosystem for integration with Bootstrap, Bootswatch, Font-Awesome, LESS, and other various JavaScript libraries. And if I need to do it myself, it's easy and reusable.
  7. I'll leverage my existing knowledge and Wicket component libraries, both for stateful and stateless apps, without maintaining two separate projects with similar functionality, and two separate parts of my brain.
  8. In any case I need stateful stuff, it's there.
Note that this post is my findings plus opinions, it's not my intention to attack Thymeleaf in any way, in fact I quite liked it other than the major (and a few minor) weaknesses I outlined above. If anything isn't accurate due to my limited knowledge of Thymeleaf or you have solutions to my problems, please do let me know in the comments and I'll correct/update them. Thanks! :)

To learn more about Spring Boot, I highly recommend Spring Boot in Action: