Skip to main content

Command Palette

Search for a command to run...

Qué es realmente AEM y qué problemas resuelve: Explicación técnica y casos prácticos para proyectos reales

Published
6 min read

Introducción

Adobe Experience Manager (AEM) no es solo un gestor de contenidos tradicional: es una plataforma altamente extensible que integra gestión, entrega, personalización y optimización de contenido en proyectos empresariales complejos. En escenarios de equipos multidisciplinarios, suele surgir confusión sobre el alcance real de AEM, el papel de Sling/JCR/OSGi, y hasta dónde llegar con código custom VS lo estándar. Malinterpretar la arquitectura puede generar problemas críticos: desde cuellos de botella en el repositorio JCR y problemas de cache en Dispatcher, hasta decisiones erróneas sobre la separación lógica/presentación o el manejo de configuraciones OSGi. Este artículo guía, de forma práctica, cómo abordar AEM como plataforma: desde la base técnica hasta casos reales.

Valor práctico: Entender qué es realmente AEM ayuda a diseñar soluciones mantenibles, orientadas a producción y escalables, reduciendo riesgos y mejorando la colaboración entre backend/frontend/ops.

Conceptos clave

AEM: Plataforma, no solo CMS

AEM integra tres tecnologías esenciales:

  • Apache Sling: Framework de solicitudes RESTful, mapea urls a recursos dentro del JCR y resuelve scripts/generadores según la jerarquía de contenido.
  • JCR (Java Content Repository): Implementación (Apache Oak) utilizada por AEM para almacenar contenido y datos de la aplicación de forma jerárquica y estructurada.
  • OSGi: Framework modular para gestionar servicios/librerías/componentes (bundles), permitiendo configuración dinámica en producción y separación clara de responsabilidades.

¿Cómo encajan? Cuando una request HTTP llega a AEM:

  • Dispatcher (en el edge) determina si sirve HTML, data o delega a AEM.
  • Sling resuelve la URL dentro del JCR y determina el modelo/presentación HTL asociada.
  • OSGi administra todos los servicios reutilizables (persistencia, workflows, autenticación, integración headless, etc.)

Riesgo de no entender esto: Mezclar lógica de negocio en HTL, usar JCR como base de datos transaccional, o romper la modularidad de OSGi pueden degradar rápidamente el stack, generando deuda técnica y problemas de rendimiento.

Ejemplos prácticos

Caso: Mostrar información dinámica de producto en una página

Problema que resuelve
Traducir datos almacenados en el JCR a vistas HTML personalizadas, desacoplando la lógica (modelo) de la presentación (HTL) y optimizando la entrega para Dispatcher y cachés.

Implementación

Sling Model

// /core/src/main/java/com/example/core/models/ProductModel.java
package com.example.core.models;

import javax.inject.Inject;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.models.annotations.DefaultInjectionStrategy;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.injectorspecific.ValueMapValue;

