Guide du protocole OData

1. Introduction

Dans ce didacticiel, nous explorerons OData, un protocole standard qui permet un accès facile aux ensembles de données à l'aide d'une API RESTFul.

2. Qu'est-ce que OData ?

OData est une norme OASIS et ISO / CEI pour accéder aux données à l'aide d'une API RESTful. En tant que tel, il permet à un consommateur de découvrir et de parcourir des ensembles de données à l'aide d'appels HTTP standard.

Par exemple, nous pouvons accéder à l'un des services OData accessibles au public avec un simple curl one-liner :

curl -s //services.odata.org/V2/Northwind/Northwind.svc/Regions   Regions //services.odata.org/V2/Northwind/Northwind.svc/Regions ... rest of xml response omitted

Au moment d'écrire ces lignes, le protocole OData en est à sa 4e version - 4.01 pour être plus précis. OData V4 a atteint le niveau standard OASIS en 2014, mais son histoire est plus longue. Nous pouvons retracer ses racines dans un projet Microsoft appelé Astoria, qui a été renommé ADO.Net Data Services en 2007. L'entrée de blog originale annonçant ce projet est toujours disponible sur le blog OData de Microsoft.

Le fait d'avoir un protocole basé sur des normes pour accéder à l'ensemble de données apporte certains avantages par rapport aux API standard telles que JDBC ou ODBC. En tant que consommateur au niveau de l'utilisateur final, nous pouvons utiliser des outils populaires tels qu'Excel pour récupérer des données auprès de n'importe quel fournisseur compatible. La programmation est également facilitée par un grand nombre de bibliothèques clientes REST disponibles.

En tant que fournisseurs, l'adoption d'OData présente également des avantages: une fois que nous avons créé un service compatible, nous pouvons nous concentrer sur la fourniture d'ensembles de données précieux, que les utilisateurs finaux peuvent utiliser à l'aide des outils de leur choix. Puisqu'il s'agit d'un protocole basé sur HTTP, nous pouvons également exploiter des aspects tels que les mécanismes de sécurité, la surveillance et la journalisation.

Ces caractéristiques ont fait d'OData un choix populaire des agences gouvernementales lors de la mise en œuvre de services de données publiques, comme nous pouvons le vérifier en jetant un œil à cet annuaire.

3. Concepts OData

Au cœur du protocole OData se trouve le concept d'un modèle de données d'entité - ou EDM en abrégé. L'EDM décrit les données exposées par un fournisseur OData via un document de métadonnées contenant un certain nombre de méta-entités:

  • Type d'entité et ses propriétés (par exemple, personne , client , commande , etc.) et clés
  • Relations entre entités
  • Types complexes utilisés pour décrire des types structurés intégrés dans des entités (par exemple, un type d'adresse qui fait partie d'un type de client )
  • Ensembles d'entités, qui regroupent les entités d'un type donné

La spécification stipule que ce document de métadonnées doit être disponible à l'emplacement standard $ metadata à l'URL racine utilisée pour accéder au service. Par exemple, si nous avons un service OData disponible sur //example.org/odata.svc/ , alors son document de métadonnées sera disponible sur //example.org/odata.svc/$metadata .

Le document retourné contient un tas de XML décrivant les schémas pris en charge par ce serveur:

   ... schema elements omitted  

Décomposons ce document en ses sections principales.

L'élément de premier niveau, ne peut avoir qu'un seul enfant, le élément .La chose importante à noter ici est l'URI de l'espace de noms car il nous permet d'identifier la version OData utilisée par le serveur. Dans ce cas, l'espace de noms indique que nous avons un serveur OData V2, qui utilise les identifiants de Microsoft.

Un élément DataServices peut avoir un ou plusieurs éléments de schéma , chacun décrivant un ensemble de données disponible. Étant donné qu'une description complète des éléments disponibles dans un schéma dépasse le cadre de cet article, nous nous concentrerons sur les plus importants: EntityTypes, Associations et EntitySets .

3.1. Élément EntityType

Cet élément définit les propriétés disponibles d'une entité donnée, y compris sa clé primaire. Il peut également contenir des informations sur les relations avec d'autres types de schémas et, en regardant un exemple - un CarMaker - nous pourrons voir qu'il n'est pas très différent des descriptions trouvées dans d'autres technologies ORM, telles que JPA:

