Capítulo 2: Conceptos Fundamentales de OData
Para trabajar eficazmente con OData, es esencial comprender sus conceptos básicos. Este capítulo profundiza en los elementos centrales que definen la estructura y el comportamiento de un servicio OData, incluyendo el modelo de datos, los tipos de datos, los documentos clave del servicio, las convenciones de URI y los formatos de representación de datos.
Modelo de Datos de Entidad (EDM)
En el corazón de cada servicio OData se encuentra su Modelo de Datos de Entidad (Entity Data Model - EDM). El EDM es un modelo abstracto que formaliza la descripción de los recursos expuestos por el servicio. Define la estructura de los datos, incluyendo los tipos de entidades, sus propiedades, y las relaciones (asociaciones y propiedades de navegación) entre ellas. ¿Cómo se describe el EDM?
El Lenguaje de Definición de Esquema Común (CSDL, por sus siglas en inglés Common Schema Definition Language) es el formato utilizado para describir formalmente el EDM en servicios OData. Aunque el EDM es un modelo abstracto, necesita una representación concreta que pueda ser interpretada por clientes y herramientas.
CSDL cumple esta función mediante una estructura estandarizada basada en XML (y también JSON en OData V4), que incluye:
Tipos de entidad y sus claves
Propiedades primitivas y complejas
Relaciones entre entidades
Conjuntos de entidades, singletons, acciones y funciones
Esta representación se expone a través del endpoint $metadata
de un servicio OData, y permite que los consumidores descubran y comprendan automáticamente la estructura de los datos disponibles.
Los componentes clave del EDM son:
Tipos de Entidad (Entity Types): Representan la estructura de los recursos de nivel superior, como Productos, Clientes o Pedidos. Cada tipo de entidad tiene un nombre único dentro de su espacio de nombres (namespace) y se compone de un conjunto de propiedades. Debe tener una clave definida, que consiste en una o más propiedades primitivas que identifican de forma única una instancia de ese tipo.
<EntityType Name="Producto">
<Key>
<PropertyRef Name="ID" />
</Key>
<Property Name="ID" Type="Edm.Int32" Nullable="false" />
<Property Name="Nombre" Type="Edm.String" />
<Property Name="Precio" Type="Edm.Decimal" />
</EntityType>
Explicación del código:
Se define un tipo de entidad denominado Producto
, con tres propiedades: ID
, Nombre
y Precio
. La propiedad ID
está marcada como clave primaria a través del elemento <Key>
, lo que garantiza que cada instancia de Producto
sea única. Las propiedades utilizan tipos de datos primitivos definidos por OData, como Edm.Int32
para enteros y Edm.Decimal
para valores numéricos con decimales.
Tipos Complejos (Complex Types): Son conjuntos estructurados de propiedades sin una clave propia. Se utilizan para agrupar propiedades relacionadas dentro de un tipo de entidad (por ejemplo, un tipo complejo Direccion con propiedades Calle, Ciudad, CodigoPostal dentro de un tipo de entidad Cliente).
<ComplexType Name="Direccion">
<Property Name="Calle" Type="Edm.String" />
<Property Name="Ciudad" Type="Edm.String" />
<Property Name="CodigoPostal" Type="Edm.String" />
</ComplexType>
Explicación del código:
El tipo complejo Direccion
contiene tres propiedades: Calle
, Ciudad
y CodigoPostal
, todas de tipo Edm.String
. Este tipo no tiene clave propia y se utiliza como parte de otra entidad, como por ejemplo un Cliente
, para encapsular información relacionada con su ubicación. Los tipos complejos permiten reutilizar estructuras dentro de múltiples entidades sin necesidad de definirlas repetidamente.
Propiedades (Properties): Definen los datos contenidos dentro de los tipos de entidad o tipos complejos. Cada propiedad tiene un nombre y un tipo de dato (primitivo o complejo).
<Property Name="Nombre" Type="Edm.String" />
<Property Name="Precio" Type="Edm.Decimal" />
Explicación del código:
Estas líneas muestran dos propiedades que podrían estar contenidas dentro de una entidad como Producto
. La propiedad Nombre
representa una cadena de texto, y Precio
un número decimal. El atributo Type
indica el tipo de dato, en este caso tipos primitivos definidos por el estándar OData (Edm.String
, Edm.Decimal
). Las propiedades forman la base del contenido estructurado de una entidad.
Propiedades de Navegación (Navigation Properties): Representan las relaciones o asociaciones entre tipos de entidad. Permiten navegar desde una entidad a entidades relacionadas (por ejemplo, desde un Pedido a su Cliente asociado o a su colección de LineasDePedido). Se basan en Asociaciones.
<NavigationProperty Name="Pedidos" Type="Collection(MiServicio.Pedido)" Partner="Cliente" />
Explicación del código:
Esta propiedad de navegación llamada Pedidos
indica que una entidad, como Cliente
, está relacionada con múltiples instancias de la entidad Pedido
. La relación es de uno a muchos, como lo sugiere la palabra Collection
. El atributo Partner="Cliente"
establece la referencia inversa desde la entidad Pedido
, completando la asociación bidireccional entre ambas entidades.
Asociaciones (Associations): Definen la relación entre dos tipos de entidad, incluyendo la cardinalidad (uno a uno, uno a muchos, muchos a muchos). Las propiedades de navegación se derivan de estas asociaciones.
<EntityType Name="Pedido">
<Key>
<PropertyRef Name="ID" />
</Key>
<Property Name="ID" Type="Edm.Int32" Nullable="false" />
<NavigationProperty Name="Cliente" Type="MiServicio.Cliente" Partner="Pedidos" />
</EntityType>
Explicación del código:
En OData V4, las asociaciones no se declaran de forma explícita como en V2, sino que se representan directamente mediante propiedades de navegación. En este ejemplo, se declara que un Pedido
está asociado a un Cliente
. Esta relación se vincula a través del atributo Partner="Pedidos"
, que hace referencia a la colección correspondiente en la entidad opuesta (Cliente
).
Contenedor de Entidades (Entity Container): Es el punto de entrada al modelo de datos. Agrupa los Conjuntos de Entidades (Entity Sets), Singletons, Importaciones de Funciones (Function Imports) e Importaciones de Acciones (Action Imports) expuestos por el servicio.
<EntityContainer Name="ServicioContenedor">
<EntitySet Name="Productos" EntityType="MiServicio.Producto" />
<EntitySet Name="Clientes" EntityType="MiServicio.Cliente" />
<Singleton Name="Yo" Type="MiServicio.Cliente" />
</EntityContainer>
Explicación del código:
El contenedor ServicioContenedor
agrupa y define los puntos de entrada públicos del servicio OData. Incluye conjuntos de entidades (Productos
y Clientes
) y un singleton llamado Yo
, que representa una instancia única del tipo Cliente
. Este contenedor permite acceder a las colecciones o entidades individuales a través de URLs como /Productos
, /Clientes
o /Yo
.
Conjuntos de Entidades (Entity Sets): Son colecciones nombradas de instancias de un tipo de entidad específico (por ejemplo, un conjunto de entidades Productos que contiene todas las instancias de la entidad Producto). Son los principales puntos de entrada para consultar colecciones de datos.
<EntitySet Name="Productos" EntityType="MiServicio.Producto" />
<EntitySet Name="Clientes" EntityType="MiServicio.Cliente" />
Explicación del código:
Cada línea define un conjunto de entidades disponible en el servicio. Productos
hace referencia a una colección de instancias del tipo Producto
, mientras que Clientes
agrupa instancias de la entidad Cliente
. Estos conjuntos permiten consultar y manipular múltiples recursos del mismo tipo a través de endpoints como /Productos
o /Clientes
.
Singletons: Representan una única instancia de un tipo de entidad, accesible directamente desde el contenedor de entidades (por ejemplo, Yo para el usuario actual).
<Singleton Name="Yo" Type="MiServicio.Cliente" />
Explicación del código:
El singleton Yo
representa una sola instancia de la entidad Cliente
. A diferencia de los EntitySets
, los singletons no son colecciones, sino entidades individuales accesibles directamente desde la raíz del servicio, mediante una ruta como /Yo
.
Importaciones de Funciones y Acciones: Definen operaciones personalizadas que se pueden invocar en el servicio (se detallan en capítulos posteriores).
<ActionImport Name="ConfirmarPedido" Action="MiServicio.ConfirmarPedido" />
Explicación del código:
Este ejemplo muestra una importación de acción llamada ConfirmarPedido
, que permite ejecutar una operación definida en el modelo, invocable mediante un POST
al servicio. Las acciones permiten implementar lógica adicional, como validaciones o cálculos, que no corresponden directamente a las operaciones CRUD estándar.
Modelo completo de ejemplo:
<Schema xmlns="http://docs.oasis-open.org/odata/ns/edm" Namespace="MiServicio">
<ComplexType Name="Direccion">
<Property Name="Calle" Type="Edm.String" />
<Property Name="Ciudad" Type="Edm.String" />
<Property Name="CodigoPostal" Type="Edm.String" />
</ComplexType>
<EntityType Name="Cliente">
<Key>
<PropertyRef Name="ID" />
</Key>
<Property Name="ID" Type="Edm.Int32" Nullable="false" />
<Property Name="Nombre" Type="Edm.String" />
<Property Name="Email" Type="Edm.String" />
<Property Name="Direccion" Type="MiServicio.Direccion" />
<NavigationProperty Name="Pedidos" Type="Collection(MiServicio.Pedido)" Partner="Cliente" />
</EntityType>
<EntityType Name="Pedido">
<Key>
<PropertyRef Name="ID" />
</Key>
<Property Name="ID" Type="Edm.Int32" Nullable="false" />
<Property Name="Fecha" Type="Edm.DateTimeOffset" />
<Property Name="Total" Type="Edm.Decimal" />
<NavigationProperty Name="Cliente" Type="MiServicio.Cliente" Partner="Pedidos" />
</EntityType>
<EntityType Name="Producto">
<Key>
<PropertyRef Name="ID" />
</Key>
<Property Name="ID" Type="Edm.Int32" Nullable="false" />
<Property Name="Nombre" Type="Edm.String" />
<Property Name="Precio" Type="Edm.Decimal" />
</EntityType>
<EntityContainer Name="ServicioContenedor">
<EntitySet Name="Clientes" EntityType="MiServicio.Cliente">
<NavigationPropertyBinding Path="Pedidos" Target="Pedidos" />
</EntitySet>
<EntitySet Name="Pedidos" EntityType="MiServicio.Pedido">
<NavigationPropertyBinding Path="Cliente" Target="Clientes" />
</EntitySet>
<EntitySet Name="Productos" EntityType="MiServicio.Producto" />
<Singleton Name="Yo" Type="MiServicio.Cliente" />
<ActionImport Name="ConfirmarPedido" Action="MiServicio.ConfirmarPedido" />
</EntityContainer>
</Schema>
El EDM se describe formalmente en el documento de metadatos del servicio, generalmente utilizando el Lenguaje de Definición de Esquema Común (CSDL - Common Schema Definition Language), una representación basada en XML.
Tipos de Datos (Primitivos, Complejos, Enumeraciones)
OData define un conjunto de tipos de datos abstractos que se utilizan en el EDM. Estos se mapean a tipos concretos en el protocolo y en las implementaciones del servicio.
Tipos Primitivos: Representan valores atómicos. Algunos tipos primitivos comunes incluyen:
Edm.String: Secuencias de caracteres Unicode.
Edm.Boolean: Valores booleanos (true, false).
Edm.Byte, Edm.SByte: Enteros de 8 bits (con o sin signo).
Edm.Int16, Edm.Int32, Edm.Int64: Enteros de 16, 32 y 64 bits con signo.
Edm.Single, Edm.Double: Números de punto flotante de precisión simple y doble.
Edm.Decimal: Valores numéricos de alta precisión.
Edm.Guid: Identificadores únicos globales.
Edm.Binary: Datos binarios.
Edm.Date (V4): Fecha sin hora ni zona horaria.1
Edm.TimeOfDay (V4): Hora del día sin fecha ni zona horaria.1
Edm.DateTimeOffset: Fecha y hora con desplazamiento respecto a UTC (reemplaza a Edm.DateTime de V2, que fue deprecado por falta de información de zona horaria).1
Edm.Duration (V4): Duración de tiempo.12
Edm.Stream: Flujo de datos binarios (usado para Media Resources).
Tipos Complejos: Como se mencionó, son tipos estructurados sin clave, compuestos por otras propiedades (primitivas o complejas).7
Tipos de Enumeración (Enum Types - V4): Permiten definir un conjunto de valores nombrados (miembros) para una propiedad.1 Por ejemplo, un EstadoPedido podría ser una enumeración con miembros Nuevo, Procesando, Enviado, Entregado.
La versión 4 de OData introdujo y refinó varios tipos de datos para mayor precisión, especialmente en el manejo de fechas y horas.1
Estructura de un Servicio OData
Un servicio OData expone varios recursos clave a través de URIs específicas:
Documento de Servicio
Ubicación: En la raíz del servicio (por ejemplo, http://host/service/).6
Propósito: Lista los recursos de nivel superior disponibles en el servicio, como los Conjuntos de Entidades, Singletons e Importaciones de Funciones/Acciones.6
Uso: Permite a los clientes descubrir los puntos de entrada principales del servicio y navegar por el modelo de forma hipermedia.6
Formato: Puede ser Atom/XML o JSON.8
Documento de Metadatos ($metadata)
Ubicación: En la URI $metadata relativa a la raíz del servicio (por ejemplo, http://host/service/$metadata).6
Propósito: Describe el Modelo de Datos de Entidad (EDM) completo del servicio.6 Define todos los tipos de entidad, tipos complejos, enumeraciones, propiedades, asociaciones, conjuntos de entidades, acciones, funciones, etc., que el servicio comprende.6
Uso: Es fundamental para los clientes, ya que les permite entender la estructura de los datos, cómo interactuar con las entidades y cómo construir consultas válidas.4 Herramientas y bibliotecas cliente lo utilizan para generar proxies o facilitar el desarrollo.4
Formato: Tradicionalmente en formato CSDL (XML).6 OData V4 también permite un formato JSON.18
Convenciones de URI
OData define un conjunto de convenciones (recomendadas pero no estrictamente requeridas en V2, aunque muy alentadas) para construir URIs que identifican los diversos recursos expuestos por el servicio.7 Seguir estas convenciones promueve la consistencia y facilita la creación de herramientas y bibliotecas cliente reutilizables.8
La estructura general de una URI OData es 8:
scheme://host:port/serviceRoot/resourcePath?queryOptions
serviceRoot: La URI raíz del servicio (ej. https://services.odata.org/OData/OData.svc/).8 Apunta al Documento de Servicio.
resourcePath: Identifica el recurso específico al que se accede. Puede ser:
$metadata: Para el documento de metadatos.
EntitySetName: Para acceder a una colección de entidades (ej. Products).6
EntitySetName(key): Para acceder a una entidad individual por su clave (ej. Products(1) o Categories(CategoryID=1, CategoryName='Food') si la clave es compuesta).8
EntitySetName(key)/PropertyName: Para acceder a una propiedad específica de una entidad (ej. Products(1)/Name).8
EntitySetName(key)/PropertyName/$value: Para acceder al valor crudo (raw) de una propiedad primitiva (ej. Products(1)/Name/$value).26
EntitySetName(key)/NavigationPropertyName: Para acceder a entidades relacionadas a través de una propiedad de navegación (ej. Categories(1)/Products).8
EntitySetName(key)/$links/NavigationPropertyName: Para acceder a las referencias (URIs) de las entidades relacionadas.8
FunctionImportName(parameters): Para invocar una importación de función.8
queryOptions: Opciones de consulta del sistema (prefijadas con $) y opciones de consulta personalizadas, separadas por & (ej. ?$top=2&$orderby=Name).6 Se detallan en el Capítulo 4.
Formatos de Datos (JSON, Atom/XML)
OData especifica formatos para representar tanto los datos como el modelo de datos.
Representación de Datos:
JSON (JavaScript Object Notation): Es el formato estándar y preferido en OData V4.2 Es ligero, legible por humanos y fácil de procesar por clientes JavaScript.9 OData V4 introduce mejoras para trabajar con JSON, incluyendo diferentes niveles de control de metadatos en la respuesta (odata.metadata=full|minimal|none).9
Atom/XML: Basado en el formato Atom Publishing Protocol (AtomPub) y XML.7 Era el formato principal en versiones anteriores y sigue siendo una opción en V4 (aunque en estado de "committee specification" en 6). Suele ser más verboso que JSON.18
Representación del Modelo de Datos (Metadatos):
CSDL (Common Schema Definition Language) XML: La representación estándar y tradicional del EDM, definida en formato XML.6 Es el formato devuelto por la solicitud $metadata.
CSDL JSON: OData V4 también define una representación JSON para los metadatos.18
La elección del formato de datos suele negociarse mediante las cabeceras HTTP Accept (en la solicitud del cliente) y Content-Type (en la respuesta del servidor).
Last updated