как Разбивать базу проекта на Domain и Application Logic

ivanov77

Новичок
я много лет не писал на yii, но мне понадобилось 3 клика мышкой и 3 скролла по странице чтобы открыть http://www.yiiframework.com/doc-2.0/yii-test-baseactivefixture.html
Эти фикстуры используются для интеграционных тестов, а не чистых модульных, которые выше советовал @fixxxer
Интеграционными то да, тестируется нормально.
 

grigori

( ͡° ͜ʖ ͡°)
Команда форума
Подумай немного над значением слова "модуль" во фразе "модульный тест". Тест модуля. Одного. Active Record - это обертка нескольких слоев. Для тестирования связи нескольких слоев используются интеграционные тесты.
 
Последнее редактирование:

ivanov77

Новичок
Не только мусор но и вредная. У чела все в голове смешалось и не разобравшись со всеми терминами и паттернами решил излить свой поток сознания.
А что конкретно считаете вредным в той статье? Касается это того его подхода по работе с AR?
 

Yoskaldyr

"Спамер"
Партнер клуба
А что конкретно считаете вредным в той статье? Касается это того его подхода по работе с AR?
Просто прежде чем что-то советовать, надо сначала хорошо разобраться с темой совета.
Если автор бы статью назвал "Еще один подход работы с Active Record в Yii" и не делал громких заявлений Data Mapper - говно, то было все норм, а так что по коду что по тексту видно что у чела в голове каша.
И каша - это не плохо, и очень часто это норма, но пусть он сам ей питается и не пытается кормить окружающих.
А так он просто показал как он решил собственные неудобства при работе с Active Record от Yii
 

AnrDaemon

Продвинутый новичок
У меня, человека далёкого от темы как AR, так и DM, возник вопрос, как это автор статьи так резво перепыгнул от выяснения достоинств и недостатков двух подходов на демонстрацию приёмов работы с конкретным фреймворком.
И даже не столько "как", сколько "зачем" он это сделал?
 

ivanov77

Новичок
У меня, человека далёкого от темы как AR, так и DM, возник вопрос, как это автор статьи так резво перепыгнул от выяснения достоинств и недостатков двух подходов на демонстрацию приёмов работы с конкретным фреймворком.
И даже не столько "как", сколько "зачем" он это сделал?
Там сказано, что для примера.
И весьма удачного примера, т.к. в том числе на yii сейчас идет большая критика, и хороший такой пласт этой критики идет на то как разработчики пользуются ее AR. Когда начинаешь выяснять "что не так" все в результате сводится к "не используй AR вообще т.к. это плохо", а используй datamapper-ы. Без вариантов.
Но это очень радикальный совет и его принять не просто.
Поэтому всякие поиски правильного пути использования AR в yii - вовсе не праздный вопрос, а весьма как раз насущный.
Неплохо что и тот автор таким вопросом задался, но его статье не хватает системности, многие вещи и ограничения указаны просто как утверждения, без логического обоснования, поэтому применить его советы на практике... кодить без понимания что и для чего делаешь, это выльется в кашу.

Вообще эта ситуация немало сбивает с толку. AR использовался и используется годами и повсеместно - RoR, Django, Yii, Laravel, а ищешь есть ли какие то реальные howto как ею пользоваться с минимальными проблемами, пришли ли сообщества к какому то общему знаменателю, и ничего не находишь.
 

WMix

герр M:)ller
Партнер клуба
Но это очень радикальный совет и его принять не просто.
При использовании маппера код становится чище, сущности легкие (нет этого conneection в каждой сущности), тестирование в большинстве случаев не требует базы данных.
Yii плюс ко всему (я точно не знаю но гляжу примеры) привязывает весь проект к себе, избавиться от него будет практически невозможно. там в большем количестве каждом классов есть зависимости от framework.
если на секунду представить что в день Х появится версия Yii3 которая не совместима с предыдущей версией, поймешь проблему.
 

Вурдалак

Продвинутый новичок
@ivanov77 я всё же советую почитать комменты, там много из того, что примерно возразил бы я, сказал тот же Fesor.

