Sling Models are simple enough to misuse without noticing for a long time. These are the practices that make the difference between a codebase that stays maintainable and one that becomes fragile as it grows.
Be explicit about injection strategy
Default injection ordering (resource, then request, then OSGi service, and so on) is convenient but can silently pick up the wrong value if two sources expose a property with the same name. Set @Inject defaults or specify the injector explicitly for anything business-critical rather than relying on the default resolution order.
Keep adaptables narrow
A model adaptable from both Resource and SlingHttpServletRequest is convenient, but it invites logic that behaves differently depending on adaptable type in ways that are easy to miss during review. Prefer one adaptable per model unless there is a genuine reuse need, and document the reason when you do combine them.
Push logic out of exporters
When exposing models via the Sling Model Exporter for JSON/GraphQL consumption, keep exporter-facing getters thin. Business logic belongs in dedicated methods that are unit-testable outside of a running Sling context — this alone makes the biggest difference to how testable a codebase actually is.
Cache expensive computations at the model level, not per-getter
If a getter does non-trivial work (a query, a service call), compute it once in @PostConstruct and store the result, rather than recomputing it every time the getter is called from a template. Templates often call getters more than once per render.
Testing
- Use Sling Mocks / AEM Mocks (io.wcm or the Adobe testing framework) to unit test models without a full AEM instance
- Test both the happy path and missing/malformed resource data — content authors will eventually produce content your model does not expect
- Avoid asserting against exact JSON string output for exporters; assert against the parsed structure so formatting changes do not break unrelated tests
None of this is exotic — the value comes from applying it consistently across a large team, since Sling Models are usually the single most numerous class type in an AEM codebase.