<rmcreative>

RSS

Тонкие и толстые модели

4 апреля 2011

  1. Толстые модели можно охватить модульными тестами. С тонкими придётся писать ещё и функциональные.
  2. Реализацию метода толстой модели легко подменить. Для меня это основной аргумент (хотя часто и я ленюсь). Например, есть у нас модели в системе, использующие Active Record:
class MyThinModel extends CActiveRecord
{}

В большинстве случаев вызывают их в контроллерах как-то так:

$criteria = new CDbCriteria();
$criteria->compare('used', 1);
$models = MyThinModel::model()->findAll($criteria);

А теперь представим, что проект у нас вырос до приличных нагрузок и кэш с оптимизацией запросов не помогают. Что теперь делать? Приходится перекраивать хранилище на какой-нибудь быстрый key-value, например, тот же Redis. Вот только проблема есть… проект очень завязан на SQL-ный Active Record и программист, посмотрев на всё это дело, принимает популярное решение «переделать с нуля». А ведь можно было этого избежать (ну или хотя-бы сгладить эффект), если бы изначально использовались толстые модели:

class MyThinModel extends CActiveRecord
{
    public function getAllUsed()
    {
        $criteria = new CDbCriteria();
        $criteria->compare('used', 1);
        return self::model()->findAll($criteria);
    }
}

Ну и в контроллере:

$models = MyThinModel::model()->getAllUsed();

В этом случае переписать необходимо будет только getAllUsed.

