Протокол OData имеет множество скрытых достоинств (хотя и недостатков хватает). Начиная с этой статьи, мы хотели бы поделиться мелкими полезными хитростями использования протокола OData.

Все дальнейшие примеры используют следующую простую схему данных

  • persons
  • books
  • companies

person properties:

  • id (string)
  • firstname (string)
  • lastname (string)
  • age (number)
  • likes (list of books)

book properties:

  • id (string)
  • title (string)
  • author (list of persons)
  • publisher (a company)

company properties:

  • id (string)
  • name (string)
  • president (person)


Тестовая база выложена на сайте databoom.space
Тестовые данные можно посмотреть используя OData URL samples.databoom.space/api1/sampledb/collections/allobjects

1. Как получить массив объектов, на которые кто-то ссылается


Предположим мы хотим получить список книг, которые нравятся какому-либо человеку
Чтобы получить человека с id 'person1', мы можем написать
.../persons(person1)

Чтобы получить книги, которые нравятся этому человеку, добавим к URL имя свойства likes
.../persons(person1)/likes

Теперь мы можем пойти дальше и найти все издательства, издавшие книги, которые нравятся person1
.../persons(person1)/likes/publisher

2. Как получить массив объектов которые на кого-то ссылаются


В предыдущем примере мы получали список книг, которые нравятся какому человеку с id 'person1'.
Теперь мы хотим получить всех людей которым нравится книга с id 'book2'. Обратите внимание человек через свойство likes ссылается на книги которые он любит, а книги не ссылаются на людей, которые их любят.

Чтобы получить книгу с id ‘book2’, мы можем написать
.../books(book2)

Мы не можем написать .../books(book1)/persons_who_likes — у книги нет такого свойства

Тогда мы пишем
.../books(book1)/_backlink(likes)

_backlink(likes) — это как бы обратный линк для линка likes
likes — что любят
_backlink(likes) — кто любит

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



Если Вас заинтересовал данный пост, Вы можете также посмотреть нашу документацию и примеры использования REST API, а также примеры с использованием JavaScript библиотеки

В следующем посте мы расскажем, как отфильтровать объекты с учетом их взаимосвязей.

