Перейти к основному содержимому

Руководство по протоколу OData

· 11 мин. чтения

1. Введение

В этом руководстве мы рассмотрим OData — стандартный протокол, обеспечивающий простой доступ к наборам данных с помощью RESTFul API.

2. Что такое OData ?

OData — это стандарт OASIS и ISO/IEC для доступа к данным с использованием RESTful API. Таким образом, он позволяет потребителю находить наборы данных и перемещаться по ним с помощью стандартных вызовов HTTP.

Например, мы можем получить доступ к одному из общедоступных сервисов OData с помощью простого однострочника curl :

curl -s https://services.odata.org/V2/Northwind/Northwind.svc/Regions
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<feed xml:base="https://services.odata.org/V2/Northwind/Northwind.svc/"
xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices"
xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
xmlns="http://www.w3.org/2005/Atom">
<title type="text">Regions</title>
<id>https://services.odata.org/V2/Northwind/Northwind.svc/Regions</id>
... rest of xml response omitted

На момент написания этой статьи протокол OData находится в 4-й версии — 4.01, если быть точнее. OData V4 достиг стандартного уровня OASIS в 2014 году, но у него более длинная история. Мы можем проследить его корни до проекта Microsoft под названием Astoria, который в 2007 году был переименован в ADO.Net Data Services . Исходная запись в блоге, анонсирующая этот проект, по-прежнему доступна в блоге Microsoft OData.

Наличие основанного на стандартах протокола для доступа к набору данных дает некоторые преимущества по сравнению со стандартными API, такими как JDBC или ODBC. Как потребитель уровня конечного пользователя мы можем использовать популярные инструменты, такие как Excel, для получения данных от любого совместимого поставщика. Программирование также облегчается большим количеством доступных клиентских библиотек REST.

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

Эти характеристики сделали OData популярным выбором государственных учреждений при внедрении общедоступных служб данных, в чем мы можем убедиться, взглянув на этот каталог .

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

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

  • Тип объекта и его свойства (например , Person , Customer , Order и т. д.) и ключи
  • Отношения между сущностями
  • Сложные типы, используемые для описания структурированных типов, встроенных в сущности (например, тип адреса, являющийся частью типа Customer ) .
  • Наборы сущностей, объединяющие сущности данного типа.

Спецификация требует, чтобы этот документ метаданных был доступен в стандартном расположении $metadata по корневому URL-адресу, используемому для доступа к службе. Например, если у нас есть служба OData, доступная по адресу http://example.org/odata.svc/ , то ее документ метаданных будет доступен по адресу http://example.org/odata.svc/$metadata .

Возвращенный документ содержит набор XML, описывающих схемы, поддерживаемые этим сервером:

<?xml version="1.0"?>
<edmx:Edmx
xmlns:edmx="http://schemas.microsoft.com/ado/2007/06/edmx"
Version="1.0">
<edmx:DataServices
xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
m:DataServiceVersion="1.0">
... schema elements omitted
</edmx:DataServices>
</edmx:Edmx>

Давайте разберем этот документ на основные разделы.

У элемента верхнего уровня <edmx:Edmx> может быть только один дочерний элемент, элемент <edmx:DataServices> . Здесь важно отметить URI пространства имен, поскольку он позволяет нам определить, какую версию OData использует сервер. В данном случае пространство имен указывает, что у нас есть сервер OData V2, который использует идентификаторы Microsoft.

Элемент DataServices может иметь один или несколько элементов Schema , каждый из которых описывает доступный набор данных. Поскольку полное описание доступных элементов в схеме выходит за рамки этой статьи, мы сосредоточимся на наиболее важных из них: EntityTypes, Associations и EntitySets .

3.1. Элемент EntityType

Этот элемент определяет доступные свойства данного объекта, включая его первичный ключ. Он также может содержать информацию об отношениях с другими типами схем, и, взглянув на пример — CarMaker — мы сможем увидеть, что он не сильно отличается от описаний, найденных в других технологиях ORM, таких как JPA:

