«1. Введение

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

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

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

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

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

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

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

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

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

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

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

    Спецификация предписывает, чтобы этот документ метаданных был доступен в стандартном месте корневой 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>

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

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

3.1. Элемент EntityType

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

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

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

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

3.2. Элемент Association

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

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

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

3.3. Элемент EntitySet

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

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

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

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

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

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

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

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

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

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

http://example.org/odata/CarMakers

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

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

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

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

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

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

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

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

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

5.1. $top и $skip

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

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

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

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

«

.../CarMakers/$count

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

.../CarMakers?$count=true

5.2. $filter

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

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

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

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

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

5.3. $expand

По умолчанию запрос 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

Мы используем параметр запроса $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

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

5.6. $format

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

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

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

.../CarModels?$format=json

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

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

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

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

«