Комментарии (18)


  1. lair
    17.07.2015 22:42
    +1

    А где хитрости-то?


    1. databoom Автор
      18.07.2015 00:26

      они мелкие :)

      1) Довольно многие люди не знают что OData позволяет переходить по ссылкам как угодно далеко
      /persons(person1)/likes/publisher/president
      обычно рассматривают лишь получение объектов по прямой ссылке — /persons(person1)/likes

      причем для обработки таких длинных ссылок вовсе не требуется что то писать на сервере — обычно OData провайдеры превращают такую ссылку в запрос автоматически

      2) Также возникает проблема построить url — когда нет прямой ссылки на объект и разработчик сервера не позаботился об этом — OData провайдеры такую ссылку тоже превращают в запрос к базе данных автоматически создавая правильный SQL join запрос или аналогичный запрос для NoSQL баз данных


      1. lair
        18.07.2015 01:56

        причем для обработки таких длинных ссылок вовсе не требуется что то писать на сервере — обычно OData провайдеры превращают такую ссылку в запрос автоматически

        Что значит «обычно OData провайдер»? По-моему, как на той стороне сочтут нужным ограничить, так и будет.


        1. databoom Автор
          18.07.2015 13:33

          могут лишь ограничить доступ к таким длинным ссылкам — но не поменять семантику — если мы по ссылке
          /persons(person1)/likes/publisher/president — получим не президентов компаний издавших книги, которые нравятся person1 — то это уже самодельный REST API а не OData


          1. lair
            18.07.2015 13:35

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


  1. drakmail
    18.07.2015 09:29

    А как в OData с обновлением вложенных свойств? Например для

    studio: {
      addresses: [{
        groups: [{
          job_name: "rnb"
        }, {
          job_name: "test",
        }]
      }]
    }
    


    Нужно обновить group, в которой job_name равен rnb. Как это правильнее сделать? Или тут правильнее хранить линк на job_id для списка jobs и просто обновлять job?


    1. databoom Автор
      18.07.2015 13:30

      можно прямо так сохранять — но нужно однозначно идентифицировать каждый job — если хочется его проапдейтить
      мы обязательно расскажем подробнее про добавление вложенных объектов в следующих постах


      1. databoom Автор
        18.07.2015 13:40

        идентифицировать — имеется в виду выставить ключевое поле — в разных реализациях OData это может быть поле id, _id и т.д. или же тот же набор полей, что в базе названы ключом


      1. drakmail
        18.07.2015 14:56

        Да, было бы очень интересно прочитать. Недавно столкнулся с проблемой, что в монге просто так работать с вложенными объектами не получится (да, надо было читать матчасть сначала :))


  1. vintage
    18.07.2015 20:23

    1. А как получить все необходимые мне данные (понравившиеся книги, их издательства, кому они ещё понравились и тд) одним запросом, а не десятком последовательных запросов?

    2. Прямые ссылки нормальные, а обратные через какие-то костыли. Почему бы не сделать модель с нормальными двусторонними ссылками (likedBook — likesPerson или in_likes — out_likes)?


  1. databoom Автор
    18.07.2015 21:35

    1) А как получить все необходимые мне данные (понравившиеся книги, их издательства, кому они ещё понравились и тд) одним запросом, а не десятком последовательных запросов?

    для этого существует опция $expand
    например получить понравившиеся книги с издательствами и авторами
    .../person(person1)/likes?$expand=publisher,author

    мы про $expand расскажем подробнее в следующих постах

    2. Прямые ссылки нормальные, а обратные через какие-то костыли. Почему бы не сделать модель с нормальными двусторонними ссылками (likedBook — likesPerson или in_likes — out_likes)?

    можно даже не предусматривать двусторонние ссылки а просто указать что обратная ссылка с именем likesPerson или out_likes является обратной от likes (или наоборот) — провайдер просто должен иметь информацию как генерить join запрос

    вопрос был а если разработчики сервера не описали модель и не написали никаких кодов для обработки таких обратных ссылок — можно ли как то получить данные?
    например если провайдер использует schemaless модель — типа сохраняем данные как они есть без описания модели (например, сохраняет данные в mongodb)


  1. vintage
    18.07.2015 21:52

    для этого существует опция $expand
    Тогда зачем что-то помимо expand? Я могу написать например: /person(person1)?$expand=likes.publisher,likes.author,likes.publisher.books


  1. databoom Автор
    18.07.2015 22:13

    Да можно раскрыть объекты на любую глубину, но это:
    1) Иногда увеличивает объем данных — например, каждая книга одного и того же автора в json будет содержать дублирующуюся информацию об авторе. Мы в одном из постов расскажем как избегать такого дублирования данных.
    2) Иногда информация про промежуточные объекты бывает не нужна — например мы просто хотим получить список любимых издательств.
    Еще пример, я хочу найти друзей друзей, которые учились со мной в одном институте — зачем мне тысячи человек? мне нужно только несколько.


  1. vintage
    18.07.2015 22:36

    1) Иногда увеличивает объем данных — например, каждая книга одного и того же автора в json будет содержать дублирующуюся информацию об авторе. Мы в одном из постов расскажем как избегать такого дублирования данных
    Выдавать сущности не развесистым деревом, а плоским списком — не будет никакого дублирования.

    Еще пример, я хочу найти друзей друзей, которые учились со мной в одном институте — зачем мне тысячи человек? мне нужно только несколько.

    1. Ну так и пляшите от института, а не от друзей.
    2. Но даже если выдадут 1000 айдишников — ничего страшного.
    3. $expand=likes,likes.publisher и $expand=likes.publisher вполне могут выдавать разный набор данных. Второй вариант просто не выведет информацию о лайках, только список издательств.


  1. databoom Автор
    18.07.2015 22:50

    Выдавать сущности не развесистым деревом, а плоским списком — не будет никакого дублирования.

    OData предполагает выдачу в виде дерева в случае $expand — это оговорено в стандарте


    1. vintage
      18.07.2015 23:56

      Глупый какой-то стандарт. И как решается проблема дублирования? Данные выдаются только для первого вхождения, а для остальных только айдишник?


      1. databoom Автор
        19.07.2015 00:54

        Вообще то в стандарте это проблема — там в сообществе ведутся споры как это делать — предлагается несколько вариантов — но пока ни один не вошел в текущий стандарт.
        Вопрос не в том как сделать а вопрос скорее в синтаксисе с которым согласится большинство :)


        1. vintage
          19.07.2015 11:16

          Ну да, а толпа глупа и очень инертна, так что уверен победит самое бестолковое решение :-)