<EntityType Name="CarMaker">
<Key>
<PropertyRef Name="Id"/>
</Key>
<Property Name="Id" Type="Edm.Int64"
Nullable="false"/>
<Property Name="Name" Type="Edm.String"
Nullable="true"
MaxLength="255"/>
<NavigationProperty Name="CarModelDetails"
Relationship="default.CarModel_CarMaker_Many_One0"
FromRole="CarMaker"
ToRole="CarModel"/>
</EntityType>

Здесь наш CarMaker имеет только два свойства — Id и Name — и связь с другим EntityType . Подэлемент Key определяет первичный ключ объекта как его свойство Id , а каждый элемент Property содержит данные о свойстве объекта, такие как его имя, тип или допустимость значений NULL.

NavigationProperty — это особый вид свойства, описывающий «точку доступа» к связанному объекту.

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

Элемент Association описывает ассоциацию между двумя объектами, которая включает множественность на каждом конце и, необязательно, ограничение ссылочной целостности:

<Association Name="CarModel_CarMaker_Many_One0">
<End Type="default.CarModel" Multiplicity="*" Role="CarModel"/>
<End Type="default.CarMaker" Multiplicity="1" Role="CarMaker"/>
<ReferentialConstraint>
<Principal Role="CarMaker">
<PropertyRef Name="Id"/>
</Principal>
<Dependent Role="CarModel">
<PropertyRef Name="Maker"/>
</Dependent>
</ReferentialConstraint>
</Association>

Здесь элемент Association определяет отношение «один ко многим» между объектами CarModel и CarMaker , где первый выступает в качестве зависимой стороны.

3.3. Элемент EntitySet

Последнее понятие схемы, которое мы рассмотрим, — это элемент EntitySet , представляющий набор сущностей заданного типа. Хотя их легко представить как аналог таблицы (а во многих случаях это именно так), лучшая аналогия — это представление. Причина этого в том, что у нас может быть несколько элементов EntitySet для одного и того же EntityType , каждый из которых представляет разное подмножество доступных данных.

Элемент EntityContainer , который является элементом схемы верхнего уровня, группирует все доступные EntitySet s:

<EntityContainer Name="defaultContainer" 
m:IsDefaultEntityContainer="true">
<EntitySet Name="CarModels"
EntityType="default.CarModel"/>
<EntitySet Name="CarMakers"
EntityType="default.CarMaker"/>
</EntityContainer>

В нашем простом примере у нас есть только два EntitySet , но мы также можем добавить дополнительные представления, такие как ForeignCarMakers или HistoricCarMakers .

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

Чтобы получить доступ к данным, предоставляемым службой OData, мы используем обычные HTTP-глаголы:

  • GET возвращает один или несколько объектов
  • POST добавляет новую сущность в существующий набор сущностей.
  • PUT заменяет данный объект
  • PATCH заменяет определенные свойства данного объекта
  • DELETE удаляет данный объект

Для выполнения всех этих операций требуется путь к ресурсу. Путь к ресурсу может определять набор сущностей, сущность или даже свойство внутри сущности.

Давайте взглянем на пример URL-адреса, используемого для доступа к нашему предыдущему сервису OData:

http://example.org/odata/CarMakers

Первая часть этого URL-адреса, начиная с протокола и заканчивая сегментом odata/ path, называется корневым URL-адресом службы и одинакова для всех путей к ресурсам этой службы. Поскольку корень службы всегда один и тот же, мы заменим его в следующих образцах URL многоточием («…») .

CarMakers в данном случае ссылается на один из объявленных наборов EntitySet в метаданных службы. Мы можем использовать обычный браузер для доступа к этому URL-адресу, который затем должен вернуть документ, содержащий все существующие объекты этого типа:

<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"
xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices"
xml:base="http://localhost:8080/odata/">
<id>http://localhost:8080/odata/CarMakers</id>
<title type="text">CarMakers</title>
<updated>2019-04-06T17:51:33.588-03:00</updated>
<author>
<name/>
</author>
<link href="CarMakers" rel="self" title="CarMakers"/>
<entry>
<id>http://localhost:8080/odata/CarMakers(1L)</id>
<title type="text">CarMakers</title>
<updated>2019-04-06T17:51:33.589-03:00</updated>
<category term="default.CarMaker"
scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme"/>
<link href="CarMakers(1L)" rel="edit" title="CarMaker"/>
<link href="CarMakers(1L)/CarModelDetails"
rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/CarModelDetails"
title="CarModelDetails"
type="application/atom+xml;type=feed"/>
<content type="application/xml">
<m:properties>
<d:Id>1</d:Id>
<d:Name>Special Motors</d:Name>
</m:properties>
</content>
</entry>
... other entries omitted
</feed>

