Толстая модель. История разработки ORM
Upcoming SlideShare
Loading in...5
×

Like this? Share it with your network

Share

Толстая модель. История разработки ORM

  • 2,062 views
Uploaded on

Доклад на ZFConf2011

Доклад на ZFConf2011

More in: Technology
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Be the first to comment
    Be the first to like this
No Downloads

Views

Total Views
2,062
On Slideshare
2,054
From Embeds
8
Number of Embeds
4

Actions

Shares
Downloads
15
Comments
0
Likes
0

Embeds 8

http://www.linkedin.com 5
http://twitter.com 1
http://a0.twimg.com 1
https://www.linkedin.com 1

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
    No notes for slide

Transcript

  • 1. Толстая модель История разработки ORMШамин МихаилGeometria.ruВедущий разработчик
  • 2. Geometria.ru• Главный фотохроникер страны• 8 лет на рынке• Представительство в 150 городах России, СНГ и Прибалтики• Ежедневно 80 000 пользователей / 600 000 просмотров• В понедельник 110 000 / 1 000 000• Более 500 000 репортажей• 15 000 000 фотографий• 800 000 зарегистрированных пользователей
  • 3. Почему понадобился свой ORMБыло• Наследство в виде залежей кода-лапши• Практически вся бизнес-логика в контроллерах• Вплоть до формирования select ов!• Некоторые экшены размером в 200 строк!• В роли модели - Zend_Db_Table
  • 4. Почему понадобился свой ORMСтало• Стали использовать NoSQL решения, такие как Redis и Mongo• Понадобилось решение, готовое работать с любым хранилищем, а не только SQL• Есть ли что-то на рынке?• Doctrine2 в alphа, еще сырая - страшно.• Что делать?• Пишем свой велосипед!
  • 5. Выбор дизайна Открываем книгу Мартина Фаулера (Martin Fowler)"Шаблоны корпоративных приложений" ("Patterns of Enterprise Application Architecture")
  • 6. Выбор дизайна И находим то что нужно. Domain Model или Модель предметной области
  • 7. Поля модели. Как задавать ?• В Zend_Db_Table_Row поля не прописаны явно, а берутся из схемы таблицы БД• В Doctrine2 через задание private/protected свойств и генерацию getter/setter методов.
  • 8. Поля модели. Решение:Использовать DocBlockПрофиты:• Готовый шаблон для типов данных• Сразу в аннотации класса видны все поля модели• Автокомплит в IDE (Zend Studio, PhpStorm, NetBeans)• Быстрое создание классов
  • 9. Zend_Reflection для генерации полей/** * @property integer $id * @property string $title * @property string $body * @property boolean $hidden * @property integer $date **/class Model_Post extends Geometria_Model{}
  • 10. Zend_Reflection для генерации полейПосле $post = new Model_Post();свойство $_data будет выглядеть следующим образом:class Model_Post extends Geometria_Model{ protected $_data = array( id => null, title => null, body => null, hidden => null, date => null, );}
  • 11. Доступ к полям• Внешний доступ к полям обеспечивается через магические методы __get() и __set()• Можно реализовать методы get<поле> и set<поле>, чтобы изменить логику установки/получения значения поля.
  • 12. /** * ... * @property integer $date Unix timestamp */class Model_Post extends Geometria_Model...public function setDate($value){ if ($value instanceof Zend_Date) { $value = $value->getTimestamp(); } $this->_data[date] = $value;}
  • 13. Установка значения по умолчаниюclass Model_Post extends Geometria_Model...public function getDate($value){ if (null === $this->_data[date]) { $this->_data[date] = time(); } return $this->_data[date];}
  • 14. Как хранить модель?Используем DataMapper• Маппер знает все о модели и о том, как и где еѐ хранить.• Модель ничего не знает о хранилище.• Логика домена отделена от persist логики• Можно менять структуру бд или даже сменить хранилище, не меняя логику модели, всего лишь изменив маппер.• Маппер выполняет CRUD операции• Можно использовать любое хранилище: MySQL, Mongo, Redis, Config file, RESTApi и др.
  • 15. Интерфейс маппераinterface Geometria_Model_Mapper_Interface{ public function create(Geometria_Model $model); public function update(Geometria_Model $model); public function delete(Geometria_Model $model); public function fetchOne($cond, $sort); public function fetchAll($cond, $sort, $limit, $skip); public function getCount($cond);}
  • 16. Работа с моделью$post = new Model_Post();$post->title = hello world!;$post->body = foo bar;$postMapper = new Model_Post_Mapper();$postMapper->create($post);echo $post->id; // 1 маппер сам проставил в моделиid
  • 17. Выборки• Условие $cond - простой массив имя поля => значение• Сортировка $sort - тоже просто массив имя поля => (bool) направление сортировки• Для более сложных выборок пишем отдельный методВыбрать 10 скрытых постов, начиная с самых новых$mapper->fetchAll( array(hidden => true), array(date => false), 10);
  • 18. Делаем ActiveRecordРассказываем модели, что у нее есть маппер.• Делаем статический метод getMapper() который из специального контейнера Geometria_Model_Mapper_Manager достает нужный ей маппер• Делаем у модели методы create(), update(), delete()public function create(){ return self::getMapper()->create($this);}
  • 19. Теперь создание модели выглядит так:$post = new Model_Post();$post->title = hello world!;$post->body = foo bar;$post->create();echo $post->id; // 1А пост можно получить в одну строчку:$post = Model_Post::getMapper()->fetchOne( array(id => 1));или так$post = Model_Post::getMapper()->fetch(1);
  • 20. Что вернет fetchAll()? Коллекцию!• аналог Zend_Db_Table_Rowset• Паттерн Record Set• Позволяет выполнять массовые действия с набором моделейinterface Geometria_Model_Collection_Interfaceextends Iterator, Countable{ public function append(Geometria_Model $model); public function prepend(Geometria_Model $model); public function populate(array $data); public function clear(); public function toArray();}
  • 21. Нужен Paginator?class Geometria_Paginator_Adapter_Mapper implements Zend_Paginator_Adapter_Interface{ public function __construct( Geometria_Model_Mapper_Interface $mapper, array $cond = null, array $sort = null ) { $this->_mapper = $mapper; $this->_cond = $cond; $this->_sort = $sort; }}
  • 22. Нужен Paginator?class Geometria_Paginator_Adapter_Mapper ... public function getItems($offset, $limit) { return $this->_mapper->fetchAll( $this->_cond, $this->_sort, $limit, $offset ); } public function count() { return $this->_mapper->getCount( $this->_cond, $this->_sort ); }
  • 23. Хотим кешировать, логировать и тд.• Используем декоратор для маппера• Декортатор - это матрешка: в конструктор первого декоратора передаем маппер, в конструктор второго передаем первый декоратор и так далее• Декоратор перехватывает "интересующие" его методы, и изменяет результат на ему угодный, остальные методы просто пропускает дальше.• При инициализации маппера маппер-менеджер спрашивает у маппера, хочет ли он задекорироваться и оборачивает во все указанные декоратры
  • 24. Примеры декораторовCache• fetchOne(), fetchAll() - на основании переданного условия берет данные из кеша, или же просит маппер выполнить запрос и кеширует его результат.• create(), update(), delete() - сбрасывает соответсвующий кеш.Profiler• Декоратор пропускает все запросы через себя, записывая в лог время выполнения запроса.Identity Map • Кеширует результаты в памяти, чтобы маппер не выполнял одинаковые запросы дважды
  • 25. ОтношенияРаз уж строим ORM, то должны быть отношения междусущностями.• Отношения так же, как и поля, задаются в DocBlock• Параметры описываются в спец формате• При создании модели, создаются объекты-менеджеры отношений• При обращении к полю, ссылающемуся на внешнюю сущность, объект-менеджер отношения делает запрос к внешнему мапперу и возвращает полученый объект.
  • 26. Пример работы с отношениями/** * ... * @property integer $authorId * @property Model_Author $author[relation=belongsTo;localKey=authorId] */class Model_Post extends Geometria_Model{...}$post = Model_Post::getMapper()->fetchOne(array(authorId=> 5));$author = $post->author; // Model_AuthorМенеджер отношения в данном случае выполнит запросModel_Author::getMapper()->fetchOne(array(id => 5));
  • 27. Виды отношений• hasOne - one-to-one отношение• belongsTo - тоже что и hasOne, но требует обязательного наличия объекта• hasMany - one-to-many отношение
  • 28. Полиморфические связиОбеспечивают связь с несколькими видами сущностей, тона какой тип сущности стоит ссылка определяет параметрownerType, в то время как параметр ownerTypeIdопределяет id сущности./** * @property string $ownerType * @property integer $ownerId * @property Model_User|Model_Post $owner[relation=polymorhic; localKey=ownerId;localTypeKey=ownerType] */class Model_Comment extends Geometria_Model{..}
  • 29. Тонкости отношений$posts = Model_Post::getMapper()->fetchAll();foreach ($posts as $post) { echo $post->title . by . $post->author;}Автор запрашивается при каждой итерации.Если у нас 10 постов, значит мы сделаем 1 запрос наполучение постов и 10 запросов на получение авторов.Итого 11 запросов - плохо!
  • 30. Тонкости отношенийРешение:$posts->fetchRelations(author);Просим relation-manager получить всех авторов однимзапросом и проставить во всех постах коллекции.Итого: 2 запроса, независимо от количества постов.
  • 31. Тонкости отношенийА если у автора есть связь с картинкой-аватаркой?$posts->fetchRelations(author, picture);Что означает, что перед тем, как "распихать" всех авторовпо постам, у полученной коллекции авторов будетвызван метод:$authors->fetchRelations(picture);
  • 32. Каскадные операцииУ отношений можно прописать действие, которое будетвыполняться при удалении модели onDelete: • CASCADE - удалить все связанные зависимые модели • SET NULL - очистить значения внешних ключейЭто позволяет сохранять целостность связей внутринашей системы.
  • 33. Жизнь без JoinовКак сделать выборку постов, написанных женщинами, еслипосты используют одно хранилище, а авторы другое, и нетвозможности сделать join?Использовать sphinx.• Создаем индекс в сфинксе для такого рода выборки.• Индексируем данные.• Создаем sphinx декоратор• Декоратор ищет id документов, удовлетворяющих поисковому запросу. И по этом списку id маппер возвращает коллекцию с результатом.
  • 34. Что дало внедрение ORM• Существенное ускорение разработки• Время вхождения в чужой код значительно уменьшилось• Использование Domain Driven Design позволяет говорить на языке предметной области, что повышает читаемость кода• Логика приложения вынесена в отдельный слой сервисов. Что позволяет использовать ее не только в MVC, но и в CLI, например.• Размер экшенов в контроллерах сократился до 10 строк.
  • 35. Будет ли open source?Будет, но позже )
  • 36. СпасибоTwitter: @munk13МойКруг: http://munkie.moikrug.ru