AEM Sling Models - Why Bother?

July 22, 2020
VP, AEM Engineering

A debate sometimes arises among developers creating components on Adobe Experience Manager (AEM) as to whether or not to create sling models for all components. The discussion often focuses on simple components that merely read authored properties from the JCR and display them on the page since HTL can readily do this without the aid of sling models.

Some common perceptions are that sling models for these components represent unnecessary complexity (arguable in some aspects, but not true in the big picture) and that development is simpler and quicker, especially for front-end developers, by omitting sling models (also not true).

Sling models are recommended for all AEM components, complex or simple, and building them via standard practices saves development time in both initial implementation and ongoing maintenance.

Adobe Best Practices

Coding components with sling models is the recommended AEM best practice from Adobe, as demonstrated by the implementation patterns in WCM Core Components. At face value, this reason can seem arbitrary. However, there are some very good reasons why we should follow AEM best practices in most cases.

For starters, clients specifically request industry best practice development. Naturally, Adobe prefers partners that follow and tout best practices.

A more concrete reason to follow Adobe’s recommended practices is to ensure projects minimize long-term maintainability costs in context of future AEM upgrades. Building components the way Adobe officially supports gives the highest probability of code that “just works” on future versions of AEM, and this consideration is always worth keeping in mind when proposing alternative solutions to “the AEM way.”

Lastly, in partnering with clients there are times we are tasked with enabling internal IT to take on long-term ownership of their AEM platform. As we teach AEM development to our fellow IT brethren, we want to make sure we’re in lockstep with Adobe and their documentation unless we have some very good and objective reasons for diverging.

Content Service (JSON) Support

Sling models coded according to best practices ensure that all content within a website can be accessed as JSON web services (via the .model.json URL extension). Content as a service is a feature that AEM fundamentally supports out of the box, and a feature that Adobe ensures customers are aware of.

Though the usefulness of web pages delivered as JSON can be debated, not building in this support is setting a potential landmine that can blow up in your face if a client asks why this fundamental feature does not work for the platform you built.

This reason alone is a solid argument for building sling model support for all components.

Image/Link/Other Processing

Accessing properties directly in HTL leaving only complex values to sling models may seem convenient in some cases. However, that convenience will almost inevitably lead to shortcuts with hidden deficiencies and maintainability implications.

Take images for example. In HTL you can render an image simply by grabbing the fileReference property from the image node and dropping it into the src attribute of a <img> tag. This will display the image directly from the Digital Asset Manager (DAM), giving an impression that the coding for this image is complete. However, this approach has two very grave deficiencies.

First, the image will be rendered without a cache buster value in the URL. This means that even if the asset is updated in the DAM, any user that has already viewed the image on the website will not see the new version due to browser caching.

Second, the image will be rendered in its original form which could be very large (potentially multiple MB) and without any support for adapting to the device viewport or styling constraints of the website, resulting in poor website performance, search ranking, and user experience. This is a huge blunder, and the problem is exacerbated by the fact that it will be almost imperceptible to testers who see the image render “correctly” in their web browser.

URL links also open a pitfall when rendering properties directly in HTL. Say we‘re coding a Button component with a path property that we expect authors to point to an internal page on the website. In HTL, we can simply render this link into an <a> tag by setting the href value to ${properties.path}.html. However, what happens when an author decides to link the component to another of the client's domains, such as http://blogs.clientsite.com/? The HTL now renders http://blogs.clientsite.com/.html and thus we have a bug.

Now let's say we update the HTL to check if the path starts with / and only conditionally append .html. This will give the appearance of solving the problem, but again we will have applied only a partial solution. Common link processing functionality checks internal paths and renders them as their vanity URL if present. By coding the link directly in HTL via direct properties access, we've unintentionally subverted that behavior, and have created an inconsistency in how links work across components.

Inheritance and Override Support

