Что такое идеальный "квант информации" или "минимальная единица смысла" и зачем задаваться этим вопросом? С этим связано много боли, часто даже неосознаваемой. Мы предлагаем решение, но сначала нужно разобраться для чего это нужно. Рекомендуем ознакомиться с первой статьёй в серии.

Ретроспектива

Relational tables, document oriented databases, jsonb stores... В них мы часто представляем минимальной единицей смысла модель в таблице/коллекции. Модель состоит из разной структуры колонок/полей, и отражает некий реальный объект или абстрактную концепцию автора.

Такой подход создает проблемы:

  • Множество пространств адресации. (Если вы не видите ее, вскоре будет статья исключительно на эту тему, и вы увидите почему это проблема реальна).

  • Множество точек контроля структуры данных, изменение которых стоит дорого.

  • Совместимость только на том уровне который заложен автором.

  • Неспособность полноценно описать что-то новое без создания еще одной таблицы/коллекции.

  • Невозможность разделить предметы от отношений между ними.

Все эти проблемы решаются в ассоциативной модели данных Deep.Case.

Если вам интересна оптимизация или поиск по диапазонам в графе - здесь будет ссылка.

Давайте разбираться

Deep.Case оперирует концепцией Link (связь) как минимальной единицей смысла / квантом информации.

Links таблица

Links (сеть связей) очень похожа на граф, однако в графе есть однозначное деление на nodes (узлы) и edges (рёбра) ссылающиеся только на них. Links не разделяет эти пространства адресов. Это позволяет связям ссылаться на связи и нести любой смысл который в них может заложить автор моделей данных.

Link структура

Структура связи состоит из обязательных уникального id связи и type_id связи используемой как тип данной связи. Необязательные поля from_id и to_id могут быть заполнены только вместе.

Типы для создания связей легко создаются пользователем самостоятельно в рамках модели проекта, или поставляются вместе с импортируемыми моделями-пакетами.

Заполненные поля from_id и to_id ссылаются на другие связи. Такими связями можно описывать любые отношения.

{
  links(where: { id: { _eq: 8 } }) {
    id
    type_id
    from_id
    to_id
  }
}
{
  "data": {
    "links": [
      {
        "id": 8,
        "type_id": 1,
        "from_id": 7,
        "to_id": 6
      }
    ]
  }
}

Link как узел (node)

В связях можно не заполнять from_id и to_id. Такие связи играют роль node, узла, точки, предмета отношений.

{
  links(where: { id: { _eq: 28 } }) {
    id
    type_id
    from_id
    to_id
  }
}
{
  "data": {
    "links": [
      {
        "id": 28,
        "type_id": 6,
        "from_id": 0,
        "to_id": 0
      }
    ]
  }
}

Link как связь

Если from_id и to_id заполнены, связь играет роль отношения между узлами или связями. Она может ответить на вопрос как они связаны используя type_id. Некоторое окружение узла связями может ответить на множество вопросов о его смысле, отношениях, предназначении, значении и состоянии.

{
  links(where: { id: { _in: [6,7,8] } }) {
    id
    type_id
    from_id
    to_id
  }
}
{
  "data": {
    "links": [
      {
        "id": 6,
        "type_id": 1,
        "from_id": 0,
        "to_id": 0
      },
      {
        "id": 7,
        "type_id": 1,
        "from_id": 0,
        "to_id": 0
      },
      {
        "id": 8,
        "type_id": 1,
        "from_id": 7,
        "to_id": 6
      }
    ]
  }
}

Единый GraphQL API

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

Очевидно, что там доступна структура самой связи, а также из связи можно перейти к другим связям как по ее ссылкам (from_id и to_id), так и по обратным ссылкам от других связей (исходящих и входящих). Например, можно перейти на все связи которые ссылаются на данную связь по from_id (исходят из неё) с помощью relationship out, или по to_id (входят в неё) с помощью relationship in.

{
  links(where: { id: { _eq: 8 } }) {
    id
    type {
      id
    }
    from {
      id
      type_id
      from_id
      to_id
      out {
        id
        type_id
        from_id
        to_id
      }
    }
    to {
      id
      type_id
      from_id
      to_id
      in {
        id
        type_id
        from_id
        to_id
      }
    }
  }
}
{
  "data": {
    "links": [
      {
        "id": 8,
        "type": {
          "id": 1
        },
        "from": {
          "id": 7,
          "type_id": 1,
          "from_id": 0,
          "to_id": 0,
          "out": [
            {
              "id": 8,
              "type_id": 1,
              "from_id": 7,
              "to_id": 6
            }
          ]
        },
        "to": {
          "id": 6,
          "type_id": 1,
          "from_id": 0,
          "to_id": 0,
          "in": [
            {
              "id": 8,
              "type_id": 1,
              "from_id": 7,
              "to_id": 6
            },
            {
              "id": 13,
              "type_id": 1,
              "from_id": 6,
              "to_id": 6
            },
            {
              "id": 24,
              "type_id": 22,
              "from_id": 23,
              "to_id": 6
            }
          ]
        }
      }
    ]
  }
}

