Ръководство за протокол OData

1. Въведение

В този урок ще изследваме OData, стандартен протокол, който позволява лесен достъп до набори от данни с помощта на RESTFul API.

2. Какво е OData ?

OData е OASIS и ISO / IEC стандарт за достъп до данни с помощта на RESTful API. Като такъв той позволява на потребителя да открива и навигира през набори от данни, използвайки стандартни HTTP повиквания.

Например, ние може да получите достъп до един от публично достъпни OData услуги с един прост къдри фраза, която :

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

Към момента на написването протоколът OData е в четвъртата си версия - 4.01, за да бъдем по-точни. OData V4 достигна стандартното ниво на OASIS през 2014 г., но има по-дълга история. Можем да проследим корените му до проект на Microsoft, наречен Astoria, който беше преименуван на ADO.Net Data Services през 2007 г. Оригиналният запис в блога, обявяващ този проект, все още е достъпен в блога на Microsoft OData.

Наличието на стандартен протокол за достъп до набора от данни носи някои предимства пред стандартните API като JDBC или ODBC. Като потребител на ниво краен потребител можем да използваме популярни инструменти като Excel за извличане на данни от всеки съвместим доставчик. Програмирането се улеснява и от голям брой налични REST клиентски библиотеки.

Като доставчици приемането на OData също има предимства: след като създадем съвместима услуга, можем да се съсредоточим върху предоставянето на ценни набори от данни, които крайните потребители могат да консумират, използвайки инструментите по техен избор. Тъй като това е протокол, базиран на HTTP, можем да използваме и аспекти като механизми за сигурност, наблюдение и регистриране.

Тези характеристики направиха OData популярен избор от правителствените агенции при внедряването на услуги за обществени данни, както можем да проверим, като разгледаме тази директория.

3. Концепции на OData

В основата на протокола OData е концепцията за Entity Data Model - или накратко EDM. EDM описва данните, изложени от доставчик на OData чрез документ с метаданни, съдържащ редица мета-обекти:

  • Тип на обекта и неговите свойства (напр. Лице , Клиент , Поръчка и т.н.) и ключове
  • Връзки между субекти
  • Сложни типове, използвани за описване на структурирани типове, вградени в обекти (да речем, тип адрес, който е част от тип Клиент )
  • Набори от обекти, които обединяват обекти от даден тип

Спецификацията предвижда този документ с метаданни да е наличен на стандартното местоположение $ метаданни $ в основния URL адрес, използван за достъп до услугата. Например, ако имаме услуга OData, достъпна на //example.org/odata.svc/ , тогава нейният документ с метаданни ще бъде достъпен на //example.org/odata.svc/$metadata .

Върнатият документ съдържа куп XML, описващи схемите, поддържани от този сървър:

   ... schema elements omitted  

Нека разделим този документ на основните му раздели.

Елементът от най-високо ниво, може да има само едно дете, елемент .Важното нещо, което трябва да забележите, е URI на пространството от имена, тъй като ни позволява да идентифицираме коя версия на OData използва сървърът. В този случай пространството от имена показва, че имаме OData V2 сървър, който използва идентификаторите на Microsoft.

А DataServices елемент може да има един или повече Схема елементи, всеки описва свободен набор от данни. Тъй като пълното описание на наличните елементи в схема е извън обхвата на тази статия, ще се съсредоточим върху най-важните: EntityTypes, асоциации и EntitySets .

3.1. Елемент на EntityType

Този елемент дефинира наличните свойства на даден обект, включително неговия първичен ключ. Той може също да съдържа информация за връзки с други типове схеми и, като разгледаме пример - CarMaker - ще можем да видим, че той не е много различен от описанията, открити в други ORM технологии, като JPA:

Тук нашият CarMaker има само две свойства - Id и Name - и асоциация с друг EntityType . The Key и UB-елемент определя основен ключ на предприятието да бъде неговата Id собственост, както и всеки имот елемент съдържа данни за собственост на предприятието, като името, тип или nullability.

А NavigationProperty е специален вид на имота, който описва "точка за достъп" на свързано лице.

3.2. Асоциационен елемент

Един асоцииране елемент описва асоцииране между две лица, което включва множественост на всеки край и по избор референтна цялост ограничение:

Тук елементът Асоциация дефинира взаимоотношението един към много между обектите CarModel и CarMaker , където първият действа като зависима страна.

3.3. Елемент на EntitySet

Крайната концепция на схемата, която ще изследваме, е елементът EntitySet , който представлява колекция от обекти от даден тип. Въпреки че е лесно да ги мислим за аналогични на таблица - и в много случаи те са точно това - по-добра аналогия е тази на изгледа. Причината за това е, че можем да имаме множество елементи на EntitySet за един и същи тип EntityType , всеки от които представлява различно подмножество от наличните данни.

Елементът EntityContainer , който е елемент на схемата от най-високо ниво, групира всички налични EntitySet s:

В нашия прост пример имаме само два EntitySet , но бихме могли да добавим и допълнителни изгледи, като ForeignCarMakers или HistoricCarMakers .

4. URL адреси и методи на OData

За да получим достъп до данни, изложени от услуга OData, използваме обикновените HTTP глаголи:

  • GET връща един или повече обекти
  • POST добавя нов обект към съществуващ набор от обекти
  • PUT замества даден обект
  • PATCH замества специфични свойства на даден обект
  • DELETE премахва даден обект

Всички тези операции изискват ресурсен път, по който да се действа. Пътят на ресурса може да дефинира набор от обекти, обект или дори свойство в обекта.

Нека да разгледаме примерен URL адрес, използван за достъп до предишната ни услуга OData:

//example.org/odata/CarMakers 

Първата част на този URL, започвайки с протокола до сегмента odata / path, е известна като корен URL на услугата и е еднаква за всички пътища на ресурси на тази услуга. Тъй като коренът на услугата винаги е един и същ, ние ще го заменим в следващите примерни URL адреси с елипса („...“) .

В този случай CarMakers се отнася до един от декларираните EntitySets в метаданните на услугата. Можем да използваме обикновен браузър за достъп до този URL, който след това трябва да върне документ, съдържащ всички съществуващи обекти от този тип:

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

Върнатият документ съдържа входен елемент за всеки екземпляр на CarMaker .

Нека разгледаме отблизо каква информация имаме на разположение за нас:

  • id : връзка към този конкретен обект
  • заглавие / автор / актуализиран : метаданни за този запис
  • link elements: Връзки, използвани за насочване към ресурс, използван за редактиране на обекта ( rel = ”редактиране” ) или към свързани обекти. В този случай имаме връзка, която ни отвежда до набора от обекти на CarModel, свързани с този конкретен CarMaker .
  • съдържание : стойности на свойствата на обекта CarModel

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.

Що се отнася конкретно до JSON, той по същество е безсхемен. OData 4.01 обаче определя JSON схема и за крайни точки на метаданни. Това означава, че вече можем да пишем клиенти, които могат напълно да се отърват от XML обработката, ако решат да го направят.

6. Заключение

В това кратко въведение в OData, ние разгледахме неговата основна семантика и как да извършим проста навигация в набора от данни. Следващата ни статия ще продължи там, където сме тръгнали, и ще влезем направо в библиотеката на Олинго. След това ще видим как да внедрим примерни услуги с помощта на тази библиотека.

Примери за кодове, както винаги, са достъпни в GitHub.