Familiarize yourself with basic AEM Project Archetype use, and the FileVault Content Maven Plug-in as this article builds upon these learnings and concepts.
This article outlines the changes required to Adobe Experience Manager Maven projects to be AEM as a Cloud Service compatible by ensuring that they respect the split of mutable and immutable content. Also, dependencies are established to create non-conflicting, deterministic deployments, and they are packaged in a deployable structure.
AEM application deployments must be composed of a single AEM package. This package should in turn contain subpackages that comprise everything required by the application to function, including code, configuration, and any supporting baseline content.
AEM requires a separation of content and code, which means a single content package cannot deploy to both /apps
and runtime-writable areas (for example, /content
, /conf
, /home
, or anything not /apps
) of the repository. Instead, the application must separate code and content into discrete packages for deployment into AEM.
The package structure outlined in this document is compatible with both local development deployments and AEM Cloud Service deployments.
The configurations outlined in this document are provided by AEM Project Maven Archetype 24 or later.
The /apps
and /libs
areas of AEM are considered immutable because they cannot be changed (create, update, delete) after AEM starts (that is, at runtime). Any attempt to change an immutable area at runtime fails.
Everything else in the repository, /content
, /conf
, /var
, /etc
, /oak:index
, /system
, /tmp
, and so on, are all mutable areas, meaning they can be changed at runtime.
As in previous versions of AEM, /libs
should not be modified. Only AEM product code may deploy to /libs
.
Oak indexes (/oak:index
) are managed by the AEM as a Cloud Service deployment process. The reason is because the Cloud Manager must wait until any new index is deployed and fully reindexed before switching over to the new code image.
For this reason, although Oak indexes are mutable at run time, they must be deployed as code so that they can be installed before any mutable packages are installed. Therefore /oak:index
configurations are part of the Code Package and not part of the Content Package as described below.
For more details about indexing in AEM as a Cloud Service, see Content Search and Indexing.
This diagram provides an overview of the recommended project structure and package deployment artifacts.
The recommended application deployment structure is as follows:
The OSGi bundle Jar file is generated, and directly embedded in the all project.
The ui.apps
package contains all the code to be deployed and only deploys to /apps
. Common elements of the ui.apps
package include, but are not limited to:
/apps/my-app/components
/apps/my-app/clientlibs
/libs
/apps/cq
, /apps/dam/
, and so on./apps/settings
rep:policy
for any path under /apps
The same code must be deployed to all environments. This code ensures a level of confidence that validations on the stage environment are also in production. For more information, see the section on Runmodes.
ui.content
package contains all content and configuration. The Content Package contains all the node definitions not in the ui.apps
or ui.config
packages, or in other words, anything not in /apps
or /oak:index
. Common elements of the ui.content
package include, but are not limited to:
/conf
/content
, /content/dam
, and so on./content/cq:tags
/etc
The all
package is a container package that ONLY includes deployable artifacts, the OSGI bundle Jar file, ui.apps
, ui.config
, and ui.content
packages as embeds. The all
package must not have any content or code of its own, but rather delegate all deployment to the repository to its subpackages or OSGi bundle Jar files.
Packages are now included using the Maven FileVault Package Maven plugin’s embedded configuration, rather than the <subPackages>
configuration.
For complex Experience Manager deployments, it may be desirable to create multiple ui.apps
, ui.config
, and ui.content
projects/packages that represent specific sites or tenants in AEM. If this approach is done, ensure the split between mutable and immutable content is respected, and the required content packages and OSGi bundle Jar files are embedded as subpackages in the all
container content package.
For example, a complex deployment content package structure might look like this:
all
content package embeds the following packages, to create a singular deployment artifact
common.ui.apps
deploys code required by both site A and site Bsite-a.core
OSGi bundle Jar required by site Asite-a.ui.apps
deploys code required by site Asite-a.ui.config
deploys OSGi configurations required by Site Asite-a.ui.content
deploys content and configuration required by site Asite-b.core
OSGi bundle Jar required by site Bsite-b.ui.apps
deploys code required by site Bsite-b.ui.config
deploys OSGi configurations required by site Bsite-b.ui.content
deploys content and configuration required by site BThe ui.config
package contains all OSGi configurations:
/apps/my-app/osgiconfig
/apps/my-app/osgiconfig/config
/apps/my-app/osgiconfig/config.<author|publish>.<dev|stage|prod>
config.<runmode>
folder as outlined above, and be used to define:
If other AEM Projects – which are themselves comprised of their own code and content packages – are used by the AEM deployment, their container packages should be embedded in the project’s all
package.
For example, an AEM project that includes two vendor AEM applications might look like:
all
content package embeds the following packages, to create a singular deployment artifact
core
OSGi bundle Jar required by the AEM applicationui.apps
deploys code required by the AEM applicationui.config
deploys OSGi configurations required by the AEM applicationui.content
deploys content and configuration required by the AEM applicationvendor-x.all
deploys the everything (code and content) required by the vendor X applicationvendor-y.all
deploys the everything (code and content) required by the vendor Y applicationPackages are to be marked with their declared package type. Package types help clarify the purpose and deployment of a package.
packageType
to container
. Container packages must not contain regular nodes. Only OSGi bundles, configurations, and sub packages are allowed. Containers in AEM as a Cloud Service are not allowed to use install hooks.packageType
to application
.packageType
to content
.For more information see Apache Jackrabbit FileVault - Package Maven Plugin documentation, Apache Jackrabbit Package Types, and the FileVault Maven configuration snippet below.
See the POM XML Snippets section below for a complete snippet.
By default, Adobe Cloud Manager harvests all packages produced by the Maven build. However, because the container (all
) package is the singular deployment artifact that contains all code and content packages, you must ensure only the container (all
) package is deployed. To ensure this, other Packages the Maven build generates must be marked with the FileVault Content Package Maven Plug-In configuration of <properties><cloudManagerTarget>none</cloudManageTarget></properties>
.
See the POM XML Snippets section below for a complete snippet.
Repo Init provides instructions, or scripts, that define JCR structures, ranging from common node structures like folder trees, to users, service user, groups, and ACL definition.
The key benefits of Repo Init are they have implicit permissions to perform all actions defined by their scripts. And, such scripts are invoked early in the deployment lifecycle ensuring that all requisite JCR structures exist by the time code is run.
While Repo Init scripts themselves live in the ui.config
project as scripts, they can, and should, be used to define the following mutable structures:
Repo Init scripts are stored as scripts
entries of RepositoryInitializer
OSGi factory configurations. As such, they can be implicitly targeted by run mode, allowing for differences between AEM Author and AEM Publish Services’ Repo Init scripts, or even between environments (Dev, Stage, and Prod).
Repo Init OSGi configs are best written in the .config
OSGi configuration format as they support multi-lines, which is an exception to the best practices of using .cfg.json
to define OSGi configurations.
When defining Users, and Groups, only groups are considered part of the application, and integral to its function. You still define Organization Users and Groups at runtime in AEM. For example, if a custom workflow assigns work to a named Group, define that Group by way of Repo Init in the AEM application. However, if the Grouping is merely organizational, such as “Wendy’s Team” and “Sean’s Team”, these groups are best defined and managed at runtime in AEM.
Repo Init scripts must be defined in the inline scripts
field, or the references
configuration does not work.
The full vocabulary for Repo Init scripts is available on the Apache Sling Repo Init documentation.
See the Repo Init Snippets section below for a complete snippet.
Code Packages require configuring the FileVault Maven plug-in’s configuration to reference a <repositoryStructurePackage>
that enforces correctness of structural dependencies (to ensure that one code package does not install over another). You can create your own repository structure package for your project.
Only required for Code packages, meaning any Package marked with <packageType>application</packageType>
.
To learn how to create a repository structure package for your application, see Develop a Repository Structure Package.
Content packages (<packageType>content</packageType>
) do not require this repository structure Package.
See the POM XML Snippets section below for a complete snippet.
Content or code packages are placed in a special “side-car” folder and can be targeted for installation on either AEM author, AEM publish, or both, using the FileVault Maven plug-in’s <embeddeds>
configuration. Do not use the <subPackages>
configuration.
Common use-cases include:
To target AEM author, AEM publishes, or both, the package is embedded in the all
container package in a special folder-location, in the following format:
/apps/<app-name>-packages/(content|application|container)/install(.author|.publish)?
Breaking down this folder structure:
The 1st-level folder must be /apps
.
The 2nd-level folder represents the application with -packages
post-fixed to the folder name. Often, there is only a single 2nd-level folder that all subpackages are embedded under, however any number of 2nd-level folders can be created to best represent the application’s logical structure:
/apps/my-app-packages
/apps/my-other-app-packages
/apps/vendor-packages
By convention, subpackage embedded folders are named with the suffix of -packages
. This naming ensures that the deployment code and content packages are not deployed the target folders of any subpackage /apps/<app-name>/...
which results in destructive and cyclic installation behavior.
The 3rd-level folder must be either
application
, content
or container
application
folder holds code packagescontent
folder holds content packagescontainer
folder holds any extra application packages that might be included by the AEM application.The 4th-level folder contains the subpackages, and must be one of:
install
so you install on both AEM author and AEM publishinstall.author
so you install only on AEM authorinstall.publish
so you install only on AEM publishinstall.author
and install.publish
are supported targets. Other run modes are not supported.For example, a deployment that contains AEM author and publish specific packages may look like the following:
all
Container package embeds the following packages, to create a singular deployment artifact
ui.apps
embedded in /apps/my-app-packages/application/install
deploys code to both AEM author and AEM publishui.apps.author
embedded in /apps/my-app-packages/application/install.author
deploys code to only AEM authorui.content
embedded in /apps/my-app-packages/content/install
deploys content and configuration to both AEM author and AEM publishui.content.publish
embedded in /apps/my-app-packages/content/install.publish
deploys content and configuration to only AEM publishSee the POM XML Snippets section below for a complete snippet.
Due to the embedding of the code and content subpackages in the container package, the embedded target paths must be added to the container project’s filter.xml
. Doing so ensures that the embedded packages are included in the container package when built.
Simply add the <filter root="/apps/<my-app>-packages"/>
entries for any 2nd-level folders that contain subpackages to deploy.
See the POM XML Snippets section below for a complete snippet.
All packages must be available via the Adobe’s public Maven artifact repository or an accessible public, referenceable third-party Maven artifact repository.
If the third-party packages are in Adobe’s public Maven artifact repository, no further configuration is needed for Adobe Cloud Manager to resolve the artifacts.
If the third-party packages are in a public third-party Maven artifact repository, this repository must be registered in the project’s pom.xml
and embedded following the method outlined above.
Third-party application/connectors should be embedded using its all
package as a container in your project’s container (all
) package.
Adding Maven dependencies follow standard Maven practices, and embedding of third-party artifacts (code and content packages) are outlined above.
See the POM XML Snippets section below for a complete snippet.
ui.apps
from ui.content
PackagesTo ensure proper installation of the packages, it is recommended inter-package dependencies are established.
The general rule is packages containing mutable content (ui.content
) should depend on the immutable code (ui.apps
) that supports the rendering and use of the mutable content.
A notable exception to this general rule is if the immutable code package (ui.apps
or any other), only contains OSGi bundles. If so, no AEM package should declare a dependency on it. The reason is because immutable code packages that only contain OSGi bundles, are not registered with AEM Package Manager. Therefore, any AEM package that depends on it has an unsatisfied dependency and fail to install.
See the POM XML Snippets section below for a complete snippet.
The common patterns for content package dependencies are:
The simple case sets the ui.content
mutable content package to depend on the ui.apps
immutable code package.
all
has no dependencies
ui.apps
has no dependenciesui.content
depends on ui.apps
Complex deployments expand on the simple case, and set dependencies between the corresponding mutable content and immutable code packages. As required, dependencies can be established between immutable code packages as well.
all
has no dependencies
common.ui.apps.common
has no dependenciessite-a.ui.apps
depends on common.ui.apps
site-a.ui.content
depends on site-a.ui.apps
site-b.ui.apps
depends on common.ui.apps
site-b.ui.content
depends on site-b.ui.apps
The project structures and organization outlined in this article is fully compatible local development AEM instances.
The following are Maven pom.xml
configuration snippets that can be added to Maven projects to align to the above recommendations.
Code and content packages, which are deployed as subpackages, must declare a package type of application or content, depending on what they contain.
The container all/pom.xml
project does not declare a <packageType>
.
Code packages must set their packageType
to application
.
In the ui.apps/pom.xml
, the <packageType>application</packageType>
build configuration directives of the filevault-package-maven-plugin
plugin declaration declares its package type.
...
<build>
<plugins>
<plugin>
<groupId>org.apache.jackrabbit</groupId>
<artifactId>filevault-package-maven-plugin</artifactId>
<extensions>true</extensions>
<configuration>
<group>${project.groupId}</group>
<name>my-app.ui.apps</name>
<packageType>application</packageType>
<accessControlHandling>merge</accessControlHandling>
<properties>
<cloudManagerTarget>none</cloudManagerTarget>
</properties>
</configuration>
</plugin>
...
Content packages must set their packageType
to content
.
In the ui.content/pom.xml
, the <packageType>content</packageType>
build configuration directive of the filevault-package-maven-plugin
plugin declaration declares its package type.
...
<build>
<plugins>
<plugin>
<groupId>org.apache.jackrabbit</groupId>
<artifactId>filevault-package-maven-plugin</artifactId>
<extensions>true</extensions>
<configuration>
<group>${project.groupId}</group>
<name>my-app.ui.content</name>
<packageType>content</packageType>
<accessControlHandling>merge</accessControlHandling>
<properties>
<cloudManagerTarget>none</cloudManagerTarget>
</properties>
</configuration>
</plugin>
...
In every project generating a Package, except for the container (all
) project, add <cloudManagerTarget>none</cloudManagerTarget>
to the <properties>
configuration of the filevault-package-maven-plugin
plug-in declaration to ensure they are not deployed by Adobe Cloud Manager. The container (all
) package should be the singular package deployed via Cloud Manager, which in turn embeds all required code and content packages.
...
<build>
<plugins>
<plugin>
<groupId>org.apache.jackrabbit</groupId>
<artifactId>filevault-package-maven-plugin</artifactId>
<extensions>true</extensions>
<configuration>
...
<properties>
<cloudManagerTarget>none</cloudManagerTarget>
</properties>
</configuration>
</plugin>
...
Repo Init scripts which contain the Repo Init scripts are defined in the RepositoryInitializer
OSGi factory configuration via the scripts
property. Because these scripts defined within OSGi configurations, they can be easily scoped by run mode using the usual ../config.<runmode>
folder semantics.
Because scripts are typically multi-line declaration, it is easier to define them in the .config
file, than the JSON-based .cfg.json
format.
/apps/my-app/config.author/org.apache.sling.jcr.repoinit.RepositoryInitializer-author.config
scripts=["
create service user my-data-reader-service
set ACL on /var/my-data
allow jcr:read for my-data-reader-service
end
create path (sling:Folder) /conf/my-app/settings
"]
The scripts
OSGi property contains directives as defined by the Apache Sling’s Repo Init language.
In the ui.apps/pom.xml
and any other pom.xml
that declares a code package (<packageType>application</packageType>
), add the following repository structure package configuration to the FileVault Maven plug-in. You can create your own repository structure package for your project.
...
<build>
<plugins>
<plugin>
<groupId>org.apache.jackrabbit</groupId>
<artifactId>filevault-package-maven-plugin</artifactId>
<extensions>true</extensions>
<configuration>
...
<repositoryStructurePackages>
<repositoryStructurePackage>
<groupId>${project.groupId}</groupId>
<artifactId>ui.apps.structure</artifactId>
<version>${project.version}</version>
</repositoryStructurePackage>
</repositoryStructurePackages>
</configuration>
</plugin>
...
In the all/pom.xml
, add the following <embeddeds>
directives to the filevault-package-maven-plugin
plugin declaration. Remember, do not use the <subPackages>
configuration. The reason is because it includes the subpackages in /etc/packages
rather than /apps/my-app-packages/<application|content|container>/install(.author|.publish)?
.
...
<plugin>
<groupId>org.apache.jackrabbit</groupId>
<artifactId>filevault-package-maven-plugin</artifactId>
<extensions>true</extensions>
<configuration>
...
<embeddeds>
<!-- Include the application's ui.apps and ui.content packages -->
<!-- Ensure the artifactIds are correct -->
<!-- OSGi Bundle Jar file that deploys to BOTH AEM Author and AEM Publish -->
<embedded>
<groupId>${project.groupId}</groupId>
<artifactId>my-app.core</artifactId>
<type>jar</type>
<target>/apps/my-app-packages/application/install</target>
</embedded>
<!-- Code package that deploys to BOTH AEM Author and AEM Publish -->
<embedded>
<groupId>${project.groupId}</groupId>
<artifactId>my-app.ui.apps</artifactId>
<type>zip</type>
<target>/apps/my-app-packages/application/install</target>
</embedded>
<!-- OSGi configuration code package that deploys to BOTH AEM Author and AEM Publish -->
<embedded>
<groupId>${project.groupId}</groupId>
<artifactId>my-app.ui.config</artifactId>
<type>zip</type>
<target>/apps/my-app-packages/application/install</target>
</embedded>
<!-- Code package that deploys ONLY to AEM Author -->
<embedded>
<groupId>${project.groupId}</groupId>
<artifactId>my-app.ui.apps.author</artifactId>
<type>zip</type>
<target>/apps/my-app-packages/application/install.author</target>
</embedded>
<!-- Content package that deploys to BOTH AEM Author and AEM Publish -->
<embedded>
<groupId>${project.groupId}</groupId>
<artifactId>my-app.ui.content</artifactId>
<type>zip</type>
<target>/apps/my-app-packages/content/install</target>
</embedded>
<!-- Content package that deploys ONLY to AEM Publish -->
<embedded>
<groupId>${project.groupId}</groupId>
<artifactId>my-app.ui.content.publish-only</artifactId>
<type>zip</type>
<target>/apps/my-app-packages/content/install.publish</target>
</embedded>
<!-- Include any other extra packages -->
<embedded>
<groupId>com.vendor.x</groupId>
<artifactId>vendor.plug-in.all</artifactId>
<type>zip</type>
<target>/apps/vendor-packages/container/install</target>
</embedded>
<embeddeds>
</configuration>
</plugin>
...
In the all
project’s filter.xml
(all/src/main/content/jcr_root/META-INF/vault/definition/filter.xml
), include any -packages
folders that contain subpackages to deploy:
<filter root="/apps/my-app-packages"/>
If multiple /apps/*-packages
are used in the embedded targets, then they all must be enumerated here.
Adding more Maven repositories may extend maven build times as additional Maven repositories are checked for dependencies.
In the reactor project’s pom.xml
, add any necessary third-party public Maven repository directives. The full <repository>
configuration should be available from the third-party Repository Provider.
<repositories>
...
<repository>
<id>3rd-party-repository</id>
<name>Public Third-Party Repository</name>
<url>https://repo.3rdparty.example.com/...</url>
<releases>
<enabled>true</enabled>
<updatePolicy>never</updatePolicy>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
...
</repositories>
ui.apps
from ui.content
PackagesIn the ui.content/pom.xml
, add the following <dependencies>
directives to the filevault-package-maven-plugin
plugin declaration.
...
<plugin>
<groupId>org.apache.jackrabbit</groupId>
<artifactId>filevault-package-maven-plugin</artifactId>
<extensions>true</extensions>
<configuration>
...
<dependencies>
<!-- Declare the content package dependency in the ui.content/pom.xml on the ui.apps project -->
<dependency>
<groupId${project.groupId}</groupId>
<artifactId>my-app.ui.apps</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
...
</configuration>
</plugin>
...
In the all/pom.xml
, add the maven-clean-plugin
plug-in which cleans the target directory before a Maven build.
<plugins>
...
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<executions>
<execution>
<id>auto-clean</id>
<!-- Run at the beginning of the build rather than the default, which is after the build is done -->
<phase>initialize</phase>
<goals>
<goal>clean</goal>
</goals>
</execution>
</executions>
</plugin>
...
</plugins>