OData-Protokollhandbuch

1. Einleitung

In diesem Tutorial werden wir OData untersuchen, ein Standardprotokoll, das den einfachen Zugriff auf Datensätze mithilfe einer RESTFul-API ermöglicht.

2. Was ist OData ?

OData ist ein OASIS- und ISO / IEC-Standard für den Zugriff auf Daten mithilfe einer RESTful-API. Auf diese Weise kann ein Verbraucher mithilfe von Standard-HTTP-Aufrufen Datensätze erkennen und durch diese navigieren.

Zum Beispiel können wir mit einem einfachen Curl - Einzeiler auf einen der öffentlich verfügbaren OData-Dienste zugreifen :

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

Zum jetzigen Zeitpunkt befindet sich das OData-Protokoll in der 4. Version - genauer gesagt in 4.01. OData V4 hat 2014 das OASIS-Standardniveau erreicht, hat aber eine längere Geschichte. Wir können seine Wurzeln auf ein Microsoft-Projekt namens Astoria zurückführen, das 2007 in ADO.Net Data Services umbenannt wurde. Der ursprüngliche Blogeintrag, in dem dieses Projekt angekündigt wird, ist weiterhin im OData-Blog von Microsoft verfügbar.

Ein standardbasiertes Protokoll für den Zugriff auf Datensätze bietet einige Vorteile gegenüber Standard-APIs wie JDBC oder ODBC. Als Endbenutzer können wir beliebte Tools wie Excel verwenden, um Daten von jedem kompatiblen Anbieter abzurufen. Die Programmierung wird auch durch eine große Anzahl verfügbarer REST-Client-Bibliotheken erleichtert.

Als Anbieter hat die Einführung von OData auch Vorteile: Sobald wir einen kompatiblen Dienst erstellt haben, können wir uns darauf konzentrieren, wertvolle Datensätze bereitzustellen, die Endbenutzer mit den Tools ihrer Wahl nutzen können. Da es sich um ein HTTP-basiertes Protokoll handelt, können wir auch Aspekte wie Sicherheitsmechanismen, Überwachung und Protokollierung nutzen.

Diese Eigenschaften machten OData bei Regierungsbehörden zu einer beliebten Wahl bei der Implementierung öffentlicher Datendienste, wie wir anhand dieses Verzeichnisses überprüfen können.

3. OData-Konzepte

Das Kernstück des OData-Protokolls ist das Konzept eines Entity Data Model - kurz EDM. Das EDM beschreibt die Daten, die von einem OData-Anbieter über ein Metadatendokument bereitgestellt werden, das eine Reihe von Meta-Entitäten enthält:

  • Entitätstyp und seine Eigenschaften (z. B. Person , Kunde , Bestellung usw.) und Schlüssel
  • Beziehungen zwischen Entitäten
  • Komplexe Typen zur Beschreibung strukturierter Typen, die in Entitäten eingebettet sind (z. B. ein Adresstyp, der Teil eines Kundentyps ist )
  • Entitätssätze, die Entitäten eines bestimmten Typs aggregieren

Die Spezifikation schreibt vor, dass dieses Metadatendokument am Standardspeicherort $ metadata unter der Stamm-URL verfügbar sein muss, die für den Zugriff auf den Dienst verwendet wird. Wenn beispielsweise ein OData-Dienst unter //example.org/odata.svc/ verfügbar ist , ist sein Metadatendokument unter //example.org/odata.svc/$metadata verfügbar .

Das zurückgegebene Dokument enthält eine Reihe von XML-Dateien, die die von diesem Server unterstützten Schemas beschreiben:

   ... schema elements omitted  

Lassen Sie uns dieses Dokument in seine Hauptabschnitte zerlegen.

Das oberste Element, kann nur ein Kind haben, das Element .Das Wichtigste dabei ist der Namespace-URI, mit dem wir feststellen können, welche OData-Version der Server verwendet. In diesem Fall gibt der Namespace an, dass wir einen OData V2-Server haben, der die Bezeichner von Microsoft verwendet.

Ein DataServices- Element kann ein oder mehrere Schemaelemente enthalten, die jeweils ein verfügbares Dataset beschreiben. Da eine vollständige Beschreibung der verfügbaren Elemente in einem Schema den Rahmen dieses Artikels sprengt, konzentrieren wir uns auf die wichtigsten: EntityTypes, Associations und EntitySets .

3.1. EntityType- Element

Dieses Element definiert die verfügbaren Eigenschaften einer bestimmten Entität, einschließlich ihres Primärschlüssels. Es kann auch Informationen über Beziehungen zu anderen Schematypen enthalten. Anhand eines Beispiels - eines CarMakers - können wir feststellen, dass es sich nicht wesentlich von den Beschreibungen in anderen ORM-Technologien wie JPA unterscheidet:

Hier unsere CarMaker haben nur zwei Eigenschaften - Id und Namen - und eine Verbindung zu einem anderen EntityType . Der Keys s ub-Element definiert die Primärschlüssel der Einheit ihre seine Id Eigenschaft und jedes Objektelement enthält Daten über eine Eigenschaft des Unternehmens, wie zB Namen, Typ oder NULL - Zulässigkeit.

Eine NavigationProperty ist eine spezielle Art von Eigenschaft, die einen „Zugriffspunkt“ auf eine verwandte Entität beschreibt.