Возвращенный документ содержит элемент ввода для каждого экземпляра CarMaker .

Давайте подробнее рассмотрим, какая информация нам доступна:

  • id : ссылка на этот конкретный объект
  • название/автор/обновлено : метаданные об этой записи
  • элементы ссылки : ссылки, используемые для указания на ресурс, используемый для редактирования объекта ( rel="edit" ) или на связанные объекты. В этом случае у нас есть ссылка, которая ведет нас к набору сущностей CarModel , связанных с этим конкретным CarMaker .
  • content : значения свойств сущности CarModel

Важным моментом, на который следует обратить внимание, является использование пары ключ-значение для идентификации конкретной сущности в наборе сущностей. В нашем примере ключ является числовым, поэтому путь к ресурсу, такой как CarMaker(1L) , относится к объекту со значением первичного ключа, равным 1 — здесь буква « L » просто обозначает длинное значение и может быть опущена.

5. Параметры запроса

Мы можем передать параметры запроса URL-адресу ресурса, чтобы изменить ряд аспектов возвращаемых данных, например, ограничить размер возвращаемого набора или его порядок. Спецификация OData определяет богатый набор параметров, но здесь мы сосредоточимся на наиболее распространенных.

Как правило, параметры запроса можно комбинировать друг с другом, что позволяет клиентам легко реализовать общие функции, такие как пейджинг, фильтрация и упорядочивание списков результатов.

5.1. $верхнее и $пропустить

Мы можем перемещаться по большому набору данных, используя параметры запроса $top и $skip :

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

$top сообщает службе, что нам нужны только первые 10 записей набора сущностей CarMakers . $skip, который применяется перед $top, указывает серверу пропустить первые 10 записей.

Обычно полезно знать размер данного набора сущностей , и для этой цели мы можем использовать подресурс $count :

.../CarMakers/$count

Этот ресурс создает текстовый/обычный документ, содержащий размер соответствующего набора. Здесь мы должны обратить внимание на конкретную версию OData, поддерживаемую провайдером. В то время как OData V2 поддерживает $count в качестве подресурса из коллекции, V4 позволяет использовать его в качестве параметра запроса. В этом случае $count является логическим значением, поэтому нам нужно соответствующим образом изменить URL-адрес:

.../CarMakers?$count=true

5.2. $фильтр

Мы используем параметр запроса $filter , чтобы ограничить возвращаемые сущности из заданного набора сущностей теми, которые соответствуют заданным критериям. Значение фильтра $filter — это логическое выражение, которое поддерживает основные операторы, группировку и ряд полезных функций. Например, давайте создадим запрос, который возвращает все экземпляры CarMaker , где его атрибут Name начинается с буквы «B»:

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

Теперь давайте объединим несколько логических операторов для поиска моделей автомобилей определенного года и производителя :

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

Здесь мы использовали оператор равенства eq для указания значений свойств. Мы также можем увидеть, как использовать свойства связанной сущности в выражении.

5.3. $ развернуть

По умолчанию запрос OData не возвращает данные для связанных сущностей , что обычно нормально. Мы можем использовать параметр запроса $expand , чтобы запросить, чтобы данные из данного связанного объекта были включены в основной контент.

Используя наш образец домена, давайте создадим URL-адрес, который возвращает данные из данной модели и ее производителя, избегая, таким образом, дополнительного обращения к серверу:

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

Возвращенный документ теперь включает данные CarMaker как часть связанного объекта:

