Durante mas de una decada, Service Builder fue la forma estandar de definir entidades de datos en Liferay. Escribias un archivo service.xml con la definicion de tu modelo, ejecutabas el generador de codigo, y obtenias las clases Java para persistencia, servicios locales, servicios remotos y finders. Era potente, pero tenia un costo alto:
service.xml tenia una curva de aprendizaje significativaEn proyectos empresariales, esto creaba un cuello de botella constante. Un analista de negocio identificaba la necesidad de una nueva entidad -- digamos, "Sucursales" con campos como nombre, direccion, region y horario -- y tenia que esperar a que un desarrollador Java la implementara en Service Builder. Para algo que conceptualmente era una tabla con columnas, el proceso podia tomar dias.
Liferay Objects, introducido en DXP 7.4, resuelve exactamente este problema. Permite crear entidades de datos completas desde la interfaz de administracion, sin escribir una sola linea de codigo. Y lo mas importante: cada entidad creada con Objects expone automaticamente una API REST completa.
Vamos a recorrer el proceso completo usando un ejemplo real: una entidad "Sucursales" para gestionar las oficinas de una empresa, con relacion a "Regiones".
Desde el Panel de Control de Liferay, en la seccion Object Definitions, creas un nuevo objeto con:
Liferay genera automaticamente el endpoint REST basandose en el nombre: /o/c/sucursals (pluraliza en ingles por convencion interna, algo que confunde al principio pero es consistente).
Objects soporta los siguientes tipos de campo:
Para nuestra entidad Sucursales, definimos:
| Campo | Tipo | Requerido | Notas | |-------|------|-----------|-------| | nombre | Text (200) | Si | Nombre de la sucursal | | direccion | Long Text | Si | Direccion completa | | telefono | Text (20) | No | Telefono de contacto | | activa | Boolean | Si | Default: true | | fechaApertura | Date | No | Cuando abrio | | capacidad | Integer | No | Capacidad maxima | | tipo | Picklist | Si | Valores: Principal, Secundaria, Express |
Cada campo permite definir reglas de validacion sin codigo. Para el campo nombre, por ejemplo, puedes establecer:
Para campos de tipo Integer, puedes definir rango minimo y maximo. Para Picklists, las opciones validas se definen en la seccion de Picklists del Panel de Control, lo que permite reutilizar la misma lista en multiples Objects.
Una vez publicado el Object, Liferay genera automaticamente endpoints REST con CRUD completo. No necesitas configurar nada adicional. Los endpoints siguen el patron:
GET /o/c/sucursals → Listar (con paginacion)
GET /o/c/sucursals/{id} → Obtener por ID
POST /o/c/sucursals → Crear
PUT /o/c/sucursals/{id} → Actualizar (completo)
PATCH /o/c/sucursals/{id} → Actualizar (parcial)
DELETE /o/c/sucursals/{id} → Eliminar
La API usa paginacion por defecto. El formato de respuesta incluye metadatos utiles:
{
"items": [...],
"page": 1,
"pageSize": 20,
"totalCount": 47,
"lastPage": 3,
"actions": {}
}
Puedes controlar la paginacion con parametros: ?page=2&pageSize=50.
Esta es una de las funcionalidades mas potentes de Objects. Los endpoints soportan filtrado usando sintaxis OData, lo que permite consultas complejas sin necesidad de endpoints personalizados:
# Sucursales activas
GET /o/c/sucursals?filter=activa eq true
# Sucursales con capacidad mayor a 50
GET /o/c/sucursals?filter=capacidad gt 50
# Sucursales de tipo Principal abiertas despues de 2023
GET /o/c/sucursals?filter=tipo eq 'Principal' and fechaApertura gt 2023-01-01
# Busqueda por texto parcial en nombre
GET /o/c/sucursals?filter=contains(nombre, 'Centro')
# Ordenamiento
GET /o/c/sucursals?sort=nombre:asc
# Combinacion de filtro, orden y paginacion
GET /o/c/sucursals?filter=activa eq true&sort=fechaApertura:desc&page=1&pageSize=10
Los operadores disponibles incluyen eq, ne, gt, ge, lt, le, contains, startswith, and, or, y not. Esto cubre la gran mayoria de casos de uso sin necesidad de logica personalizada en el backend.
Cuando un Object tiene relaciones, puedes solicitar los datos relacionados en una sola peticion usando el parametro nestedFields:
GET /o/c/sucursals?nestedFields=region&nestedFieldsDepth=1
Esto devuelve cada sucursal con su region embebida en la respuesta, evitando el problema de N+1 queries que seria comun si tuvieras que hacer una peticion adicional por cada sucursal para obtener su region.
Objects soporta dos tipos de relacion:
La mas comun. Por ejemplo, una Region tiene muchas Sucursales. Se configura creando un campo de tipo Relationship en el Object hijo (Sucursal) apuntando al Object padre (Region).
En la API, esto se refleja como:
{
"id": 42,
"nombre": "Sucursal Centro",
"r_region_c_regionId": 5,
"region": {
"id": 5,
"nombre": "Region Metropolitana"
}
}
El campo r_region_c_regionId sigue la convencion de nombres interna de Liferay para relaciones. Es verboso, pero consistente.
Soportada a partir de Liferay DXP 7.4 U72. Crea una tabla intermedia automaticamente. Por ejemplo, si una Sucursal puede pertenecer a multiples Programas de Fidelizacion, defines una relacion many-to-many y Liferay gestiona la tabla de union.
Objects se integra con el motor de workflows de Liferay. Puedes asignar un workflow a un Object para que cada nueva entrada pase por un proceso de aprobacion. Por ejemplo, una nueva Sucursal podria requerir aprobacion de un gerente regional antes de quedar activa.
Esto se configura desde la seccion de Workflow del Object Definition, sin codigo adicional.
Cada Object respeta el sistema de permisos de Liferay. Puedes configurar:
La API REST aplica estos permisos automaticamente. Si un usuario no tiene permiso para ver Sucursales, el endpoint devuelve 403, no datos vacios.
Esta es la pregunta mas comun en equipos que migran a Liferay 7.4. La respuesta depende de la complejidad de la logica de negocio:
Usa Objects cuando:
Usa Service Builder cuando:
En la practica, muchos proyectos usan ambos. Objects para las entidades simples de configuracion y catalogo, Service Builder para el dominio core con logica compleja. Lo importante es no forzar una entidad compleja en Objects ni sobredimensionar una entidad simple con Service Builder.
Despues de usar Objects extensivamente en produccion, estas son las limitaciones que he encontrado:
No hay logica de negocio personalizada: Si necesitas que al crear una Sucursal se envie un email al gerente regional, calcule distancias, o sincronice con un sistema externo, Objects no tiene donde poner ese codigo. Puedes mitigarlo parcialmente con Object Actions (webhooks que se disparan en eventos CRUD), pero la logica vive fuera de Liferay.
Rendimiento a escala: Objects usa una estructura de tablas generica internamente (patron EAV -- Entity-Attribute-Value). Para miles de registros funciona bien. Para millones con consultas complejas, empieza a mostrar limitaciones frente a tablas nativas optimizadas con indices especificos.
Migraciones de esquema: Modificar un Object publicado tiene restricciones. No puedes eliminar campos que ya tienen datos, ni cambiar tipos de campo. Puedes agregar nuevos campos, pero las modificaciones destructivas requieren recrear el Object.
Nombres de campos en la API: Los nombres generados para relaciones (r_relation_c_objectId) son poco intuitivos. Tus consumidores de API necesitaran documentacion clara.
La combinacion mas poderosa en Liferay 7.4 es Objects + Client Extensions. Tu Client Extension (React, Angular, o cualquier framework) consume los datos de Objects a traves de la API REST generada automaticamente.
Un ejemplo tipico en una Client Extension React:
async function getSucursales(filtroRegion) {
const filter = filtroRegion
? `?filter=r_region_c_regionId eq ${filtroRegion}&sort=nombre:asc`
: '?sort=nombre:asc'
const response = await Liferay.Util.fetch(
`/o/c/sucursals${filter}`,
{
headers: { 'Accept': 'application/json' },
}
)
const data = await response.json()
return data.items
}
Liferay.Util.fetch es un wrapper disponible en el contexto de Liferay que maneja automaticamente la autenticacion (agrega el token CSRF y las cookies de sesion). Para llamadas externas, usarias OAuth2.
Objects transforma a Liferay de un CMS que requiere Java para todo, a una plataforma donde un equipo frontend puede crear entidades de datos y consumirlas desde Client Extensions sin depender de un equipo backend. Eso no reemplaza a Service Builder para casos complejos, pero cubre un porcentaje significativo de las necesidades de proyectos empresariales tipicos.