Why do we need ORM? The difference between ActiveRecord and DataMapper patterns. The practical appliance of Iterative deepening depth-first search algo for topological sort of ORM relations.
Review of Cycle ORM and it features.
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
11. Концептуальные различия
- Метод save() (на самом деле нет).
- Доступ к данным через ::find() vs Repository (тоже нет).
- Представление данных и “не думай о базе данных”
- Порядок реализации и изоляция доменного слоя
11
12. Концептуальные различия
- Метод save() (на самом деле нет).
- Доступ к данным через ::find() vs Repository (тоже нет).
- Представление данных и “не думай о базе данных”
- Порядок реализации и изоляция доменного слоя
- Конкретные особенности реализации*
12
13. Active Record
13
- Не требует дополнительной настройки
- Необходимо помнить схему базы данных
- Просто выучить
- Легко написать
- Сложно поддерживать
- Всегда есть базовый класс
14. Data Mapper
14
- Требует дополнительной маппинг схемы
- Может сам сгенерировать схему базы данных
- Трудно выучить
- Сложно написать
- Сложно поддерживать
- Базовый класс не обязателен*
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
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
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 тысяч строк кода :(
42. 42
DFS + ❤ + BFS = IDDFS
- Итеративный поиск в глубину
- Преобразуем граф объектов в граф
операций
- Ищем первую операцию без
зависимостей по цепочке
- Объединяем поиск и persist в один
процесс
43. 43
Реализация в коде - Команда
- Вершина - команда
- Ребро - зависимость
- Агрегирующие команды
44. 44
Реализация в коде - Зависимость
- Проброс значения по цепочке
- Зависимость - ожидание
значения
- Одновременно уточняем
финальные данные
45. 45
Простой пример
- Команда зависит от значения
другой команды
- “Обещаем” значение через
зависимость
- Пробрасываем значения по
цепочке при выполнении
команды
46. 46
Реализация в коде - Транзакция
- Команда
- Узлы - агрегирующие команды
- Новые данные гидрируют модели
после окончания транзакции
54. 54
Что в итоге
- Persist графов любой сложности
- Сортировка и транзакция один процесс
- Связи между любыми ключами, любыми базами
- Persist изолирован от доменных моделей и entity map
56. 56
Поддержка нескольких баз данных
- MySQL, SQLite, MariaDB, SQLServer
12+, Postgres 9.2+
- Логическая изоляция баз данных
- Общая транзакция
- Read/write соединения
- Авто-переподключение
- Авто-миграции
- Под капотом PDO
57. 57
Загрузка связанных данных
- Загрузка данных любой
вложенности
- Несколько стратегий выборки
- Сложные запросы и фильтрация
58. 58
Построение сложных запросов
- Поддержка вложенных
запросов
- Запросы на чистом SQL
- Использование данных
нескольких связей
одновременно
59. 59
Описываем схему вручную
- Маппинг схема описывает все
поведение
- Можно изменять в runtime
- Можно описывать используя
декларативный билдер схем
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
- Без или с кодо-генерацией
- Описываем связи, генерируем
запросы
- Смешиваем с обычными моделями
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