Deep diving into Sling models PT1

9 juni 2020by Dylan Reniers

In this blog series on Sling models I’ll cover some of the best practices and conveniences I’ve gathered and discovered over the years, for you to apply when developing AEM components.
I’ve had the luck of working on a variety of projects: projects that have been developed for numerous years, completely new projects, projects that were made by a single vendor, by different agencies, etc. These experiences have unveiled all kinds of approaches when it comes to developing Sling models. I will be sharing my opinion on how I believe a Sling model should be developed. These guidelines are by no means official standards or recommendations. As I encounter more insights on my journey, I will be updating and adding to these series.

Part 1: Creating an interface for a model

Developing a component for AEM these days means that you will write some HTL (https://docs.adobe.com/content/help/en/experience-manager-htl/using/getting-started/getting-started.html) for the presentation layer of your component. If we want to inject values from a backend into this layer, we have to make use of a Sling model. Sling models can be written in JavaScript, but the preferred and by far most use approach is to use Java for this.
When we look at general best practices for Java development, especially with a platform that allows dependency-injection and loose coupling, we often find ourselves creating interfaces and an implementation class for that interface that we annotate and use to code our business logic. This is done because an interface is generic and can be reused in multiple scenarios. We can use the same strategy for our Sling models and in this article, you will find out why this is helpful when it comes to re-usability.

The magic of the data-sly-use attribute

Sling (https://sling.apache.org/) has done a great job in separating the presentation layer (HTL) from the data layer (the Sling model) by making use of the ‘data-sly-use’ attribute. Just as a reminder or for developers that have just started developing on AEM, let me explain to you how that is done:

  1. The user is trying to load the page /content/en_us/page.html and a corresponding resource for that request is located at /content/en_us/page
  2. The resource has a sling:resourceType property that has a value that points to an HTL script somewhere in your /apps or /libs folder. The script is being used to render this specific page.
  3. The Sling model is loaded in the HTL
  4. Data from the model is required by the HTL
  5. The data is loaded in the HTL
  6. HTML is rendered based on the HTL and the data from the Sling model
  7. The resulting HTML is set on the response, which is returned to the browser

This way we have a loose coupling between the presentation layer (HTL) and the business layer (Sling model with all the data). The Java class that is used within our HTL can be an interface, so we can easily switch between implementations and don’t have to make any changes in the HTL. This means we add an extra layer of loose coupling between our HTL and the Sling model through that interface. Now, before we get into the specifics, let’s think about why we would want to do this?

The challenge

Imagine the following scenario:
You are working for a company that has decided to go with AEM as their CMS (obviously, since else you wouldn’t be here, right?) and your company has multiple business lines. You’ve developed a component for business line A and suddenly business line B says: “Hey, we saw what you made for A and that stuff is awesome, we’d like it as well!”. Great, right? Kudos to the team! But here it comes… “Oh yeah, but the data we want to show is not coming from system X, but from system Y”.

Darn it!

So, you create a new component, set its resourceSuperType to the other component, develop another model and create a common template for the two components, or overlay the entire HTML. And you would have to do all that because of that ‘data-sly-use’ attribute in your first component that is still pointing to the model you created for business line A.
This means you now have 2 or 3 HTML files you need to maintain, while the two components are identical from an HTL perspective.

The solution

So, what if we were just able to say “Fine, all I have to do is create another model for it”? That would make this a lot easier from a maintenance perspective. Then let’s do just that!

Let’s take our previous scenario but make use of an actual real-life example: you are given the task to build a component that shows you the rating of a product. The rating consists out of 0 to 5 stars and is provided to you by an external vendor (let’s call it AwesomeReviews), that keeps track of your product reviews. The component will reside in /apps/mycompony/components/product/rating.

Your interface could be something like this:

package com.mycompany.products.rating;

public interface ProductRating {

    double getNumberOfStars();
}

And your model:

package com.mycompany.products.rating.awesomereviews;

import com.mycompany.products.rating.ProductRating;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.models.annotations.Model;

import javax.annotation.PostConstruct;

@Model(adaptables = {Resource.class}, adapters = {ProductRating.class}, resourceType = "mycompony/components/product/rating")
public class AwesomeReviewProductRating implements ProductRating {

    private double numberOfStars;

    @PostConstruct
    public void init() {
        //code here that fetches the number of stars from the Awesome Reviews system
    }

    @Override
    public double getNumberOfStars() {
        return numberOfStars;
    }
}

 

And of course, the html (rating.html):

<div data-sly-use.ratingModel="com.mycompany.products.rating.ProductRating" data-sly-list="${ [1,2,3,4,5] }">
    <span class="fa fa-star ${item <= ratingModel.numberOfStars ? 'checked' : ''}"></span>
</div>

So, what exactly did we do here?

First, we created our Sling model using an interface that we implemented. Our @Model annotation says it can adapt from a Resource and the interface that can adapt to AwesomeReviewProductRating is ProductRating. By adding these properties to the annotation, Sling will know that if it finds the ProductRating interface being referenced in HTL it has to take the AwesomeReviewsProductRating model.

Now let’s say for business line B you have to get these reviews from another data source (let’s call that one SuperReviews). We can simply create another component (which can be found under /apps/businessB/components/product/rating), set the sling:resourceSuperType=”mycompany/components/product/rating” and create another model, like so:

package com.mycompany.products.rating.superreviews;

import com.mycompany.products.rating.ProductRating;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.models.annotations.Model;

import javax.annotation.PostConstruct;

@Model(adaptables = {Resource.class}, adapters = {ProductRating.class}, resourceType = "businessB/components/rating")
public class SuperReviewProductRating implements ProductRating {

    private double numberOfStars;

    @PostConstruct
    public void init() {
        //code here that fetches the number of stars from the Super Reviews system
    }

    @Override
    public double getNumberOfStars() {
        return numberOfStars;
    }
}

Ok, so now we have 2 candidates that the ProductRating interface can adapt to, depicted by the following class diagram:

But how does Sling know which one to pick?
The answer: the resourceType property of your @Model annotation. If you are trying to render a component using the businessB/components/product/rating script, it will know that it has to take the SuperProductReviewRating model over the AwesomeProductReviewRating, because that one’s resourceType property is also set to the businessB/components/product/rating.

It is therefore important that if your model is used to gather all data required to render a component, that you set the correct resourceType value to the @Model annotation that points to that component. With this you basically build a 1-to-1 relationship between a model and a component.

I hope that this has been informative to you and if you still have some questions, feel free to reach out and we will be glad to help you.

Until next time!

CONTACTHoofdkantoor
Klaverbladstraat 7a b5,
3560 Lummen - BE
JONG EN HIP?Gebruik onze social links
U kan ons ook contacteren via
onze verschillende social media kanalen.
JOBSOok Joker worden?
Bezoek onze carrière pagina voor
openstaande opportuniteiten.
ContactHoofdkantoor
Klaverbladstraat 7a b5, 3560 Lummen - BE
JONG & HIP?Gebruik onze Social links
U kan ons ook contacteren via onze verschillende social media kanalen.