Если очень вкратце, в чём проблема автора из того, что я вижу в самом начале статьи:
  • он не понимает, что чтение и изменение совершенно разные операции, которые желательно размещать в разных сервисах («в любом серьезном приложении»), гуглится по CQRS; тогда нет никакой проблемы использовать голый SQL для чтения «множества записей»; с изменениями вообще никаких проблем нет — несколько простых UPDATE — это не проблема изначально;
  • он называет сущности «пассивными записями» (ну, те, которые в DataMapper пихают) и чуть далее «Особенно хорошо это заметно в CRUD-приложениях» — иными словами «сущности — анемичны, особенно это хорошо заметно, когда они анемичны», это порочный круг; более того, скорее всего при детальном рассмотрении так называемые «CRUD-приложения» — это то, как сам автор называет свои приложения, потому что не видит логики и что ещё более опасно — не видит бизнес-глаголы и мыслит свойствами;
  • «Правильный способ получения зависимостей — внедрение через конструктор» — просто голословное утверждение; сущность — это как человек, человек получает информацию извне (команды) и как-то внутренне меняется (события); глаз человека можно назвать в данном контексте сервисом, но статическим, поскольку он работает в пределах нашего тела, нам в голову не протянут провод откуда-то снаружи, чтобы мы могли видеть. То есть, рассчитать какой-то нибудь md5 или расстояние между двумя точками на сфере — это статическая логика, которую можно вызывать в самой сущности, всё остальное можно передавать через обычные методы сущности, поскольку, очевидно, это внешняя для сущности информация. И нам не меняют свойства напрямую, мы получаем множество сигналов через наши органы чувств, методы сущности — это и есть те самые органы чувств — внешний интерфейс сущности. btw, я не использую double dispatch, а передаю результат работы нужного мне внешнего сервиса, т.е. я передаю в методы только скаляры и value objects.
Ну и там куча мелких и не очень логических подтасовок и некорректных утверждений, лень комментировать.

Поэтому всякие поиски правильного пути использования AR в yii - вовсе не праздный вопрос, а весьма как раз насущный.
ActiveRecord в принципе устроен так, что он работает на неверных предпосылках, которые я перечислил выше. Здесь я пытался показать, что работать с Yii «правильно» просто неудобно. Переход на DataMapper не является достаточным условием того, что твой код станет правильным, если ты будешь допускать те же ошибки, но он в определённом смысле является необходимым.
 
Последнее редактирование:

ivanov77

Новичок
При использовании маппера код становится чище, сущности легкие (нет этого conneection в каждой сущности), тестирование в большинстве случаев не требует базы данных.
Ну а в реалиях php, такое решение выльется в безальтернативность использования Doctrine2?
Маппинг дело то непростое, видел я как люди пробуют велосипедить, все эти непростые решения, создание объектов без вызова конструктора, распривачивание св-в через рефлексию, чтобы заполнить. Кто все такое поддерживать будет?... Вон вы про зависимость от Yii печетесь, а зависимость от Doctrine вас почему то не тревожит.
 

WMix

герр M:)ller
Партнер клуба
в самом маппинге нет ничего сложного, doctrine делает еще кучу других вещей
PHP:
class User{
  private $id;
  private $name;

  public function rename($name){
    $this->name = $name;
  }

// это только потому что поля приватные и рефлексией пользоваться не хотим, можно в trait вырезать или в маппере заюзать http://php.net/manual/ru/closure.call.php
  public function hydrate($arr){
    $this->id = $arr['id'];
    $this->name = $arr['name'];
  }
  public function extract(): array{
    return ['id' => $this->id, 'name' => $this->name];
  }
}

class UserMapper{
  private $db;
  public function read( $id ): User{
    // чтоб представление иметь что происходит внутри
    $arr = $this->db->sql('select id, name from users where id=?', [$id])->row();
    $user = new User;
    $user->hydrate($arr);
  }
  public function update(User $user){
   $arr = $user->extract();
   $this->db->sql('update users set name=? where id=?',[$arr['name'],$arr['id']])->execute();
  }
  // остальные методы сам додумай
}