Ici, notre CarMaker n'a que deux propriétés - Id et Name - et une association à un autre EntityType . L' élément Key s ub-element définit la clé primaire de l'entité comme étant sa propriété Id , et chaque élément Property contient des données sur la propriété d'une entité, telles que son nom, son type ou son caractère nul.

Un NavigationProperty est un type spécial de propriété qui décrit un «point d'accès» à une entité associée.

3.2. Élément d' association

Un élément Association décrit une association entre deux entités, qui comprend la multiplicité à chaque extrémité et éventuellement une contrainte d'intégrité référentielle:

Ici, l' élément Association définit une relation un-à-plusieurs entre un CarModel et des entités CarMaker , où la première agit en tant que partie dépendante.

3.3. Élément EntitySet

Le concept de schéma final que nous allons explorer est l' élément EntitySet , qui représente une collection d'entités d'un type donné. Bien qu'il soit facile de les penser comme analogues à un tableau - et dans de nombreux cas, ce n'est que cela - une meilleure analogie est celle d'une vue. La raison en est que nous pouvons avoir plusieurs éléments EntitySet pour le même EntityType , chacun représentant un sous-ensemble différent des données disponibles.

L' élément EntityContainer , qui est un élément de schéma de niveau supérieur, regroupe tous les EntitySet disponibles :

Dans notre exemple simple, nous n'avons que deux EntitySet , mais nous pourrions également ajouter des vues supplémentaires, telles que ForeignCarMakers ou HistoricCarMakers .

4. URL et méthodes OData

Afin d'accéder aux données exposées par un service OData, nous utilisons les verbes HTTP habituels:

  • GET renvoie une ou plusieurs entités
  • POST ajoute une nouvelle entité à un ensemble d'entités existant
  • PUT remplace une entité donnée
  • PATCH remplace les propriétés spécifiques d'une entité donnée
  • DELETE supprime une entité donnée

Toutes ces opérations nécessitent un chemin de ressources sur lequel agir. Le chemin de ressource peut définir un ensemble d'entités, une entité ou même une propriété au sein d'une entité.

Jetons un coup d'œil à un exemple d'URL utilisée pour accéder à notre précédent service OData:

//example.org/odata/CarMakers 

La première partie de cette URL, en commençant par le protocole jusqu'au segment odata / path, est connue sous le nom d' URL racine du service et est la même pour tous les chemins de ressources de ce service. Puisque la racine du service est toujours la même, nous la remplacerons dans les exemples d'URL suivants par des points de suspension («…») .

CarMakers , dans ce cas, fait référence à l'un des EntitySets déclarés dans les métadonnées du service. Nous pouvons utiliser un navigateur classique pour accéder à cette URL, qui devrait alors renvoyer un document contenant toutes les entités existantes de ce type:

  //localhost:8080/odata/CarMakers CarMakers 2019-04-06T17:51:33.588-03:00      //localhost:8080/odata/CarMakers(1L) CarMakers 2019-04-06T17:51:33.589-03:00      1 Special Motors    ... other entries omitted 

Le document renvoyé contient un élément d' entrée pour chaque instance CarMaker .

Examinons de plus près les informations dont nous disposons:

  • id : un lien vers cette entité spécifique
  • title / author / updated : métadonnées sur cette entrée
  • éléments de lien : liens utilisés pour pointer vers une ressource utilisée pour éditer l'entité ( rel = "edit" ) ou vers des entités associées. Dans ce cas, nous avons un lien qui nous amène à l'ensemble des CarModel entités associées à ce particulier CarMaker .
  • content : valeurs de propriété de l' entité CarModel

Un point important à noter ici est l'utilisation de la paire clé-valeur pour identifier une entité particulière dans un ensemble d'entités. Dans notre exemple, la clé est numérique, donc un chemin de ressource comme CarMaker (1L) fait référence à l'entité avec une valeur de clé primaire égale à 1 - le « L » ici désigne simplement une valeur longue et pourrait être omis.

5. Options de requête

Nous pouvons passer des options de requête à une URL de ressource afin de modifier un certain nombre d'aspects des données renvoyées, par exemple pour limiter la taille de l'ensemble retourné ou son ordre. La spécification OData définit un riche ensemble d'options, mais nous nous concentrerons ici sur les plus courantes.

En règle générale, les options de requête peuvent être combinées entre elles, permettant ainsi aux clients d'implémenter facilement des fonctionnalités communes telles que la pagination, le filtrage et le classement des listes de résultats.

5.1. $ top et $ skip

