Lessons
  Menu

Improving AEM Sling Model with Service Layer Integration

When developing Adobe Experience Manager (AEM) applications, adhering to best practices and coding standards ensures maintainability, scalability, and efficiency. A common area for optimization involves the separation of concerns—specifically, the distinction between data representation (models) and business logic (services). This article will explore the benefits of moving business logic from a Sling Model’s init method to a dedicated service, demonstrate how to make this change, highlight issues related to coding standards in the provided model, and finally, present a revised version of the code adhering to AEM’s recommended practices.

Moving Business Logic to a Service

The initial code sample reveals a Sling Model performing tasks beyond data representation, including querying the JCR for data. This approach can lead to bloated models, hindered testability, and reduced reusability. Instead, these responsibilities should be offloaded to a service.

Revised Model with Service Integration

// ArticleListModel.java
@Model(adaptables = Resource.class, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class ArticleListModel {

    @ValueMapValue
    private String articleRootPath;

    @Self
    private Resource resource;

    @OSGiService
    private ArticleListService articleListService;

    private List<ArticleListDataBean> articleListDataBeans;

    public String getArticleRootPath() {
        return articleRootPath;
    }

    public List<ArticleListDataBean> getArticleListDataBeans() {
        return articleListDataBeans;
    }

    @PostConstruct
    protected void init() {
        articleListDataBeans = articleListService.getArticleListDataBeans(resource, articleRootPath);
    }
}

The Dedicated Service

// ArticleListService.java
@Service
public class ArticleListServiceImpl implements ArticleListService {

    private static final Logger LOGGER = LoggerFactory.getLogger(ArticleListServiceImpl.class);

    @Reference
    private QueryBuilder builder;

    @Override
    public List<ArticleListDataBean> getArticleListDataBeans(Resource resource, String articleRootPath) {
        List<ArticleListDataBean> articleListDataBeans = new ArrayList<>();
        ResourceResolver resourceResolver = resource.getResourceResolver();
        Session session = resourceResolver.adaptTo(Session.class);

        Map<String, String> predicate = new HashMap<>();
        predicate.put("path", articleRootPath);
        predicate.put("type", "cq:Page");

        try {
            Query query = builder.createQuery(PredicateGroup.create(predicate), session);
            SearchResult searchResult = query.getResult();

            for (Hit hit : searchResult.getHits()) {
                try {
                    String path = hit.getPath();
                    Resource articleResource = resourceResolver.getResource(path);
                    Page articlePage = articleResource.adaptTo(Page.class);

                    if (articlePage != null) {
                        ArticleListDataBean articleListDataBean = new ArticleListDataBean();
                        articleListDataBean.setPath(path);
                        articleListDataBean.setTitle(articlePage.getTitle());
                        articleListDataBean.setDescription(articlePage.getDescription());

                        articleListDataBeans.add(articleListDataBean);
                    }
                } catch (RepositoryException e) {
                    LOGGER.error("Error processing search result hit", e);
                }
            }
        } catch (Exception e) {
            LOGGER.error("Error executing query", e);
        }
        return articleListDataBeans;
    }
}

Issues and Improvements in the Original Model

  1. Separation of Concerns: The original model mixes data fetching and representation, which should be separated for clarity and maintainability.
  2. Resource Handling: The model directly uses Session and ResourceResolver without demonstrating proper resource management practices, such as closing sessions or handling potential leaks.
  3. Testability: Embedding business logic within the model makes unit testing more challenging, as testing the model requires mocking more external dependencies.
  4. Reusability: The logic within the init method is specific to this model and cannot be easily reused by other models or components.

Transformed Code as per AEM Standards

The revised model demonstrates the application of AEM coding standards by delegating business logic to a service, thereby adhering to the separation of concerns principle and enhancing testability and reusability. Additionally, it ensures efficient resource management by handling resource resolution within the service layer, where resources can be managed more systematically.

This approach aligns with AEM’s best practices, facilitating a cleaner, more modular architecture that benefits developers and the overall application lifecycle.