class UserService{
  private $mapper;
  public function rename($id, $name){
    $user = $this->mapper->read($id);
    $user->rename($name);
    $this->mapper->update($user);
  }
}
зависимость от Doctrine вас почему то не тревожит.
чтоб выкинуть доктрину нужно просто переписать маппер, чтоб выкинуть yii придеться переписать все
 

fixxxer

К.О.
Партнер клуба
зависимость от Doctrine вас почему то не тревожит
Потому что ее нет. :)

Я пользуюсь Analogue ORM, могу легко заменить его на Doctrine, не поменяется вообще ничего, кроме реализации репозиториев и конфигурации маппинга. То есть, на картинке Вурдалака с 3-й страницы поменяется только кусочек Infrastructure.

все эти непростые решения, создание объектов без вызова конструктора, распривачивание св-в через рефлексию, чтобы заполнить. Кто все такое поддерживать будет?
Ну вот, поддерживают как-то:
doctrine/doctrine2: 536 contributors
 
Последнее редактирование:

Вурдалак

Продвинутый новичок
Я ещё раз глянул комменты к статье на хабре и, если честно, они радуют. Если несколько лет назад такую бы статью заплюсовали, то теперь подобный треш встречают вполне сносными комментариями. Прогресс идёт, товарищи.
 

fixxxer

К.О.
Партнер клуба
btw, я не использую double dispatch, а передаю результат работы нужного мне внешнего сервиса
Это легко (и правильно) сделать, когда внешнему сервису не требуется знать о состоянии сущности.

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

Вурдалак

Продвинутый новичок
Это легко (и правильно) сделать, когда внешнему сервису не требуется знать о состоянии сущности.
Тут хитрость вот в чём: если в сущности есть нечто, то это «нечто» либо туда как-то попало снаружи, либо были вычислено внутри сущности, а значит скорее всего фигурировало в событии. По событию мы могли записать это значение в read model и оттуда считывать. Я как бы и не лезу в саму сущность, всё по-честному, я лезу в её проекцию, которая знает о сущности ровно столько, сколько она сама ранее сообщила.

Если же говорить про тот пример с солью, мне он кажется неудачным :) Он вроде бы технические ограничения пытается задать, но ...
Во-первых, я бы хешер не стал бы делать внешним сервисом. password_verify()/password_hash() прямо внутри сущности.
Во-вторых, если уж всё-таки это сервис, то я бы просто соль заново сгенерил. Не вижу практических причин переиспользовать то же самое значение.
В-третьих, читерство, описанное выше, даёт возможность обойти практически все подобные проблемы, включая эту.

Я на практике пока не встречал ни одного случая, когда мне double dispatch был действительно необходим. В то время, когда мы это на форуме обсуждали, у меня была какая-то глупая мотивация из разряда «для согласованности модели», т.е. якобы для того, чтобы контракты модели не позволяли в явном виде передать напрямую в сущность какой-то мусор снаружи, только через реализацию доменных сервисов. Но по факту это какой-то бессмысленный пуризм, в модели в первую очередь важны сами действия и логика внутри, а не то каким образом мы получили внешние данные.
 

Вурдалак

Продвинутый новичок
И немного ретроспективы (просто нашёл в том топике, где был пример с солью):
Но все же пусть так - предположим, что надо проверить существование mx-записи, и если она вдруг пропала, выбросить исключение, ничего не сохраняя.
Вот нафиг не нужно делать реальную проверку существования MX-записи в сущности, пусть и через заинжекченный через double dispatch сервис. Нужен просто сигнал сущности, который будет включать в себя флаг bool $mxRecordExists (если для модели вообще имеет смысл ситуация с $mxRecordExists === false; иначе просто до модели не должно доходить дело). Модель просто как-то реагирует на поступающие сообщения и не должна лезть сама, пусть и опосредованно, во внешний мир. Если я захочу, я могу обманывать модель, подпихивая ей вымышленные сообщения. На то она и модель, чтобы моделировать различные ситуации.
 