Nous pouvons naviguer dans un grand ensemble de données en utilisant les options de requête $ top et $ skip :

.../CarMakers?$top=10&$skip=10 

$ top indique au service que nous ne voulons que les 10 premiers enregistrements de l' ensemble d'entités CarMakers . Un $ skip, qui est appliqué avant le $ top, indique au serveur de sauter les 10 premiers enregistrements.

Il est généralement utile de connaître la taille d'un ensemble d'entités donné et, à cette fin, nous pouvons utiliser la sous-ressource $ count :

.../CarMakers/$count 

This resource produces a text/plain document containing the size of the corresponding set. Here, we must pay attention to the specific OData version supported by a provider. While OData V2 supports $count as a sub-resource from a collection, V4 allows it to be used as a query parameter. In this case, $count is a Boolean, so we need to change the URL accordingly:

.../CarMakers?$count=true 

5.2. $filter

We use the $filter query option to limit the returned entities from a given Entity Set to those matching given criteria. The value for the $filter is a logical expression that supports basic operators, grouping and a number of useful functions. For instance, let's build a query that returns all CarMaker instances where its Name attribute starts with the letter ‘B':

.../CarMakers?$filter=startswith(Name,'B') 

Now, let's combine a few logical operators to search for CarModels of a particular Year and Maker:

.../CarModels?$filter=Year eq 2008 and CarMakerDetails/Name eq 'BWM' 

Here, we've used the equality operator eq to specify values for the properties. We can also see how to use properties from a related entity in the expression.

5.3. $expand

By default, an OData query does not return data for related entities, which is usually OK. We can use the $expand query option to request that data from a given related entity be included inline with the main content.

Using our sample domain, let's build an URL that returns data from a given model and its maker, thus avoiding an additional round-trip to the server:

.../CarModels(1L)?$expand=CarMakerDetails 

The returned document now includes the CarMaker data as part of the related entity:

  //example.org/odata/CarModels(1L) CarModels 2019-04-07T11:33:38.467-03:00      //example.org/odata/CarMakers(1L) CarMakers 2019-04-07T11:33:38.492-03:00      1 Special Motors        1 1 Muze SM001 2018   

5.4. $select

We use the $select query option to inform the OData service that it should only return the values for the given properties. This is useful in scenarios where our entities have a large number of properties, but we're only interested in some of them.

Let's use this option in a query that returns only the Name and Sku properties:

.../CarModels(1L)?$select=Name,Sku 

The resulting document now has only the requested properties:

... xml omitted   Muze SM001   ... xml omitted

We can also see that even related entities were omitted. In order to include them, we'd need to include the name of the relation in the $select option.

5.5. $orderBy

The $orderBy option works pretty much as its SQL counterpart. We use it to specify the order in which we want the server to return a given set of entities. In its simpler form, its value is just a list of property names from the selected entity, optionally informing the order direction:

.../CarModels?$orderBy=Name asc,Sku desc 

This query will result in a list of CarModels ordered by their names and SKUs, in ascending and descending directions, respectively.

An important detail here is the case used with the direction part of a given property: while the spec mandates that server must support any combination of upper- and lower-case letters for the keywords asc and desc, it also mandates that client use only lowercase.

5.6. $format

This option defines the data representation format that the server should use, which takes precedence over any HTTP content-negotiation header, such as Accept. Its value must be a full MIME-Type or a format-specific short form.

For instance, we can use json as an abbreviation for application/json:

.../CarModels?$format=json 

This URL instructs our service to return data using JSON format, instead of XML, as we've seen before. When this option is not present, the server will use the value of the Accept header, if present. When neither is available, the server is free to choose any representation – usually XML or JSON.

En ce qui concerne JSON en particulier, il est fondamentalement sans schéma. Cependant, OData 4.01 définit également un schéma JSON pour les points de terminaison de métadonnées. Cela signifie que nous pouvons maintenant écrire des clients qui peuvent se débarrasser totalement du traitement XML s'ils le souhaitent.

6. Conclusion

Dans cette brève introduction à OData, nous avons couvert sa sémantique de base et comment effectuer une navigation simple dans les ensembles de données. Notre article de suivi continuera là où nous sommes partis et ira directement dans la bibliothèque Olingo. Nous verrons ensuite comment implémenter des exemples de services à l'aide de cette bibliothèque.

Des exemples de code, comme toujours, sont disponibles à l'adresse over sur GitHub.