3.2. Verband Element

Ein Assoziationselement beschreibt eine Assoziation zwischen zwei Entitäten, die die Multiplizität an jedem Ende und optional eine referenzielle Integritätsbeschränkung enthält:

Hier definiert das Assoziationselement eine Eins-zu-Viele-Beziehung zwischen einer CarModel- und einer CarMaker- Entität, wobei die erstere als abhängige Partei fungiert.

3.3. EntitySet- Element

Das letzte Schemakonzept , das wir untersuchen werden, ist das EntitySet- Element, das eine Sammlung von Entitäten eines bestimmten Typs darstellt. Während es einfach ist, sie als analog zu einer Tabelle zu betrachten - und in vielen Fällen sind sie nur das -, ist eine bessere Analogie die einer Ansicht. Der Grund dafür ist, dass wir mehrere EntitySet- Elemente für denselben EntityType haben können , die jeweils eine andere Teilmenge der verfügbaren Daten darstellen.

Das EntityContainer- Element, bei dem es sich um ein Schemaelement der obersten Ebene handelt, gruppiert alle verfügbaren EntitySets :

In unserem einfachen Beispiel haben wir nur zwei EntitySets , aber wir können auch zusätzliche Ansichten hinzufügen, z. B. ForeignCarMakers oder HistoricCarMakers .

4. OData-URLs und -Methoden

Um auf Daten zuzugreifen, die von einem OData-Dienst verfügbar gemacht werden, verwenden wir die regulären HTTP-Verben:

  • GET gibt eine oder mehrere Entitäten zurück
  • POST fügt einem vorhandenen Entitätssatz eine neue Entität hinzu
  • PUT ersetzt eine bestimmte Entität
  • PATCH ersetzt bestimmte Eigenschaften einer bestimmten Entität
  • DELETE entfernt eine bestimmte Entität

Für all diese Vorgänge ist ein Ressourcenpfad erforderlich, auf den reagiert werden kann. Der Ressourcenpfad kann einen Entitätssatz, eine Entität oder sogar eine Eigenschaft innerhalb einer Entität definieren.

Schauen wir uns eine Beispiel-URL an, die für den Zugriff auf unseren vorherigen OData-Service verwendet wird:

//example.org/odata/CarMakers 

Der erste Teil dieser URL, beginnend mit dem Protokoll bis zum Odata / Pfad-Segment, wird als Dienststamm-URL bezeichnet und ist für alle Ressourcenpfade dieses Dienstes gleich. Da der Dienststamm immer derselbe ist, ersetzen wir ihn in den folgenden URL-Beispielen durch ein Auslassungszeichen („…“) .

CarMakers bezieht sich in diesem Fall auf eines der deklarierten EntitySets in den Service-Metadaten. Wir können einen normalen Browser verwenden, um auf diese URL zuzugreifen. Dieser sollte dann ein Dokument zurückgeben, das alle vorhandenen Entitäten dieses Typs enthält:

  //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 

Das zurückgegebene Dokument enthält ein Eintragselement für jede CarMaker- Instanz.

Schauen wir uns genauer an, welche Informationen uns zur Verfügung stehen:

  • id : ein Link zu dieser bestimmten Entität
  • Titel / Autor / aktualisiert : Metadaten zu diesem Eintrag
  • Verknüpfungselemente : Verknüpfungen, die auf eine Ressource verweisen, die zum Bearbeiten der Entität verwendet wird ( rel = ”edit” ), oder auf verwandte Entitäten. In diesem Fall haben wir einen Link, der uns zu den CarModel- Entitäten führt, die diesem bestimmten CarMaker zugeordnet sind .
  • Inhalt : Eigenschaftswerte der CarModel- Entität

An important point to notice here is the use of the key-value pair to identify a particular entity within an entity set. In our example, the key is numeric so a resource path like CarMaker(1L) refers to the entity with a primary key value equal to 1 – the “L” here just denotes a long value and could be omitted.

5. Query Options

We can pass query options to a resource URL in order to modify a number of aspects of the returned data, such as to limit the size of the returned set or its ordering. The OData spec defines a rich set of options, but here we'll focus on the most common ones.

As a general rule, query options can be combined with each other, thus allowing clients to easily implement common functionalities such as paging, filtering and ordering result lists.

5.1. $top and $skip

We can navigate through a large dataset using the $top an $skip query options:

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

$top tells the service that we want only the first 10 records of the CarMakers entity set. A $skip, which is applied before the $top, tells the server to skip the first 10 records.

It's usually useful to know the size of a given Entity Set and, for this purpose, we can use the $count sub-resource:

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

In Bezug auf JSON ist es grundsätzlich schemenlos. OData 4.01 definiert jedoch auch ein JSON-Schema für Metadatenendpunkte. Dies bedeutet, dass wir jetzt Clients schreiben können, die die XML-Verarbeitung vollständig entfernen können, wenn sie dies wünschen.

6. Fazit

In dieser kurzen Einführung in OData haben wir die grundlegende Semantik und die einfache Navigation von Datensätzen behandelt. Unser Folgeartikel wird dort fortgesetzt, wo wir abgereist sind, und direkt in die Olingo-Bibliothek gehen. Wir werden dann sehen, wie Beispieldienste mithilfe dieser Bibliothek implementiert werden.

Codebeispiele sind wie immer auf GitHub verfügbar.