HTL is a robust syntax, elegantly joining HTML with java code to render dynamic content to a page. However, HTL is still not a full-fledged coding language that easily supports granular overrides via inheritance. Imagine a Quote component that renders a piece of text (the quote) and the name of the person who coined the quote (the author).

Now let's say we want to create a Product Quote component that looks the same but instead renders the latest product review for the product referenced on the current page. In the HTL, all we really want to change is how the quote and author are calculated—95 percent of the HTL we wish to keep the same. Given the limitations of HTL, changing the logic of how we fetch the quote and author forces us to either overwrite the entire file (if we are extending the original Quote component) or create a completely new component (effectively a copy and paste).

Take the same scenario but instead using a sling model. In this case we extend the original QuoteImpl class, changing the implementation of getQuote() and getAuthor() to incorporate the logic required for our new component. Now our Product Quote component that extends Quote doesn't need an HTL file at all—it simply uses the inherited one with no changes, and we can be assured that any changes to the markup, look, and feel of the original Quote component will automatically be applied to the Product Quote component as well, as we've avoided the pitfall of copy and paste.

The Quote example may seem like a simplistic case where it could be argued that creating a separate component without inheritance is low risk, but the value proposition of component inheritance grows proportionally with component complexity. Inheritance is particularly common in page components which can contain a significant number of HTL files and amount of sling model functionality that can become a maintenance nightmare without proper inheritance.

Code Consistency

When considering maintainability of a code base, consistency trumps convention most of the time. Unless we are executing an incremental, intentional effort to change the overall implementation pattern of a code base to a new standard, keeping patterns consistent is one of the ways to ensure that developers both old and new to a project can easily learn and support all parts of the code base.

When developers start making judgment calls of when to use sling models versus when to access properties directly via HTL, teams find themselves having additional conversations/debates on the merits of either approach in a myriad of circumstances, seeing additional items turned back in code reviews (due to pitfalls described above), and ultimately landing on a code base with some components fully supported by sling models, some with no sling model at all, and others implemented partially with both patterns.

For an expert with many years of AEM experience and the foresight to know and navigate all the potential problems, this may seem like not that big a deal. But for anyone new to a project, let alone new to AEM, this can be extremely confusing and error-prone, resulting in significant time lost navigating the code and pitfalls.

Remember the effect of code inconsistency on content service (JSON) support? In a project where some fields are present in sling models and others are not, content APIs will contain some fields and others not. This is arguably worse than not supporting content services at all.

No Extra Effort

A common argument for accessing properties directly in HTL without sling models is speed of development, particularly for front-end developers. In the past, I would be inclined to concede this point, but given the emergence of the AEM Component Generator, this simply isn't true.

Regardless of how efficient a developer is in creating an AEM component, it’s still more efficient to leverage the component generator. The generator not only builds a fully functional sling model requiring zero developer interaction, but also outputs the dialog XML (including support for shared and global properties), base clientlib folders, and an HTL file hooked up to the sling model with sample code accessing every property.

Another argument can arise on projects that require unit testing coverage of a certain percent, where it can be perceived that the requirement of a sling model is adding additional work (in the form of unit tests) for some components. Though this is true in limited context, it's also not accounting for the entire picture. 

Ignoring the merits of whether it's acceptable to use a method of coding to purposely avoid unit testing requirements, let's consider the unit testing effort for these sling models. We’re only incrementally increasing sling model unit testing efforts—any complex fields requiring sling model support will already require unit testing. Unit tests for simple sling models are trivial to write once the testing patterns are established. The "visible" time spent here will be offset by "invisible" time saved mitigating other issues discussed in this article.

Final Thoughts 

Change for the sake of change in development patterns is something we actively steer our developers and clients away from. Sling models are a case where change is non-debatable. Component development with sling models has numerous advantages that strongly advocate for transition away from legacy and other non-standard AEM coding practices.

Saving development time and budget by using best practice component patterns, more resources can be allocated to custom features that truly make a difference for your business.