Headless AEM Approaches

Antonio Estrada
Globant
Published in
7 min readJul 6, 2022

When they started surfacing, Content Management Systems followed a monolithic architecture and were responsible for the content curation, delivery, page rendering and caching, which meant developers typically needed to learn how each particular CMS worked in order to be able to customize it to the project’s needs.

Many of the modern CMSs still provide this headful or traditional approach, but most have started also supporting headless delivery, including Adobe Experience Manager (AEM). In this article, we’ll take a quick look at five different headless delivery approaches that can be achieved with AEM.

What is headless delivery and why would I need it?

In a nutshell, headless delivery takes away the page rendering responsibility from the CMS. The CMS exposes the data as an API, and other applications can consume it in order to process and render it. This is useful for omnichannel delivery, decoupled architectures, scalability, future proofing among others.

Not all projects need it, an analysis is required on a case by case basis, however if any of the above are a concern, it’d be a good idea to consider going headless.

JSON Approach

AEM has been adding support for headless delivery for a while, starting with simply swapping the .html extension for .infinity.json to a published resource. This comes out of the box as part of AEM’s Sling Model implementation.

Doing this will retrieve the raw JCR (Java Content Repository) properties stored in that particular resource and show it as JSON tree.

JSON Approach (about-us.infinity.json)

Infinity in this case means the number of child nodes that the API will show, this can be changed to 0 to only show the top level properties, 1 to show the top level plus one, etc.

JSON Approach (about-us.0.json)
JSON Approach (about-us.1.json)

This approach works wonders in most cases, though there are a couple of downsides to it:

  • It still relies on AEM’s dispatcher and publisher instances to be accessed, which means that although the rendering is no longer the CMS responsibility, it still will be tasked with caching and delivering the data directly to the third-party applications, this can present scalability issues down the road.
  • It’s immutable, as it’s just a visual representation of the stored data in AEM, so it includes a lot of unnecessary information that can be hard to parse.

Content Fragments

Content fragments are instances of models that don’t have any presentation layer attached to them, these are essentially a block of data that abides to a defined schema. Fragments can be used in multiple ways, including linking them to a component to retrieve the information stored in them, or just as data first resources.

These entities are stored as assets within AEM, and also come with an OOTB JSON API that can be used to retrieve the data directly.

JSON Representation of a Content Fragment

For a third party application, this has the same downsides as the previous approach, namely that the dispatcher still needs to handle these requests, and the immutability, although the data is stored under jcr:content/data so it’s a little tidier out of the box.

Sling Model Exporters

While the above approaches work, something more robust can be achieved through the use of Sling Model Exporters. These basically allow to expose the properties of a Sling Model but can be modified to accommodate specific naming conventions, can insert code to transform existing properties or inject additional ones. Since it’s a fully fledged class, it allows for any operations that might be required (e.g. querying nodes based on a property of the model, doing calculations, etc.) and then displaying that data as part of the payload. It provides a lot of flexibility to show or hide specific properties as well via annotations like JsonIgnore.

An example of a Sling Model Exporter payload (resource.model.json)

This example can be achieved using a class like the one below. This class is letting AEM know that for the resource type test/sampleModel it will use this model exporter by visiting the resource address and adding model.json, like we would do with the infinity modifier.

We can inject properties using the @JsonProperty annotation and giving it a value, and we can perform operations within the getmethods of the properties. Using the PostConstructannotation we can access other JCR resources and perform more expensive computations, look at the references for a more detailed description on how to use them.

Sample Sling Model Exporter

GraphQL API

There’s also the GraphQL API that AEM 6.5 comes bundled with, which is targeted at working with content fragments exclusively. Taking advantage of the data first approach of content fragments, AEM has incorporated a GraphQL instance (GraphiQL).

Typically, schemas in GraphQL are strongly typed, so they must be defined and structured prior to being used. However, in AEM the schema is generated based on the content fragment model, which can be modified according to the business’ needs and will automatically update said model from these changes.

The implementation goes beyond the scope of this document, however the GraphQL interface is still served through the dispatcher, as all previous methods have also done it. What happens when we need something fully decoupled?

Custom Replication / Transport

There are times when a project needs a fully decoupled architecture for a number of reasons, performance, platform availability, limited networking, etc.

Custom replication provides full control over the data flow and where it will be stored, which could be an S3 Bucket in AWS, ElasticSearch, Solr, Kafka, SQS, among others.

Diagram of a decoupled application using Custom Transport

This one is the most taxing and needs the heaviest lift from a development standpoint, however, it allows for a truly decoupled environment that doesn’t rely on AEM’s dispatcher or publisher instances directly.

To start, you need to create a TransportHandler, a Transport Handler allows us to hook into the replication mechanism in order to build our own solution. AEM already comes bundled with this implementation and is used for the out of the box replication agents on author and publisher.

An implementation of a Transport Handler basically needs two main things, a canHandle method to determine if the custom agent will be used or not, and a deliver method, which will be responsible for the bulk of the process. This is where you can add a custom ContentBuilder to manage the content, otherwise it will attempt to send the entire page as it would do between author and publish. A barebones approach can be seen below:

Sample Custom Transport

This needs to go hand in hand with an instance of this replication agent, it can be created on the miscadmin panel. In order to ensure the custom transport is used, we need to specify the URI to use the one on the handler. Typically here you would add a user and password, along with other configuration options, like the name of an S3 bucket, perhaps AWS credentials, etc.

Custom Agent Settings

Finally, when a resource is published, you can look at the logs and see if it came through. In this case, we see the custom message we added to the log.

Custom Agent Log

In the end, this allows for a fully decoupled architecture, not relying on the dispatcher to serve content, as stated before this one is the most development taxing, but provides a lot of flexibility. We’ll go into more detail in a future article.

Note, in AEM Cloud, content is published using Sling Content Distribution. Custom replication agents or transport are no longer supported and would need to be handled via an EventHandler.

Conclusion

There are a lot of different approaches to achieve headless delivery with Adobe Experience Manager, many that we didn’t even include here like Custom Servlets, the QueryBuilder API, the Assets CRUD operations, etc. In this article we just scratched the surface of this topic, gave a few pointers at the most common ways of achieving such a response, hopefully this is a good starting point for you. Happy coding!

References

--

--