Cycle ORM
и графы
2
Антон Титов (a.k.a. “Wolfy-J”)
Технический директор и соучредитель Spiral Scout
https://github.com/wolfy-j https://habr.com/ru/users/lachezis/ https://twitter.com/lachezis
Коммерческая разработка последние 13 лет
Основной стек: PHP7, Golang
О чем я буду рассказывать
1. Зачем нужны ORM?
2. ActiveRecord vs DataMapper
3. Что необходимо для разработки ORM?
4. Реализация persist слоя в гифках
5. Обзор Cycle ORM
3
Зачем нужны ORM?
4
Зачем нужны ORM?
- Упрощают общение с источниками данных
5
Зачем нужны ORM?
- Упрощают общение с источниками данных
- Замедляют приложение
6
Зачем нужны ORM?
- Упрощают общение с источниками данных
- Замедляют приложение
- Изолируют доменный слой
7
Зачем нужны ORM?
- Упрощают общение с источниками данных
- Замедляют приложение
- Изолируют доменный слой
- Упрощают тестирование
8
ActiveRecord vs DataMapper
9
Концептуальные различия
- Метод save() (на самом деле нет).
- Доступ к данным через ::find() vs Repository (тоже нет).
10
Концептуальные различия
- Метод save() (на самом деле нет).
- Доступ к данным через ::find() vs Repository (тоже нет).
- Представление данных и “не думай о базе данных”
- Порядок реализации и изоляция доменного слоя
11
Концептуальные различия
- Метод save() (на самом деле нет).
- Доступ к данным через ::find() vs Repository (тоже нет).
- Представление данных и “не думай о базе данных”
- Порядок реализации и изоляция доменного слоя
- Конкретные особенности реализации*
12
Active Record
13
- Не требует дополнительной настройки
- Необходимо помнить схему базы данных
- Просто выучить
- Легко написать
- Сложно поддерживать
- Всегда есть базовый класс
Data Mapper
14
- Требует дополнительной маппинг схемы
- Может сам сгенерировать схему базы данных
- Трудно выучить
- Сложно написать
- Сложно поддерживать
- Базовый класс не обязателен*
Где какой использовать?
15
- ActiveRecord - для мелких проектов.
Где какой использовать?
16
- ActiveRecord - для мелких проектов.
- DataMapper - для сложный проектов.
Где какой использовать?
17
- ActiveRecord - для мелких проектов.
- DataMapper - для сложный проектов.
- ...
- Вообще не использовать ORM!
“А как же тесты?”
18
Тестируем Data Mapper
19
- 2 мока:
- EntityManager
- UserRepository
- Unit тест
- Без запросов в базу
- Доверяем ORM
Тестируем Active Record
20
- Unit тест
- Мокаем Record?
- Или Repository?
- onSave события?
- Acceptance тест
- SQLite в памяти
- Dev база
- Не доверяем ORM
Практические различия
- Различные принципы изоляции доменного кода
- Data Mapper проще тестировать чем Active Record
- Active Record следует за схемой таблиц
- Data Mapper следует за схемой маппинга
- Выбор Active Record ORM гораздо шире (написать AR проще)
21
22
Active Record Data MapperHybrid
23
Active Record Data MapperHybrid
24
Active Record Data MapperHybrid
Active Record
25
Data MapperHybrid
Что необходимо для ORM?
2
6
Что необходимо для ORM?
- Database Abstraction Layer (DBAL) - интроспекция, рефлексия
- Query Builder, Query DSL, любой другой механизм выборки
27
Что необходимо для ORM?
- Database Abstraction Layer (DBAL) - интроспекция, рефлексия
- Query Builder, Query DSL, любой другой механизм выборки
- EntityMap/Heap для хранения объектов и их связей
- Mapping schema
28
Что необходимо для ORM?
- Database Abstraction Layer (DBAL) - интроспекция, рефлексия
- Query Builder, Query DSL, любой другой механизм выборки
- EntityMap/Heap для хранения объектов и их связей
- Mapping schema
- Persist слой
- Различные типы связей
29
Что необходимо для ORM?
- Database Abstraction Layer (DBAL) - интроспекция, рефлексия
- Query Builder, Query DSL, любой другой механизм выборки
- EntityMap/Heap для хранения объектов и их связей
- Mapping schema
- Persist слой
- Различные типы связей
- Тесты... очень много тестов
30
Persist и графы зависимостей
3
1
Требование к persist слою
- Топологическая сортировка объектов и их связей
- Оптимальное число запросов
32
Требование к persist слою
- Топологическая сортировка объектов и их связей
- Оптимальное число запросов
- Переносимые транзакции и легкое тестирование
- Корректная обработка ошибок
33
Требование к persist слою
- Топологическая сортировка объектов и их связей
- Оптимальное число запросов
- Переносимые транзакции и легкое тестирование
- Корректная обработка ошибок
- Отсутствие утечек памяти (RoadRunner)
- Возможность работы с несколькими базами одновременно
34
Требование к persist слою
- Топологическая сортировка объектов и их связей
- Оптимальное число запросов
- Переносимые транзакции и легкое тестирование
- Корректная обработка ошибок
- Отсутствие утечек памяти (RoadRunner)
- Возможность работы с несколькими базами одновременно
- Динамический маппинг модели к нескольким таблицам (hi, Drupal!)
35
Граф зависимостей
36
- Ориентированный граф
- Вершины - доменные сущности
- Ребра - связи между сущностями
- Типы графов в ORM:
- Направленный ациклический
- Направленный граф-цикл
Граф с циклами
37
- Ориентированный граф
- Вершины - доменные сущности
- Ребра - связи между сущностями
- Типы графов в ORM:
- Направленный ациклический
- Направленный граф-цикл
38
Топологическая сортировка в Doctrine
- InternalCommitOrderCalculator
- Поиск в глубину (DFS)
- Сортировка предшествует сохранению
- Сортируем ClassMetadata
- Вызываем Persister в порядке
сортировки
- Выполняем расчеты внутри EntityMap
39
Persist в Doctrine
- computeChangeSets
- assertThatThereAreNoUnintentionallyNonPersistedAssociations
- orphanRemovals
- getCommitOrder
- collectionDeletions, entityInsertions, entityUpdates, extraUpdates,
collectionUpdates, entityDeletions
- события и хуки
- Около 4 тысяч строк кода :(
40
Можно ли проще?
41
Поиск в глубину (DFS) vs поиск в ширину (BFS)
42
DFS + ❤ + BFS = IDDFS
- Итеративный поиск в глубину
- Преобразуем граф объектов в граф
операций
- Ищем первую операцию без
зависимостей по цепочке
- Объединяем поиск и persist в один
процесс
43
Реализация в коде - Команда
- Вершина - команда
- Ребро - зависимость
- Агрегирующие команды
44
Реализация в коде - Зависимость
- Проброс значения по цепочке
- Зависимость - ожидание
значения
- Одновременно уточняем
финальные данные
45
Простой пример
- Команда зависит от значения
другой команды
- “Обещаем” значение через
зависимость
- Пробрасываем значения по
цепочке при выполнении
команды
46
Реализация в коде - Транзакция
- Команда
- Узлы - агрегирующие команды
- Новые данные гидрируют модели
после окончания транзакции
47
Реальный пример
48
Реальный пример
49
Зависимость от нескольких родителей
50
Зависимость от нескольких родителей
51
Внезапно... циклы!
52
Внезапно... циклы!
53
Преобразуем в граф команд
54
Что в итоге
- Persist графов любой сложности
- Сортировка и транзакция один процесс
- Связи между любыми ключами, любыми базами
- Persist изолирован от доменных моделей и entity map
Cycle ORM
5
5
56
Поддержка нескольких баз данных
- MySQL, SQLite, MariaDB, SQLServer
12+, Postgres 9.2+
- Логическая изоляция баз данных
- Общая транзакция
- Read/write соединения
- Авто-переподключение
- Авто-миграции
- Под капотом PDO
57
Загрузка связанных данных
- Загрузка данных любой
вложенности
- Несколько стратегий выборки
- Сложные запросы и фильтрация
58
Построение сложных запросов
- Поддержка вложенных
запросов
- Запросы на чистом SQL
- Использование данных
нескольких связей
одновременно
59
Описываем схему вручную
- Маппинг схема описывает все
поведение
- Можно изменять в runtime
- Можно описывать используя
декларативный билдер схем
60
Либо используя аннотации
- На основе Doctrine Annotation
- Есть autocomplete
61
Локальные транзакции
- Несколько UoW в одном приложении
- Абстракция с тремя методами
- Каскадное сохранение
- Простое тестирование
62
Lazy-loaded Embeddings
- Загружаем только нужные части
объекта
- При необходимости, используем
lazy-load
- Embeddings ведут себя как обычные
связи
- Выбираем embedding отдельно от
родителя
63
Динамическая схема сущностей (hi Drupal nodes!)
- Описываем схему в runtime
- Конфигурируем базу данных
используя декларативные схемы
- Храним схему в JSON прямо в базе
64
Динамическая схема сущностей (hi, Drupal nodes!)
- Описываем маппинг схему в runtime
- Без или с кодо-генерацией
- Описываем связи, генерируем
запросы
- Смешиваем с обычными моделями
65
Динамическая схема сущностей (hi Drupal nodes!)
- Описываем маппинг схему в runtime
- Без или с кодо-генерацией
- Описываем связи, генерируем
запросы
- Смешиваем с обычными моделями
66
Производительность
67
CYCLE-ORM.DEV
- 4-е поколение ORM
- В 8 раз меньше Doctrine 2
- 94% покрытие тестами, 92% MSI
- Spiral Framework и Yii 3
- Официальная поддержка Spiral Scout
68
Недостатки
- Новая ORM
- Небольшое комьюнити
- Отсутствуют коробочные интеграции в большинство фреймворков
- Есть большой запас для оптимизаций
- Пока нет поддержки композитных ключей (но мы работаем над этим!)
69
Спасибо за внимание
Ссылки:
- https://github.com/doctrine/orm/blob/master/lib/Doctrine/ORM/Internal/CommitOrderCalculator.php
- https://www.doctrine-project.org/projects/doctrine-orm/en/2.6/reference/limitations-and-known-issues.html
- https://en.wikipedia.org/wiki/Depth-first_search
- https://en.wikipedia.org/wiki/Breadth-first_search
- https://en.wikipedia.org/wiki/Iterative_deepening_depth-first_search
- https://www.geeksforgeeks.org/iterative-depth-first-traversal/
- https://cycle-orm.dev/docs
- https://github.com/cycle/orm
- https://github.com/cycle/docs/issues/3 (сравнение с Doctrine 2 и Eloquent)
- https://github.com/yiisoft/yii-cycle

Anton Tsitou "Cycle ORM and Graphs"

  • 1.
  • 2.
    2 Антон Титов (a.k.a.“Wolfy-J”) Технический директор и соучредитель Spiral Scout https://github.com/wolfy-j https://habr.com/ru/users/lachezis/ https://twitter.com/lachezis Коммерческая разработка последние 13 лет Основной стек: PHP7, Golang
  • 3.
    О чем ябуду рассказывать 1. Зачем нужны ORM? 2. ActiveRecord vs DataMapper 3. Что необходимо для разработки ORM? 4. Реализация persist слоя в гифках 5. Обзор Cycle ORM 3
  • 4.
  • 5.
    Зачем нужны ORM? -Упрощают общение с источниками данных 5
  • 6.
    Зачем нужны ORM? -Упрощают общение с источниками данных - Замедляют приложение 6
  • 7.
    Зачем нужны ORM? -Упрощают общение с источниками данных - Замедляют приложение - Изолируют доменный слой 7
  • 8.
    Зачем нужны ORM? -Упрощают общение с источниками данных - Замедляют приложение - Изолируют доменный слой - Упрощают тестирование 8
  • 9.
  • 10.
    Концептуальные различия - Методsave() (на самом деле нет). - Доступ к данным через ::find() vs Repository (тоже нет). 10
  • 11.
    Концептуальные различия - Методsave() (на самом деле нет). - Доступ к данным через ::find() vs Repository (тоже нет). - Представление данных и “не думай о базе данных” - Порядок реализации и изоляция доменного слоя 11
  • 12.
    Концептуальные различия - Методsave() (на самом деле нет). - Доступ к данным через ::find() vs Repository (тоже нет). - Представление данных и “не думай о базе данных” - Порядок реализации и изоляция доменного слоя - Конкретные особенности реализации* 12
  • 13.
    Active Record 13 - Нетребует дополнительной настройки - Необходимо помнить схему базы данных - Просто выучить - Легко написать - Сложно поддерживать - Всегда есть базовый класс
  • 14.
    Data Mapper 14 - Требуетдополнительной маппинг схемы - Может сам сгенерировать схему базы данных - Трудно выучить - Сложно написать - Сложно поддерживать - Базовый класс не обязателен*
  • 15.
    Где какой использовать? 15 -ActiveRecord - для мелких проектов.
  • 16.
    Где какой использовать? 16 -ActiveRecord - для мелких проектов. - DataMapper - для сложный проектов.
  • 17.
    Где какой использовать? 17 -ActiveRecord - для мелких проектов. - DataMapper - для сложный проектов. - ... - Вообще не использовать ORM!
  • 18.
    “А как жетесты?” 18
  • 19.
    Тестируем Data Mapper 19 -2 мока: - EntityManager - UserRepository - Unit тест - Без запросов в базу - Доверяем ORM
  • 20.
    Тестируем Active Record 20 -Unit тест - Мокаем Record? - Или Repository? - onSave события? - Acceptance тест - SQLite в памяти - Dev база - Не доверяем ORM
  • 21.
    Практические различия - Различныепринципы изоляции доменного кода - Data Mapper проще тестировать чем Active Record - Active Record следует за схемой таблиц - Data Mapper следует за схемой маппинга - Выбор Active Record ORM гораздо шире (написать AR проще) 21
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
    Что необходимо дляORM? - Database Abstraction Layer (DBAL) - интроспекция, рефлексия - Query Builder, Query DSL, любой другой механизм выборки 27
  • 28.
    Что необходимо дляORM? - Database Abstraction Layer (DBAL) - интроспекция, рефлексия - Query Builder, Query DSL, любой другой механизм выборки - EntityMap/Heap для хранения объектов и их связей - Mapping schema 28
  • 29.
    Что необходимо дляORM? - Database Abstraction Layer (DBAL) - интроспекция, рефлексия - Query Builder, Query DSL, любой другой механизм выборки - EntityMap/Heap для хранения объектов и их связей - Mapping schema - Persist слой - Различные типы связей 29
  • 30.
    Что необходимо дляORM? - Database Abstraction Layer (DBAL) - интроспекция, рефлексия - Query Builder, Query DSL, любой другой механизм выборки - EntityMap/Heap для хранения объектов и их связей - Mapping schema - Persist слой - Различные типы связей - Тесты... очень много тестов 30
  • 31.
    Persist и графызависимостей 3 1
  • 32.
    Требование к persistслою - Топологическая сортировка объектов и их связей - Оптимальное число запросов 32
  • 33.
    Требование к persistслою - Топологическая сортировка объектов и их связей - Оптимальное число запросов - Переносимые транзакции и легкое тестирование - Корректная обработка ошибок 33
  • 34.
    Требование к persistслою - Топологическая сортировка объектов и их связей - Оптимальное число запросов - Переносимые транзакции и легкое тестирование - Корректная обработка ошибок - Отсутствие утечек памяти (RoadRunner) - Возможность работы с несколькими базами одновременно 34
  • 35.
    Требование к persistслою - Топологическая сортировка объектов и их связей - Оптимальное число запросов - Переносимые транзакции и легкое тестирование - Корректная обработка ошибок - Отсутствие утечек памяти (RoadRunner) - Возможность работы с несколькими базами одновременно - Динамический маппинг модели к нескольким таблицам (hi, Drupal!) 35
  • 36.
    Граф зависимостей 36 - Ориентированныйграф - Вершины - доменные сущности - Ребра - связи между сущностями - Типы графов в ORM: - Направленный ациклический - Направленный граф-цикл
  • 37.
    Граф с циклами 37 -Ориентированный граф - Вершины - доменные сущности - Ребра - связи между сущностями - Типы графов в ORM: - Направленный ациклический - Направленный граф-цикл
  • 38.
    38 Топологическая сортировка вDoctrine - InternalCommitOrderCalculator - Поиск в глубину (DFS) - Сортировка предшествует сохранению - Сортируем ClassMetadata - Вызываем Persister в порядке сортировки - Выполняем расчеты внутри EntityMap
  • 39.
    39 Persist в Doctrine -computeChangeSets - assertThatThereAreNoUnintentionallyNonPersistedAssociations - orphanRemovals - getCommitOrder - collectionDeletions, entityInsertions, entityUpdates, extraUpdates, collectionUpdates, entityDeletions - события и хуки - Около 4 тысяч строк кода :(
  • 40.
  • 41.
    41 Поиск в глубину(DFS) vs поиск в ширину (BFS)
  • 42.
    42 DFS + ❤+ BFS = IDDFS - Итеративный поиск в глубину - Преобразуем граф объектов в граф операций - Ищем первую операцию без зависимостей по цепочке - Объединяем поиск и persist в один процесс
  • 43.
    43 Реализация в коде- Команда - Вершина - команда - Ребро - зависимость - Агрегирующие команды
  • 44.
    44 Реализация в коде- Зависимость - Проброс значения по цепочке - Зависимость - ожидание значения - Одновременно уточняем финальные данные
  • 45.
    45 Простой пример - Командазависит от значения другой команды - “Обещаем” значение через зависимость - Пробрасываем значения по цепочке при выполнении команды
  • 46.
    46 Реализация в коде- Транзакция - Команда - Узлы - агрегирующие команды - Новые данные гидрируют модели после окончания транзакции
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
    54 Что в итоге -Persist графов любой сложности - Сортировка и транзакция один процесс - Связи между любыми ключами, любыми базами - Persist изолирован от доменных моделей и entity map
  • 55.
  • 56.
    56 Поддержка нескольких базданных - MySQL, SQLite, MariaDB, SQLServer 12+, Postgres 9.2+ - Логическая изоляция баз данных - Общая транзакция - Read/write соединения - Авто-переподключение - Авто-миграции - Под капотом PDO
  • 57.
    57 Загрузка связанных данных -Загрузка данных любой вложенности - Несколько стратегий выборки - Сложные запросы и фильтрация
  • 58.
    58 Построение сложных запросов -Поддержка вложенных запросов - Запросы на чистом SQL - Использование данных нескольких связей одновременно
  • 59.
    59 Описываем схему вручную -Маппинг схема описывает все поведение - Можно изменять в runtime - Можно описывать используя декларативный билдер схем
  • 60.
    60 Либо используя аннотации -На основе Doctrine Annotation - Есть autocomplete
  • 61.
    61 Локальные транзакции - НесколькоUoW в одном приложении - Абстракция с тремя методами - Каскадное сохранение - Простое тестирование
  • 62.
    62 Lazy-loaded Embeddings - Загружаемтолько нужные части объекта - При необходимости, используем lazy-load - Embeddings ведут себя как обычные связи - Выбираем embedding отдельно от родителя
  • 63.
    63 Динамическая схема сущностей(hi Drupal nodes!) - Описываем схему в runtime - Конфигурируем базу данных используя декларативные схемы - Храним схему в JSON прямо в базе
  • 64.
    64 Динамическая схема сущностей(hi, Drupal nodes!) - Описываем маппинг схему в runtime - Без или с кодо-генерацией - Описываем связи, генерируем запросы - Смешиваем с обычными моделями
  • 65.
    65 Динамическая схема сущностей(hi Drupal nodes!) - Описываем маппинг схему в runtime - Без или с кодо-генерацией - Описываем связи, генерируем запросы - Смешиваем с обычными моделями
  • 66.
  • 67.
    67 CYCLE-ORM.DEV - 4-е поколениеORM - В 8 раз меньше Doctrine 2 - 94% покрытие тестами, 92% MSI - Spiral Framework и Yii 3 - Официальная поддержка Spiral Scout
  • 68.
    68 Недостатки - Новая ORM -Небольшое комьюнити - Отсутствуют коробочные интеграции в большинство фреймворков - Есть большой запас для оптимизаций - Пока нет поддержки композитных ключей (но мы работаем над этим!)
  • 69.
    69 Спасибо за внимание Ссылки: -https://github.com/doctrine/orm/blob/master/lib/Doctrine/ORM/Internal/CommitOrderCalculator.php - https://www.doctrine-project.org/projects/doctrine-orm/en/2.6/reference/limitations-and-known-issues.html - https://en.wikipedia.org/wiki/Depth-first_search - https://en.wikipedia.org/wiki/Breadth-first_search - https://en.wikipedia.org/wiki/Iterative_deepening_depth-first_search - https://www.geeksforgeeks.org/iterative-depth-first-traversal/ - https://cycle-orm.dev/docs - https://github.com/cycle/orm - https://github.com/cycle/docs/issues/3 (сравнение с Doctrine 2 и Eloquent) - https://github.com/yiisoft/yii-cycle