Best practices for developing a component library for AEM
Code is most valuable to us when it is efficient, lasting, and can be easily applied to other use cases. Well-written code can save huge amounts of development time, and if the library does its job efficiently, we can extend the code to other use cases, getting even more utility out of it. We’ve honed some best practices developing component libraries for Adobe Experience Manager (AEM), paying close attention to flexibility and versioning to build a solution that works for the organization. In this article, we’ll address the following:
- How to increase flexibility in your library
- How to achieve a proper versioning system
- How to organize the development teams that use the library
Optimizing for flexibility
Sling is an open-source RESTful web framework for AEM. One of its primary features is being designed for extensibility from the get-go, since it supports inheritance in many ways.
- HTL inheritance with the ‘include’ command
There are several ways to use HTL code so it leverages inheritance. For example, you can design your components so they can be easily extended, or segment components in functional blocks of HTML so titles or lists can easily swap templates.
The teaser component uses a call-to-action element, which uses the ‘include’ command – this means consumer projects in the library can be replaced if needed.
- Use Sling models that are interfaces
Relying on interfaces for Sling models is a good practice (for those who are familiar with AEM Core Components, this is a similar approach). This way, you can include the implementation class as part of the osgi bundle. To do this, avoid using the “impl” directory to include the java class. This way, consumer projects can extend by composition or even directly if needed.
- Commit to code visibility
Code visibility can make maintaining and making changes to your component library a lot easier down the line. Here are a few example guidelines to improve the visibility of your code:
- Review your protected and private methods, and make sure they’re really needed
- Add public methods so they can easily be rewritten (e.g. getting a comparator)
- Rely on the decorator pattern to implement your models
- Limit your use of enumerator classes
Using versioning to your advantage
Usually, a library is used by several projects (consumers) in different AEM instances. In a typical ecosystem, an AEM instance can have multiple tenants (projects) which use different versions of the library. Each project can have a different lifecycle, deployment needs, or regression test requirements. Therefore, we follow 2 main principles for our versioning strategy:
- No breaking changes are allowed.
- Changes must be backward compatible.
How can we achieve these guidelines?
- Domains and Modularization
We make an effort to group code together that has the same functionality. Grouping code into solid OSGI bundles allows for smoother deployments – for example, you can simply deploy the version of your module without affecting other packages in the source code. Good modularization also helps with regression testing; we only test the modules that have been changed.
- Package Structure
When naming the package, it’s best to have the version as close as possible to the model.
package com.project.components.accordion.v1; This is a good example of a package structure. package com.project.components.v1.models.accordion; This is a bad example of a package structure.
It’s also best to avoid having Context Aware Configuration (CAC) grouped in a parent package. Although it may seem like a good place to house the configuration, the configuration is one of the pieces of code that changes most frequently, which can lead to a bottleneck. So, it’s best to group the CAC inside the package of the model or service using it.
Setting your team up for success
You can have the most beautifully optimized library – but if nobody uses it, or you become inundated with requests and questions on how to use it properly, the cost of building and maintaining the library will outweigh the benefits you reap. Here are some of the best practices we follow in setting teams up for success:
Be careful with breaking changes
We avoid breaking changes at all costs. We don’t delete a method, or change dialog properties that will require consumer projects to refactor their existing content. Instead, we make sure the implications of any changes we want to implement are planned out and followed through to ensure a smooth transition that doesn’t disrupt business operations.
Empower the Committers
As the library gets used by more and more people and projects, there’s a risk of the owner becoming a bottleneck, with requests for features and bug reports adding up. The best practice here is to empower the developers with those requests to deliver the solution themselves by making clear the purpose of the library, keeping documentation up to date, and having a clear contribution guide on how to report bugs and make pull requests.
Considerations for Delivery
Finally, it will be valuable to keep a general set of best practices around delivery.
- Release often so your library can benefit from testing and consumer validation
- Establish a solid channel of communication so all parties involved are informed about changes and future releases
- Increase test automation, so you can increase your unit test coverage and have a good set of smoke tests
Investing in structure and maintenance for your library is key to making the solution work for your organization for as long as possible. Get in touch with our experts today to learn how we can work with you to optimize your library for flexibility, efficiency, and collaboration.