← Volver al blog
·9 min de lectura

Objects en Liferay DXP: modelado de datos sin escribir codigo

LiferayAPI

La evolucion desde Service Builder

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:

  • Requeria conocimiento de Java, OSGi y el ciclo de vida de modulos de Liferay
  • El archivo service.xml tenia una curva de aprendizaje significativa
  • Cada cambio en el modelo requeria regenerar codigo, recompilar y redesplegar
  • Solo desarrolladores podian crear o modificar entidades

En 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.

Crear un Object Definition paso a paso

Vamos a recorrer el proceso completo usando un ejemplo real: una entidad "Sucursales" para gestionar las oficinas de una empresa, con relacion a "Regiones".

Definicion basica

Desde el Panel de Control de Liferay, en la seccion Object Definitions, creas un nuevo objeto con:

  • Label: Sucursales (lo que ven los usuarios)
  • Plural Label: Sucursales
  • Object Name: Sucursal (nombre interno, usado en la API)

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).

Campos disponibles

Objects soporta los siguientes tipos de campo:

  • Text: cadenas de texto con longitud maxima configurable
  • Long Text: texto largo con soporte para multilinea
  • Integer: numeros enteros
  • Long Integer: enteros de 64 bits
  • Decimal: numeros con punto decimal (BigDecimal internamente)
  • Boolean: verdadero/falso
  • Date: fecha sin hora
  • DateTime: fecha con hora y timezone
  • Picklist: lista de opciones predefinidas (dropdown)
  • Attachment: archivos adjuntos almacenados en el Document Library
  • Rich Text: HTML con editor WYSIWYG
  • Relationship: referencia a otro Object

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 |

Validaciones

Cada campo permite definir reglas de validacion sin codigo. Para el campo nombre, por ejemplo, puedes establecer:

  • Longitud minima: 3 caracteres
  • Longitud maxima: 200 caracteres
  • Expresion regular: patron personalizado si necesitas restricciones adicionales

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.

La API REST automatica

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

Paginacion

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.

Filtrado con OData

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.

Nested fields y relaciones

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.

Relaciones entre Objects

Objects soporta dos tipos de relacion:

One-to-Many

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.

Many-to-Many

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.

Integracion con workflows y permisos

Workflows

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.

Permisos

Cada Object respeta el sistema de permisos de Liferay. Puedes configurar:

  • Que roles pueden crear, leer, actualizar o eliminar entradas
  • Permisos a nivel de entrada individual (scope por site, por compania, o por usuario)
  • Permisos sobre campos especificos (un rol puede ver ciertos campos pero no otros)

La API REST aplica estos permisos automaticamente. Si un usuario no tiene permiso para ver Sucursales, el endpoint devuelve 403, no datos vacios.

Objects vs Service Builder: cuando usar cada uno

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:

  • La entidad es principalmente CRUD (crear, leer, actualizar, eliminar)
  • No necesitas logica de negocio compleja en el backend
  • Quieres que usuarios no tecnicos puedan modificar la estructura
  • Necesitas una API REST rapida para prototipar o para Client Extensions
  • Las validaciones se pueden expresar con reglas simples (requerido, longitud, regex)

Usa Service Builder cuando:

  • Necesitas logica de negocio compleja (calculos, integraciones con sistemas externos, transformaciones de datos)
  • Requieres consultas SQL personalizadas que OData no puede expresar
  • Necesitas triggers o eventos personalizados complejos que van mas alla de los workflows estandar
  • El rendimiento es critico y necesitas control fino sobre las consultas
  • Necesitas herencia de entidades o patrones de diseno complejos

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.

Limitaciones reales de Objects

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.

Como Client Extensions consumen Objects

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.