@Model(adaptables = Resource.class, 
    defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class ProductModel {
    @ValueMapValue
    private String title;

    @ValueMapValue
    private String description;

    @ValueMapValue
    private Double price;

    public String getTitle() { return title; }
    public String getDescription() { return description; }
    public Double getPrice() { return price; }
}

HTL (Sightly)

<!-- /apps/example/components/product/product.html -->
<div class="product">
  <h2>${model.title}</h2>
  <p>${model.description}</p>
  <span>Precio: $${model.price}</span>
</div>

Consumo HTTP (API Model Exporter)

GET /content/example/products/product123.model.json

Salida esperada

{
  "title": "Camiseta técnica",
  "description": "Tejido transpirable y costuras planas.",
  "price": 34.95
}

Por qué este enfoque es correcto en AEM

  • Separa la lógica (modelo Java) de la presentación (HTL), mejorando mantenibilidad.
  • Permite reutilizar el modelo para exportación JSON (headless, SPA) o vistas HTML usando el mismo contrato.

Caso: Consultar nodos del JCR de forma eficiente en producción

Problema que resuelve
Acceso robusto y performante al almacenamiento de contenido en el repositorio JCR para servicios OSGi.

Implementación

// /core/src/main/java/com/example/core/services/ProductSearchService.java
package com.example.core.services;

import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.query.Query;
import javax.jcr.query.QueryManager;
import javax.jcr.query.QueryResult;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceResolverFactory;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;

import java.util.ArrayList;
import java.util.List;

@Component(service = ProductSearchService.class)
public class ProductSearchService {
    @Reference
    private ResourceResolverFactory resolverFactory;

    public List<String> findProductTitles() throws RepositoryException {
        List<String> titles = new ArrayList<>();
        try (ResourceResolver resolver = resolverFactory.getServiceResourceResolver(null)) {
            Session session = resolver.adaptTo(Session.class);
            QueryManager qm = session.getWorkspace().getQueryManager();
            Query query = qm.createQuery(
                "SELECT * FROM [cq:PageContent] AS node WHERE ISDESCENDANTNODE(node, '/content/example/products')",
                Query.JCR_SQL2
            );
            QueryResult result = query.execute();
            result.getNodes().forEachRemaining(node -> {
                try {
                    if (node.hasProperty("title")) {
                        titles.add(node.getProperty("title").getString());
                    }
                } catch (RepositoryException e) { /* log error */ }
            });
        }
        return titles;
    }
}

Por qué este enfoque es correcto en AEM

  • Usa consultas JCR_SQL2, que permiten ser indexadas (al contrario de XPath o comodines profundos).
  • Usa ResourceResolverFactory y try-with-resources: no fuga sesiones, cumple con seguridad.

Caso: Configuración dinámica mediante OSGi para endpoint de integración

Problema que resuelve
Permitir a DevOps/power users configurar variables sensibles (API URL, keys) sin redeploy, maximizando flexibilidad y seguridad.

Implementación

// /core/src/main/java/com/example/core/config/ProductApiConfig.java
package com.example.core.config;

import org.osgi.service.metatype.annotations.AttributeDefinition;
import org.osgi.service.metatype.annotations.Designate;
import org.osgi.service.metatype.annotations.ObjectClassDefinition;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Modified;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Component(service = ProductApiConfigService.class)
@Designate(ocd = ProductApiConfig.OsgiConfig.class)
public class ProductApiConfigService {
    private static final Logger log = LoggerFactory.getLogger(ProductApiConfigService.class);
    private String endpointUrl;

    @ObjectClassDefinition(name = "Product API Configuration")
    public @interface OsgiConfig {
        @AttributeDefinition(name = "Endpoint URL")
        String endpoint_url() default "https://api.example.com/v1/products";
    }

    @Activate
    @Modified
    public void activate(ProductApiConfig.OsgiConfig config) {
        this.endpointUrl = config.endpoint_url();
        log.info("Product API endpoint configurado: {}", endpointUrl);
    }
    public String getEndpointUrl() { return endpointUrl; }
}

Por qué este enfoque es correcto en AEM

  • Permite cambio de configuración sin reinicio: OSGi Configurable.
  • Separación total del código y la configuración: adecuado para CI/CD, compliance y gobernanza.

Buenas prácticas

  • Diseño de repositorio: Usar jerarquía clara (/content/site/sección/página) y nodos técnicos separados por función (contenido, configuración, DAM).
  • Separación lógica/presentación: Solo lógica simple en HTL. Business logic siempre en modelos/servicios.
  • Configuración OSGi: Siempre externalizar parámetros cambiantes. Proteger secretos con tipo "password" y scopes adecuados.
  • Rendimiento e índices: Toda consulta al JCR /Oak debe estar cubierta por índice adecuado y tener path raíz restringido.

Errores comunes y anti-patrones

Anti-patrónImpacto en producciónCómo corregirlo
Lógica de negocio en HTLImposibilidad testear/escalarMover a Sling Models/servicios Java
Consultas JCR no indexadas (sin predicado de path)Degradación extrema performanceCrear índice Oak y acotar queries por path/type
Config en código duro (hardcoded API keys, URLs)Riesgo de seguridad y releaseExternalizar en OSGi, proteger config
Uso excesivo de ResourceResolver/AdminSessionFugas de sesión, bloqueosSiempre try-with-resources y tipo de resolver adecuado
Uso de nodos flat con miles de hijosCuellos de botella JCR/OakEstructurar nodos jerárquicos (sharding por fecha ID, etc.)

Diferencias de enfoque: Tablas comparativas clave

EnfoqueVentajasRiesgos
Sling Model + HTLSeparación clara, pruebas, SPARequiere tipado
Lógica en HTLRápido prototipoDeuda técnica
OSGi config vs. hardcodedFlexible, seguroPuede sobrecomplejizar

Consideraciones de arquitectura (producción)

  • Dispatcher: Cacheo solo de recursos públicos; invalidar con rutas específicas.
  • Caché: Respetar reglas de cache headers, deshabilitar caché para endpoints personalizados.
  • Índices Oak: Monitorear groovy/oak-run, optimizar para queries frecuentes.
  • Estructura de nodos: Evitar más de 1000 hijos por nodo para alto rendimiento.

Conclusión

AEM sólo revela todo su potencial cuando se entiende como una plataforma orientada a recursos, componible y extensible, no solo un CMS. Usar Sling y OSGi correctamente desacoplando presentación/configuración/lógica, junto con un diseño eficiente de JCR y una arquitectura de repositorio clara, permitirá entregar proyectos escalables y robustos.

Checklist final

  • [x] Desacoplada la lógica negocio/presentación
  • [x] Consultas JCR indexadas y acotadas
  • [x] Configuración OSGi externalizada
  • [x] No hay lógica en HTL
  • [x] Estructura de nodos optimizada para rendimiento
  • [x] Arquitectura Documentada
  • [x] Reglas de Dispatcher configuradas correctamente

Cuándo usar este enfoque:

  • Proyectos enterprise con alta necesidad de extensión, integración continua y seguridad.
  • Equipos con roles diferenciados back/frontend/devops.
  • Cuando sea crítico asegurar performance, escalabilidad y gobernanza técnica.