High-level overview of the Apache Sling stack
Who needs this ?
This website is trying to make a case for developing Apache Sling web-applications. In fact it is one.
This web-content is -in it's essence- text with links and images "only". No matter if you are a little corner-shop or a big organization, you want to tell a story on the internet.Changing a teaser on a web shop or blogging about things, both share the same cross-concern. To deliver content and Sling was made to do that. Fast.
Rule #1: Data First, Structure Later. Maybe.
Sling is a Java framework for building web applications. It can be hard to understand at first because it's very abstract. It does not come with a built-in frontend, so you need to design and code the user interface (using HTML, CSS, and JavaScript) on your own.
You should be familiar with Java application servers to provide users with dynamic content. Plus some DevOps skills to host that safely. This likely makes it more suitable for organizations, as one person rarely has all these skills.
The purpose of this project is to create and collect tutorials for this technology and close knowledge gaps. This page you're reading now is a humble start, created on a DIY Sling server called SliMpoGrine. It's part of a content-package that syncs with GitLab occasionally, to be included in future releases of a self-documenting application-server. That is why you can change the text and image-slop here with a pull-request! This page wants to teach you how to that.
SliMpoGrine isn’t a single software product, but a combination of open-source Sling applications. It runs on a low-cost virtual server using Docker and is fully open source.
Why not using Spring or Jakarta EE like every sane person would do?
Why OSGi ?
When you're having that question in mind you are right here.
On the frontend, all three support the Jakarta Servlet Api and integrate with template engines, e.g JSP, Thymeleaf, FreeMarker ....
In the backend all three frameworks are based on Dependency Injection. Business logic is behind backend modules that expose domain-objects and services to act on. It gets @Autowired in Spring, Jakarta Context Dependency Injection (CDI) does a similar thing:
"obtaining objects in such a way as to maximize reusability, testability and maintainability compared to traditional approaches such as constructors, factories, and service locators (e.g., JNDI)"
In case of Apache Sling, everything is based on OSGi, an open standard for this (+ a bit more). Apache Felix is the used implementation.
OSGi is around since the early 2000s in all sorts of applications, from embedded devices, Smart-Home-Hubs and desktop applications to Jakarta EE servers like GlassFish. (In case you are interested in home automation, have a look into openHAB.)
Is OSGi a bit excessive in the era of microservices?
Hot-swapping services in a running application may not be necessary anymore? We're deploying Docker images after all. Maybe the Java Platform Module System (JPMS) is enough to split large applications into smaller bits?
These are interesting questions. Two counter-points:
- During development a native hot-deployment IS nice, with short time from saving code to executing it on the server
- The Java Enterprise Ecosystem shifted rapidly from avoiding monoliths to dealing with the complexity of too many microservices. "Kubernetes is the Websphere of the millennials" someone wrote. There is a trend trying to find middle-ground here called "Modulith" and Sling was one before it was cool.
Why store web content in a JCR tree rather than a SQL or NoSQL database?
Why not both?
Ultimately, content consists of files stored on a disk (such as a hard drive or solid-state drive).
This view should correspond to the problem being addressed, and in the real world, many things are organized in a tree hierarchy:
This content-path for example resembles an organizational structure:
/corporation/brand/market/country/region/product/image
while this might be a website-structure:
/homepage/category/product/spare-part
and the DOM of a webpage is actually a tree:
/html/body/stage/column/teaser/div/p/introText
So saving web-content in a tree somehow feels natural.
Similar to OSGi (and Jakarta EE), the 'Content Repository API for Java (JCR)' is an open specification with several implementations. Apache Jackrabbit Oak is used here, but developers only interact with the interface package `javax.jcr` when building applications with Sling.
Jackrabbit comes with two node storage flavours:
- The default option, SegmentNodeStore (or TarMK), saves data in continuously growing tar files without needing an external database.
- DocumentNodeStore supports clustered setups and relies on MongoDB or a SQL database for instances that share the same JCR repository.
Traditionally, TarMK is used. Content is edited on a single author instance and then replicated to multiple publisher instances, each with its own JCR repository.
For this server, everything runs on a single instance using TarMK.
What can I do with Apache Sling?
Bringing Back the Fun!
Now we've covered the basics, let's get into creating apps with Sling.
A condensed answer to "What is Sling" from the homepage:
In a nutshell, Sling maps HTTP request URLs to content resources based on the request's path, extension and selectors. Using convention over configuration, requests are processed by scripts and servlets, dynamically selected based on the current resource.
In practice, you store your application's building blocks—like HTML pages, scripts, and text—in a content repository. You then define a "sling:resourceType" for a piece of content. This tells Sling, "When someone requests this content, use these specific scripts or templates to handle it." Sling then automatically makes that content available at a URL and uses the correct scripts to process requests to that address.
From Sling's point of view it's all content, html/js/css as well as templates. Not only the images with text and links.
This might still seem a bit theoretical. To make it more concrete, let's look at how these concepts work in an actual application.
But before we do that, let's think about what we've got here.
Due to its modularity, Sling consists of more than 300 modules. There is one for almost everything an enterprise application server needs. Scheduling, Eventing and Jobs, Cluster Discovery and Distribution, Testing ecosystem, S3/Azure Blobstore, Logging, GraphQL ...
Chances are you don't need any of that. The key takeaway is that you can start simply. You can use Sling to build and serve a basic web prototype using just HTML, JavaScript, and CSS, leveraging its powerful routing and templating from the very beginning. Sling as over-engineered file server if you will.
What is the Apache Sling Starter ?
Assembling a Feature Model
So how do we get from all those bundles to something that actually starts, creates a repository and listens to incoming HTTP requests?
While it is possible to deploy a Sling Web Application Archive (.war) file on any Servlet Container like Tomcat, that's not what we do here.
Instead it's a standalone Java application. Prior to Sling 12 an executable launchpad.jar was created with the 'provisioning model'. Now, the Sling OSGi Feature Model combines bundles, settings, content, and setup scripts into a single unit called a Sling-Feature. The corresponding Maven Plugin checks if all dependencies are fulfilled. It then creates feature files in JSON format or feature-archive files (.far), which are started using the feature-launcher. Although you could package everything into a single executable uber-jar (with the kickstarter), we instead build a Docker image directly..
Initially I went with a fork of the Sling Starter in order to add more features (Peregrine CMS) here in src/main/features/app into a working launcher. I later found that the starter project provides a new type of build output called 'osgifeature'. It's basically json with maven coordinates to bundle-jars and content-zips plus repo-init scripts.
That allows me to include it in my own maven build here inside the slingfeature-maven-plugin config:
<aggregate>
<classifier>slimpogrine_core-aggregate</classifier>
<title>Slimpogrine no Persistence</title>
<filesInclude>*.json</filesInclude> <!-- my app -->
<filesInclude>composum/*.json</filesInclude> <!-- CMS 1 -->
<filesInclude>peregrine/*.json</filesInclude><!-- CMS 2 -->
<includeArtifact>
<!-- Here we include a working starter -->
<groupId>org.apache.sling</groupId>
<artifactId>org.apache.sling.starter</artifactId>
<classifier>nosample_base</classifier>
<version>${sling.starter.version}</version>
<type>slingosgifeature</type> <!-- type is json -->
</includeArtifact>Likewise you could include this slimpogrine_core-aggregate as baseline application-server. Which is silly for this proof-of-concept, but you can see how powerful the feature model is.
There is a different approach:
Use the official Sling docker-image as a base layer in your Dockerfile. Install your application components, such as bundles, configuration files, or packages, using Maven within the image. This approach appears to be valid, although it has not been used here.
Then there is the simple option:
Just run the Apache Sling Starter Docker Image and install your applications with the package-manager application from the Composum suite.
Useful apps (Composum)
Useful apps (Composum)
So far we only have the basic engine. The Default GET Servlets handle reading data in formats like XML or JSON, while the SlingPostServlet handles writing data to the repository.
That's it. Really.
However, not everyone enjoys using command-line tools like curl to add content, especially when you are new to the system. So the Sling Starter includes three useful apps from the Composum Nodes project, that is technically not part of Sling but an open-source friend if you will.
Console Browser (/bin/browser.html)
Lets you browse the JCR tree, create and edit nodes. Similar to AEM's CRXDE Lite, you can upload files e.g a index.html and even edit it in the browser. CMS for power-users! You can break stuff.
You can set permissions (ACL), query the jcr and run Groovy scripts.
Package Manager (/bin/packages.html)
The easiest way to install an app into a running instance. The app can be bundled as an all/complete zip, that contains several OSGi bundles and content-packages. Content-Package based deployment achieves a similar outcome at runtime to what the Feature Model approach accomplishes during the build process. However, unlike the Feature Model, it does not verify that all OSGi dependencies are satisfied before deployment.
Main reason for a package-manager is of course to create, build and install content zip-files. That allows transfer of content from one instance to another. It's compatible with AEM both ways.
User Manager (/bin/users.html)
Well, it manages JCR users. Note that it's also JCR content, so there are more ways to add them. Users are below path /home/users. There are user-groups /home/groups and the system-users under /home/services, used for jobs that need some permissions but without giving them full admin access.
Write your own app
Fun and simple
Let's say you already have a Sling running inside a docker container on the internet. In a corporate environment that is probably more of an organizational issue to be honest.
Now someone has quickly vibe-coded a TypeScript app, reaching the famous 80% completion, and you want to demonstrate it on your mobile device during lunch. The app comes as a zip-file containing an index.html and the rest is compressed JavaScript. You extract the files into a content package project and install it via package-manager. Done.
Well, almost. The URL might be cumbersome, and the app may be designed to run without a context path. For example, a path like /apps/playground/my-awesome-app/index.html might not function correctly..We have not yet discussed reverse proxy and caching solutions like Apache, Nginx, or Traefik.
Alternatively, if you have HTML and CSS and want a templating solution other than PHP, review the reverse CSS Zen Garden example to see how it is implemented.. We need a link to a beginner's-guide here.
Or you have a react/angular/vue app running somewhere else, that needs a content/business API? Sling Models Exporter is your friend. Or any combination of the above, Sling does not really enforce an architecture.
This approach of combining a single-page application with a content API is now known as a "Headless CMS". Sling was one before it was cool. Downside is, the lack of a polished user interface may be why it has not been widely adopted.
Add apps from others
Peregrine CMS
Peregrine CMS was presented at adaptTo 2019. "There was no CMS in Sling, now there are suddenly three that are open source".
The Sling Feature Model 1.0 revealed was also presented that year. Slimpogrine started as an experiment with the goal to integrate Peregrine into its own feature model. It includes a user-friendly WYSIWYG editor. The system also provides its own image and asset management, along with a unique approach to templates and publishing. It has a default theme including way too many standard components.
The architecture notably shifts from traditional HTML templating to a Single Page Application (SPA) built with Vue.js. Not only is the CMS itself an SPA but also the generated website. For single-instance publishing, the system works by writing JSON files to the filesystem, which are then served directly by Apache.
The project did not get the attention it deserved I think. Then came COVID-19 pandemic and the project now shows limited recent activity on GitHub..
Building SliMpoGrine presents a minor challenge for developers because Peregrine is not on maven-central. Therefore, a developer must first build it into the local Maven .m2 repository using Java 11, while the rest of the project uses Java 21. To make it work with sling-starter 13 as feature, it has to be this fork. Alternatively, you can remove the feature by deleting the folder 'launcher/src/main/features/peregrine'.
Composum Pages
Finally let's add the Composum-Pages CMS. The page you're currently reading is made with it.
It might seem insane to run two CMSs in the same Docker image. I mean learning one is already a task. But it makes a point about the architecture of Sling! The fact that this is even possible is both logical and surprising.
Unlike nodes-browser/package-manager/user-manager explained above, it's not part of the official Sling Starter. Instead there is an official Sling CMS, but that is not used here, three would be really silly.
Again it has it's own WYSIWYG editor, too many OOTB components, asset-management and publishing concept. Publishing involves copying content nodes from the '/content' path to the '/public' path within the JCR repository. It would also support publisher-author distribution.
We're also just running the default theme here. The look and feel isn't as nice a Peregrine's default-theme and the mobile-view lacks some responsiveness.
However, the default appearance may not reflect its full capabilities, it's probably made by backend developers. The aim would actually be to explore further and to come up with an own app on top, with custom html and own components in the future.
What's with these AI slop images?
Follow the white Jackrabbit
In a cold, sterile laboratory, where the hum of machines never ceased, a small white jackrabbit sat alone in a wire cage. Its pink eyes darted nervously, ears twitching at every sound. The scientists called him Subject 47, but he knew himself as Snow. The lab was a maze of blinking lights and echoing footsteps, but worst of all were the voices.
- "Deliver the content faster," they whispered, though no one was near.
- "Optimize. Streamline. No delays," another voice hissed, bouncing off the metal walls.
Snow didn't understand what "content" was, but the voices demanded it with urgency. He pressed his paws against the cage door, longing for the open fields he barely remembered. The lab had stolen his freedom, and now it was stealing his mind.
One night, as the fluorescent lights flickered, a new voice spoke—softer, kinder.
Build a machine for me to deliver CONTENT. Then we launch it into the CLOUDS, Then you'll be FREE
Ok then, stop this "I want you to act as a storyteller" AI-generated slop. It is fun though.
Two Headed Sling
When generative AI came along, out of the need for a logo it was given the silly task to create an image of a "Two Headed Sling". That was the result.
AI has evolved, so have the prompts. Here is an inspiration, in case you feel to evolve that style.
A photo-realistic image of a two-headed sling catapult launching a laptop, set in a clean and organized, steampunk-styled laboratory. The catapult is the primary focus. A jackrabbit is present. Three signs are present in the background with the words Sling, Jackrabbit, and Content. The lighting is dominated by a blue hue.
Create a humorous, 16x9 image of a two-headed sling catapult launching a laptop in a laboratory setting. The artistic style is cyberpunk and "the matrix". A cute, white jackrabbit is present. Three signs are in the background with the words "Sling" "Jackrabbit" and "Content". The lighting has a dominant blue hue; the catapult and laptop are the primary focus.
Dark City