SQL-like queries

Мы можем не только получать данные по описанным выше структурам. Мы можем применять sql-like where предикат для выполнения разнообразных сложных фильтраций на каждом уровне.

{
  links(where: {id: { _eq: 6 } }) {
    id
    type { id }
    from { id }
    to { id }
    out(where: { from_id: { _eq: 6} }) {
      id
      type_id
      from_id
      to_id
    }
    in(where: { type_id: { _eq: 1} }, limit: 1) {
      id
      type_id
      from_id
      to_id
    }
  }
}
{
  "data": {
    "links": [
      {
        "id": 6,
        "type": {
          "id": 1
        },
        "from": null,
        "to": null,
        "out": [
          {
            "id": 13,
            "type_id": 1,
            "from_id": 6,
            "to_id": 6
          }
        ],
        "in": [
          {
            "id": 8,
            "type_id": 1,
            "from_id": 7,
            "to_id": 6
          }
        ]
      }
    ]
  }
}

Subscriptions

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

subscription {
  links(where: { from_id: { _eq: 7 }, to_id: { _eq: 6 } }) {
    id
    type_id
  }
}

Поддержите ассоциативные технологии

Мы создаем среду разработки (коробочную CE/EE версию и SaaS версию), позволяющую использовать ассоциативный подход для решения ваших бизнес задач, нуждающихся в хранении данных, с возможностью адаптации под любые изменения бизнеса. Мы создадим распределённую сеть серверных кластеров в едином ассоциативном пространстве, в том числе чтобы не думать о региональном законодательстве, создавая проект. Мы создаём культуру публикации повторно используемых моделей данных с их поведением.

Присоединяйтесь к нашему сообществу в Discord. Подпишитесь на ранний доступ в нашем Waitlist или поддержите нас на Patreon. http://deep.foundation/