Комментарии RSS

  1. №4287
    Loki
    Loki 04 апр. 2011 г., 19:49:55

    С тех пор как перешел на Yii тоже стараюсь делать толстые модели и тонкие контроллеры =) В принципе MVC и подразумевает такой подход по моему, ибо работа с данными должна быть в модели. Контроллер это лишь извлечение и подготовка к выводу)

  2. №4288
    Ekstazi
    Ekstazi 04 апр. 2011 г., 21:01:06

    Примерно так и делаю, это помогает избежать дублирование кода.

  3. №4289
    xoma
    xoma 04 апр. 2011 г., 21:20:37

    Как в таком случае быть со scopes ? Стараться как можно меньше их использовать?

  4. №4290
    porter
    porter 04 апр. 2011 г., 22:19:19

    Для этого в ZendFramework модели пишутся используя Data Mapper шаблон. Очень удобно получается, что все операции с базой хранятся в файлах Имя_моделиMapper. И тестировать очень просто. Планируется ли в Yii внедрение данного шаблона в Yii? Ибо очень удобно.

  5. №4292
    Sam
    Sam 04 апр. 2011 г., 23:00:10

    porter, внедрять ничего не надо, берите и используйте. Достаточно в Gii модели назвать как mapper.

  6. №4293
    Sasha
    Sasha 05 апр. 2011 г., 0:04:09

    Вот честно, не знал как это все называется. Но делал именно модели толстыми, изначально казалось правильным )

  7. №4294
    Сергей
    Сергей 05 апр. 2011 г., 0:41:46

    В Django так же принято делать. Опять - же, совсем не обязательно делать все функции для манипуляции с данными методами класса, можно закинуть простыми функциями в файл с моделями, если они не завязаны жестко на данные экземпляра класса. Скажем ваш пример ничто не мешает записать a-la:

    function getAllUsed(){
            $criteria = new CDbCriteria();
            $criteria->compare('used', 1);
            return MyThinModel::model()->findAll($criteria);
    }

    Правда в PHP с этим сложнее ибо глобальное пространства имен и все такое...

  8. №4295
    AmdY
    AmdY 05 апр. 2011 г., 1:26:56

    я когда-то в комментариях писал об этом, нельзя использовать query builder в контроллере. но тонкая и толстая модель не сводится к её использованию в контроллере. в контроллере просто нельзя их писать. тонкая - это когда в каждом модуле используется своя модель унаследованная от model_base, толстая - это когда для всех модулей общая и в ней куча методов.

  9. №4296
    ZloY
    ZloY 05 апр. 2011 г., 1:49:27

    непонятно тогда что длать со всякими CPagination и CActiveDataProvider; В каком месте устанавливать текущую страницу и количество страниц?

  10. №4297
    Big_Shark
    Big_Shark 05 апр. 2011 г., 3:28:04

    Я использую некую связку принципов толстой и тонкой модели. В контролере:

    ORM::factory('term_allias')->active(TRUE)->find_all();

    В модели

    function active($value = 1){
        $this->where('active','=',(bool)$value);
        return $this;
    }

    Таким образом я не завязан на имена полей в базе, и API от ORM единственное условие это сохранить функции find() и find_all() дабы не пришлось переписывать контролеры.

  11. №4298
    Евген
    Евген 05 апр. 2011 г., 6:59:18

    Согласен с Zloy. Не совсем ясно тогда как utilize CPagination для передачи данных в толстую модель.

    Сможете подсобить примером такой реализации кто делал?

  12. №4299
    Ekstazi
    Ekstazi 05 апр. 2011 г., 11:27:27

    2 Евген, Я делаю так:

    Модель:

    public function search()
        {
            $criteria=new CDbCriteria(array(
                'with'=>array('accounts','users')
            ));
            if($this->validated('search')){
                $criteria->compare('name',$this->name,true);
                $criteria->compare('country',$this->country,true);
            }
     
            return new CActiveDataProvider(__CLASS__, array(
                'criteria'=>$criteria
            ));
        }

    Контроллер:

    public function actionIndex()
        {
            $model=new Bank('search');
            $this->render('index',array(
                'dataProvider'=>$model->search(),
                'searcher'=>$model
            ));
        }

    Гда validated - вспомогательный метод из поведения прицепленного к модели, что-то типа(в очень упрощеном виде):

    function validated($scenario){
        if($t=isset($_POST[get_class($this->owner)]))
            $this->owner->attributes=$_POST[get_class($this->owner)];
            return $t&&$this->owner->validate($scenario);
        }
    }
  13. №4302
    ZloY
    ZloY 05 апр. 2011 г., 17:26:03

    ну как бы у меня на этот счет идея такая:

    В контроллере

    <?php
    $data = Model::model()->getData() //return CActiveDataProvider
    
    $pagination =  $data->getPagination();
    
    //тут какие то настройки с pagination
    
    $data->setPagination($data);
    ?>
    
    С одной стороны с идеологической точки зрения все верно все поведение настривается в контроллере, но setPagination и getPaginaiton мне как то не очень нравится.

    А толстые модели это определенно хорошо обязательно к использованию, повторное использование кода возрастает.

  14. №4303
    8bit
    8bit 05 апр. 2011 г., 23:56:12

    Также использую толстые модели, где находится вся логика работы с БД. При этом большая часть методов модели - статические, а вместо AR использую непосредственно PDO. AR в основном остается только для относительно редких и рутинных операций (всяческие формы регистрации, добавления ч-л), где очень кстати приходятся правила валидации. Вот так вот отказываюсь от удобств ORM в пользу производительности и экономии памяти, ибо хорошая, но прожорливая штука.

  15. №4304
    Timlar
    Timlar 06 апр. 2011 г., 1:18:17

    Подпишусь на комментарии... ;)

  16. №4306
    Keltanas
    Keltanas 06 апр. 2011 г., 17:06:24

    У меня все с точностью до наоборот.

    Толстые модели сильно снижают гибкость системы.

    Представьте, если потребуется выбирать список каких-то объектов (например счетов), но по какому-то статусу?

    Получится:

    Account::model()->findDeleted();
    Account::model()->findNew();
    Account::model()->findPaid();

    либо

    Account::model()->findByStatus(Account::DELETED);
    Account::model()->findByStatus(Account::NEW);
    Account::model()->findByStatus(Account::PAID);

    А теперь, нам требуется вывести все счета, выставленные в каком-то диапазоне дат

    Account::model()->findByRange( $from, $to );

    Далее возникает логичное желание выбрать новые счета за какой-то диапазон:

    Account::model()->findByStatusAndRange(Account::NEW, $from, $to);

    А теперь представьте, что таких моделей десятки, и у каждой вырастет по сотне таких комбинаций...

    Слабо будет запомнить интерфейсы всех моделей?

    Вот мне слабо...

    А потом переписывать всё это еще и под другую базу... Ну уж нет...

  17. №4307
    Ti
    Ti 06 апр. 2011 г., 20:34:45

    скобочки лишние...

    class MyThinModel() extends CActiveRecord
  18. №4308
    Алексей
    Алексей 06 апр. 2011 г., 20:49:51

    Keltanas, может я что-то не пониаю, но вы пробовали использовать scopes?

  19. №4311
    AmdY
    AmdY 06 апр. 2011 г., 23:04:04

    Keltanas, перечитай мой комментарий выше. http://rmcreative.ru/blog/post/tonkie-i-tolstye-modeli#c4295 представь, что у тебя просто запрос: function findAccounts($status = null, $from = null, $to = null) теперь он с лёгкость подходит ко всем описанным потребностям. да и запрос findAccounts(1) в ходе модификаций может потребовать не просто делать выборку where status = 1, но и заключать бизнес логику модели, например soft delete where status = 1 and deleted = 0

  20. №4312
    Keltanas
    Keltanas 07 апр. 2011 г., 11:19:34

    2Алексей scopes - хорошая идея. Но она же относится к тонким моделям? А речь же в посте не об этом...

    2AmdY то что ты написал:

    тонкая - это когда в каждом модуле используется своя модель унаследованная от model_base, толстая - это когда для всех модулей общая и в ней куча методов

    кажется мне ужасным

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

    Под бизнес-логикой все же понимаю что-то более сложное, чем where status = 1 and deleted = 0. Например расчет маржи с учетом себестоимости, доставки, радости и пр.. для всех товаров в счете.. Только надо оговориться, что у меня оптовая компания и в одном счете могут быть сотни товаров...

    Раньше сделал толстую модель для этих целей, а сейчас понял, на сколько она сложна и неповоротлива... в результате лишний refactoring..

    PS: Единственное, что не нравится в Yii - это статические вызовы в моделях... Я бы предпочел использовать фабричные методы...

  21. №4313
    Sam
    Sam 07 апр. 2011 г., 16:10:19

    Keltanas, а что кроме ::model() вызывается статично и мешает?

  22. №4346
    Serge
    Serge 12 апр. 2011 г., 13:20:10

    А вот допустим у меня есть задача добавление коммента к посту,но при это у меня должно оправляться уведомление автору поста о новом посте и должна быть запись в лог файл. Т.е. есть модель Post и метод у нее addComment(Comment $comment). Тогда возникает вопрос, где должен находиться весь код по отсылке уведомления автора поста и записи в лог? В модели Post или в контроллере поста?

  23. №4348
    Sam
    Sam 12 апр. 2011 г., 16:09:05
  24. №4349
    Serge
    Serge 12 апр. 2011 г., 16:31:51

    2Sam: Все таки по моему толстая модель отличается в основном тем что она содержит всю бизнес логику, включая в моем случае и логику добавления коммента (отсылку уведомления и запись в лог). Т.е. контроллер не содержит ни капли кода записи в лог и уведомления. В контроллере только должнен находиться код создания / валидации формы и все. А тонкая модель наоборот представляет собой только хранилище данных (ее основная задача хранить значения св-в и работа данными из БД) а весь код (логика) сосредоточен в контроллере.

  25. №4392
    rak
    rak 14 апр. 2011 г., 16:12:17

    кстати, а разве не будет более правильно метод getAllUsed объявить статическим? Ведь он к объекту не относится

  26. №4393
    Sam
    Sam 15 апр. 2011 г., 15:31:29

    rak, т.к. в AR есть метод model(), который возвращает экземпляр в состоянии fidner. Это позволяет получить большую гибкость за счёт нормального наследования.

  27. №8336
    mixartemev
    mixartemev 09 сент. 2013 г., 12:47:12

    Уважаемый Sam: Мне нужно реализовать data mapper на yii. Можно поподробнее об этом? rmcreative.ru/blog/post/tonkie-i-tolstye-modeli#c4292 Я не понял как это сделать(

  28. №8338
    Sam
    Sam 09 сент. 2013 г., 13:13:26

    mixartemev, что именно хочется получить от data mapper-а? Насколько он должен быть честным?

  29. №8340
    mixartemev
    mixartemev 10 сент. 2013 г., 15:55:28

    2Sam: что именно хочется получить от data mapper-а? получить то, что обычно от него и требуется - убрать из объекта модели информацию о бд, а оставить только бизнес логику. Насколько он должен быть честным? Насколько возможно. Буду рад любому примеру реализации.

  30. №8414
    Алексей
    Алексей 26 сент. 2013 г., 17:34:28

    mixartemev, Ну если я правильно понял, то оберните AR своей моделью. Наружу давайте только методы модели, работа с AR только внутри модели.

    Очень поверхностно, как то так.

    class UserModel
    {
      private $_dataMapper = NULL;
      private $_data = NULL;
      public function __conctruct()
      {
        $this->_dataMapper = UserModelMapper::model();
      }
      public function getAll()
      {
        $this->_data = $this->_dataMapper->findAll();
      }
    }
  1. Почта опубликована не будет.

  2. Можно использовать синтаксис Markdown или HTML.

  3. Введите ответ в поле. Щёлкните, чтобы получить другую задачу.