<?xml version="1.0" encoding="utf-8"?>
<entry xmlns="http://www.w3.org/2005/Atom"
xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices"
xml:base="http://localhost:8080/odata/">
<id>http://example.org/odata/CarModels(1L)</id>
<title type="text">CarModels</title>
<updated>2019-04-07T11:33:38.467-03:00</updated>
<category term="default.CarModel"
scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme"/>
<link href="CarModels(1L)" rel="edit" title="CarModel"/>
<link href="CarModels(1L)/CarMakerDetails"
rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/CarMakerDetails"
title="CarMakerDetails"
type="application/atom+xml;type=entry">
<m:inline>
<entry xml:base="http://localhost:8080/odata/">
<id>http://example.org/odata/CarMakers(1L)</id>
<title type="text">CarMakers</title>
<updated>2019-04-07T11:33:38.492-03:00</updated>
<category term="default.CarMaker"
scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme"/>
<link href="CarMakers(1L)" rel="edit" title="CarMaker"/>
<link href="CarMakers(1L)/CarModelDetails"
rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/CarModelDetails"
title="CarModelDetails"
type="application/atom+xml;type=feed"/>
<content type="application/xml">
<m:properties>
<d:Id>1</d:Id>
<d:Name>Special Motors</d:Name>
</m:properties>
</content>
</entry>
</m:inline>
</link>
<content type="application/xml">
<m:properties>
<d:Id>1</d:Id>
<d:Maker>1</d:Maker>
<d:Name>Muze</d:Name>
<d:Sku>SM001</d:Sku>
<d:Year>2018</d:Year>
</m:properties>
</content>
</entry>

5.4. $выбрать

Мы используем параметр запроса $select, чтобы сообщить службе OData, что она должна возвращать значения только для заданных свойств. Это полезно в сценариях, когда наши объекты имеют большое количество свойств, но нас интересуют только некоторые из них.

Давайте воспользуемся этой опцией в запросе, который возвращает только свойства Name и Sku :

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

Результирующий документ теперь имеет только запрошенные свойства:

... xml omitted
<content type="application/xml">
<m:properties>
<d:Name>Muze</d:Name>
<d:Sku>SM001</d:Sku>
</m:properties>
</content>
... xml omitted

Мы также можем видеть, что даже связанные сущности были опущены. Чтобы включить их, нам нужно указать имя отношения в опции $select .

5.5. $orderBy

Параметр $orderBy работает почти так же, как его аналог SQL. Мы используем его, чтобы указать порядок, в котором мы хотим, чтобы сервер возвращал заданный набор сущностей. В более простой форме его значение представляет собой просто список имен свойств из выбранного объекта, опционально сообщающий направление заказа:

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

Результатом этого запроса будет список CarModels , упорядоченный по именам и SKU в порядке возрастания и убывания соответственно.

Важной деталью здесь является случай, используемый с частью направления данного свойства: хотя спецификация требует, чтобы сервер поддерживал любую комбинацию букв верхнего и нижнего регистра для ключевых слов asc и desc , он также требует, чтобы клиент использовал только строчные буквы . .

5.6. $формат

Этот параметр определяет формат представления данных, который должен использовать сервер, который имеет приоритет над любым заголовком согласования содержимого HTTP, таким как Accept . Его значение должно быть полным MIME-типом или короткой формой, зависящей от формата.

Например, мы можем использовать json как аббревиатуру для application/json :

.../CarModels?$format=json

Этот URL указывает нашей службе возвращать данные в формате JSON, а не в формате XML, как мы видели ранее. Если этот параметр отсутствует, сервер будет использовать значение заголовка Accept , если оно присутствует. Когда ни один из них недоступен, сервер может выбрать любое представление — обычно XML или JSON.

Что касается конкретно JSON, он принципиально не имеет схемы. Однако OData 4.01 также определяет схему JSON для конечных точек метаданных . Это означает, что теперь мы можем писать клиенты, которые могут полностью избавиться от обработки XML, если захотят.

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

В этом кратком введении в OData мы рассмотрели его базовую семантику и то, как выполнять простую навигацию по набору данных. Наша следующая статья продолжится с того места, где мы остановились, и перейдем прямо к библиотеке Olingo. Затем мы увидим, как реализовать примеры сервисов с помощью этой библиотеки.

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