На нашем сайте можно найти ссылки на черновики будущих статей находящихся в разработке, ссылки на исходники кода, планы по project и product менеджменту и invest презентации.

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


  1. lair
    06.09.2021 00:18
    +2

    У меня, простите, идиотский вопрос.


    Как с помощью этих "квантов информации" выразить простой факт: баланс на счету мистера Икс на 09.09.1909 составлял 800 франков?


    1. IvanSGlazunov Автор
      06.09.2021 23:17
      +1

      Спасибо @lair за внимание к нам и статьям. Очень приятно видеть такие вдумчивые вопросы и интерес.

      Мы еще не релизнули packer/unpacker который бы позволял пушить ассоциативные модели с правами и поведением в пакет для пакетных мендежеров.

      Сделал за 10 минут пример. Он работоспособен в демке.

      Но если представлять как это могло бы быть не придираясь к словам: это были бы типы add, at, time в дополнение к тем что есть в демке. Я бы их добавил в mp нужный для запросов (это в следующей статье как раз). Пунктирные линии - визуализация materialized path вычисленного в триггере в момент когда mp_include добавил к каждому из новых типов.

      Пример запроса для вычисления баланса. Если эти вычисления нужны очень часто, легко добавить линк sum и с помощью (еще не описанных) Associative handlers на js прямо в триггере в рамках транзакции он будет вычисляться, при добавлении или удалении add.

      {
        number_aggregate(where: {
              link: {
            _by_item: { path_item_id: { _eq: 37 } },
            type_id: { _eq: 31 },
          },
        }) {
            aggregate {
              sum {
                value
              }
          }
        }
      }
      {
        "data": {
          "number_aggregate": {
            "aggregate": {
              "sum": {
                "value": 19
              }
            }
          }
        }
      }

      Поиск всех данных что есть

      Вставка этой демо структуры в демке. Пока так. В разработке обращение к типам не по id а по package/typename (если сильно упрощать).

      Прошу учитывать:

      • сейчас нет некоторых механик, и временный побочный эффект - необходимость указания id

      • пример перестанет работать уже в течении месяца-двух, так как 31, 32... id будут заполнены и вероятно появиться reserved id механика и механика обращения к линкам по package/linkName.

      mutation {
        insert_types: insert_links(objects: [
          { id: 31, type_id: 1, from_id: 0, to_id: 0 },
          { id: 32, type_id: 1, from_id: 0, to_id: 0 },
          { id: 33, type_id: 1, from_id: 31, to_id: 32 },
        ]) {
          returning { id }
        }
        insert_type_names: insert_string(objects: [
          { link_id: 31, value: "add" },
          { link_id: 32, value: "time" },
          { link_id: 33, value: "at" },
        ]) {
          returning { id }
        }
        insert_mp_include: insert_links(objects: [
          { id: 34, type_id: 22, from_id: 23, to_id: 31 },
          { id: 35, type_id: 22, from_id: 23, to_id: 32 },
          { id: 36, type_id: 22, from_id: 23, to_id: 33 },
        ]) {
          returning { id }
        }
        insert_demo_user: insert_links(objects: [
          { id: 37, type_id: 14, from_id: 0, to_id: 0 },
      
          { id: 38, type_id: 31, from_id: 0, to_id: 0 },
          { id: 39, type_id: 31, from_id: 0, to_id: 0 },
          { id: 40, type_id: 31, from_id: 0, to_id: 0 },
      
          { id: 41, type_id: 32, from_id: 0, to_id: 0 },
          { id: 42, type_id: 32, from_id: 0, to_id: 0 },
          { id: 43, type_id: 32, from_id: 0, to_id: 0 },
      
          { id: 44, type_id: 13, from_id: 37, to_id: 38 },
          { id: 45, type_id: 13, from_id: 37, to_id: 39 },
          { id: 46, type_id: 13, from_id: 37, to_id: 39 },
      
          { id: 47, type_id: 33, from_id: 38, to_id: 41 },
          { id: 48, type_id: 33, from_id: 39, to_id: 42 },
          { id: 49, type_id: 33, from_id: 40, to_id: 43 },
        ]) {
          returning { id }
        }
        insert_demo_values:insert_number(objects: [
          { link_id: 38, value: 12 },
          { link_id: 39, value: 7 },
          { link_id: 40, value: 15 },
        ]) {
          returning { id }
        }
      }

      Получение всего о юзере

      {
        links: links(where: {
              _by_item: { path_item_id: { _eq: 37 } },
        }) {
          id
          type_id
          number {
            id
            value
          }
        }
      }
      {
        "data": {
          "links": [
            {
              "id": 37,
              "type_id": 14,
              "number": null
            },
            {
              "id": 38,
              "type_id": 31,
              "number": {
                "id": 1,
                "value": 12
              }
            },
            {
              "id": 39,
              "type_id": 31,
              "number": {
                "id": 2,
                "value": 7
              }
            },
            {
              "id": 41,
              "type_id": 32,
              "number": null
            },
            {
              "id": 42,
              "type_id": 32,
              "number": null
            },
            {
              "id": 44,
              "type_id": 13,
              "number": null
            },
            {
              "id": 45,
              "type_id": 13,
              "number": null
            },
            {
              "id": 46,
              "type_id": 13,
              "number": null
            },
            {
              "id": 47,
              "type_id": 33,
              "number": null
            },
            {
              "id": 48,
              "type_id": 33,
              "number": null
            }
          ]
        }
      }

      GUI будет намного приятнее, мы работаем над собственным вьювером, интуитивным управлением, и еще очень много чем. То что доступно сейчас - временно решение для наглядности демонстрации.

      Мы будем подерживать кастомные таблички вроде этих string/number, поставляемые вместе с ассоциативными моделями. Единственное ограничение - они не должны использоваться для ссылочны данных, только для хранения данных.


      1. lair
        06.09.2021 23:20

        А что такое number в этой вашей записи? В статье ничего такого нет.


        1. IvanSGlazunov Автор
          06.09.2021 23:23
          +1

          В статье и не должно быть. Статьи постепенно говорят о разных подходах, и концепциях. Все разом будет слишком много. Я учту что вы бы хотели большую насыщенность)


          1. lair
            06.09.2021 23:24

            Так что же это такое?


            1. IvanSGlazunov Автор
              06.09.2021 23:30

              В конце моего первого ответа я сразу дал ответ на этот вопрос.

              Продублирую специально для вас.

              Мы будем подерживать кастомные таблички вроде этих string/number, поставляемые вместе с ассоциативными моделями. Единственное ограничение - они не должны использоваться для ссылочны данных, только для хранения данных.


              1. lair
                06.09.2021 23:33

                Я не понимаю, что такое "кастомные таблички". Слово "кастомные" наводит меня на мысль, что это что-то, что не является частью основной модели — а это, в свою очередь, наводит на мысль, что основная модель не способна к выражению нужных мне фактов.


                Ну и да, сравнение вашего рисунка сверху с записью:


                {
                  ts: 1909-09-09
                  holder: mr_x,
                  balance: 800,
                  currency: CHF
                }

                явно не в пользу первого, уж простите.


  1. nin-jin
    06.09.2021 08:34

    Вы описали принцип работы, наверно, любой графовой субд. Чем ваша реализация отличается? Ну, кроме крайней неэффективности запросов из-за отсутствия "легковесных" рёбер (прямые ссылки между узлами) и необходимости приджойнивать таблицы со значениями (про которые в статье вы почему-то забыли упомянуть).