fixxxer

К.О.
Партнер клуба
Ну, пример высосанный из пальца, да, на практике я так и не делал. Я к тому, что если мне понадобится выбирать между
PHP:
$bar = $barService->calcBar($entity->getFoo());
$entity->somethingWithBar($bar);
и
PHP:
$entity->somethingWithBar($barService);
то я выберу второе.

Хотел сейчас найти в своем коде реальный пример, где вроде у меня был Double Dispatch, но открыл код и вспомнил, что после рефакторинга там Double Dispatch-а не стало. Так что, может быть, и действительно почти никогда не надо :)
Вот нафиг не нужно
Да, это я сейчас уже понимаю :)
 

Вурдалак

Продвинутый новичок
Я понимаю о чём ты, но у меня интуитивное ощущение, что тут проблема изначально в том, что эта логика в сущности. Если на основании каких-то свойств сущности мы что-то делаем, то сама сущность уже это не контролирует. Нарушением инкапсуляции в каком-то смысле можно считать признание самого факта, что какое-то свойство существует, ведь по-хорошему мы не знаем каким образом реализовано поведение (логика записи событий). Это покажется странным, но я скорее предпочту геттер, но не у сущности, а у read model, поскольку с моей точки зрения эта логика изначально не относится непосредственно к сущности, она внешняя по отношению к ней.

Вспомни тот пример с отправкой письма: ты ведь не делаешь это через double dispatch? Откуда ты берёшь email? Я либо беру из event'а, если он там есть, либо из read model. Я не считаю это нарушением инкапсуляции, поскольку сущность сама сообщает через события про свой email и это общеизвестное значение для домена.

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

grigori

( ͡° ͜ʖ ͡°)
Команда форума
на yii сейчас идет большая критика, и хороший такой пласт этой критики идет на то как разработчики пользуются ее AR. Когда начинаешь выяснять "что не так" все в результате сводится к "не используй AR вообще т.к. это плохо", а используй datamapper-ы. Без вариантов.
Но это очень радикальный совет и его принять не просто.
Поэтому всякие поиски правильного пути использования AR в yii - вовсе не праздный вопрос, а весьма как раз насущный.

Вообще эта ситуация немало сбивает с толку. AR использовался и используется годами и повсеместно - RoR, Django, Yii, Laravel, а ищешь есть ли какие то реальные howto как ею пользоваться с минимальными проблемами, пришли ли сообщества к какому то общему знаменателю, и ничего не находишь.
Тебе нужна декомпозиция. Выбрать вопрос - и его обсуждать. Отдельный вопрос - в отдельной теме, лимита на вопросы нет. Пишешь здесь про то, про это - какое, все-таки, наивное дитя. Тебе за жизнь и отвечают.

Поиски правильного пути у тебя? С твоей техникой программирования, вполне логично обойтись базовой литературой по дизайну. Банду 4х, например - не список паттернов по оглавлению просмотреть, а прочесть первую половину, где многабукаф.

Критика на Yii сейчас? Да всегда. П$%ть - не мешки ворочать. Саппортить фреймворк - это очень много сил, и на этом вообще не зарабатывают. Я в этой теме инсайдер.
Yii - это проект, где русские учат китайцев дизайну, а китайцы русских - трудолюбию :)

AR - отстой, на каждую сущность пишем по 4 класса. Value Object, Mapper, Validator, Test. Вы держитесь здесь, всего вам доброго! (Только денег нет).
BMW лучше, чем Geely? Больше всего людям нужны автобусы.

Сообщества давно уже пришли. Противопоставление мапперов и AR - теоретическое, при желании их можно использовать одновременно.
Инкапсуляцию AR не отменяет, композиция все еще лучше наследования. Проблема всегда в дизайне, и никогда не в инструменте. Можно не наследоваться и не дописывать код в автосгенерированные кассы. Можно переписать PDO для соблюдения CQRS, в конце концов :)
Чтобы разобраться в архитектуре - надо учиться архитектуре, читать обсуждение состава цемента недостаточно.
 
Последнее редактирование:
Сверху