Как распознать Active Record?

MiksIr

miksir@home:~$
AR - когда сущность инкапсулирует в себе доступ к базе. Т.е. когда сама сущность знает как себя сохранить и получить из базы. На то он и _Активный_

$user->getCountry() говорит ни о чем. Я не понимаю, почему это вдруг становится AR на основе взгляда на интерфейс. Если у нас реализация такая, что $user сам лезет в базу и достает Country - это похоже на AR. Но если у нас User получает Country другим способом, вплоть до обращения к какому-то сервису еще, это уже не AR.

Loading a typical order with multiple order lines may involve loading the order lines as well. The request from the client will usually lead to a graph of objects being loaded, with the mapper designer deciding exactly how much to pull back in one go. The point of this is to minimize database queries, so the finders typically need to know a fair bit about how clients use the objects in order to make the best choices for pulling data back.

Since objects are very interconnected, you usually have to stop pulling the data back at some point. Otherwise, you’re likely to pull back the entire database with a request. Again, mapping layers have techniques to deal with this while minimizing the impact on the in-memory objects, using Lazy Load (200).
...
On occasion you may need the domain objects to invoke find methods on the Data Mapper. However, I’ve found that with a good Lazy Load (200) you can completely avoid this. For simpler applications, though, may not be worth trying to manage everything with associations and Lazy Load (200). Still, you don’t want to add a dependency from your domain objects to your Data Mapper.

You can solve this dilemma by using Separated Interface (476). Put any find methods needed by the domain code into an interface class that you can place in the domain package.

Т.е. наличие у нас в User конструкции $this->_countryFinder->find($this->_countryId) ну совсем не делает AR.
 

Вурдалак

Продвинутый новичок
Ожидание от phpclub при упоминании слов «сущность»: «f(state/history, cmd) -> events, aggregate roots, bounded contexts»

Реальность: «ну вот это, как евотама, ета эктивный, получается? или дейта мэпэ? как мне кантри из юзера достать??? а???».
 

WMix

герр M:)ller
Партнер клуба
Но если у нас User получает Country другим способом, вплоть до обращения к какому-то сервису еще, это уже не AR.
Т.е. наличие у нас в User конструкции $this->_countryFinder->find($this->_countryId) ну совсем не делает AR.
чистой воды AR. что за гаджет _countryFinder встроили вы в пользователя
 

fixxxer

К.О.
Партнер клуба
что за гаджет _countryFinder встроили вы в пользователя
PHP:
interface CountryFinder
{
    /**
     * @throws CountryNotFound
     */
    public function find($countryId): Country;
}
Где тут база? Файндер какой-то и все. Может, там вообще in-memory коллекция в реализации.

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

WMix

герр M:)ller
Партнер клуба
другими словами все proxy классы перегружают все (LAZY) public методы сущности

PHP:
interface CountryFinder{
    public function find($countryId): Country;
}

class User{
    /**
     * @var Country;
     */
    private $country;

    public getCountry(): Country {
        return $this->country;
    }
}

class UserProxy extends User{
  /**
   * @var CountryFinder;
   */
  private $countryFinder;

  public getCountry(): Country{
      return $this->countryFinder->getCountry($this->countryId);
  }
}
то это полный бред
получается что нет
PHP:
// этот вариант 
$this->countryFinder->getCountry($this->countryId);
// звучит не сильно иначе чем этот
$userMapper->country($user);
 

fixxxer

К.О.
Партнер клуба
PHP:
  /**
   * @var CountryFinderInterface
   */
  private $countryFinder;

  public getCountry(): Country{
      return $this->countryFinder->getCountry($this->countryId);
  }
PHP:
  /**
   * @var CollectionInterface
   */
  private $countries;

  public getCountry(): Country{
      return $this->countries->get($this->countryId);
  }
В чем тут принципиальная разница?
 

WMix

герр M:)ller
Партнер клуба
В чем тут принципиальная разница?
я это и говорю, никакой

но это в любом случае разговор о Proxy который подменит "getCountry" чтоб стиль $user->getCountry()->getLabel() сохранился.
без proxy $userMapper->country($user)->getLabel(); просто звучит несколько иначе, но и в твоем варианте эта строчка $userMapper->country($user) также присутствует
 

fixxxer

К.О.
Партнер клуба
И ты, Брут? Используешь -Interface suffix?
Это я чтобы было очевидно, что речь в тех примерах об интерфейсах (ну не use же сверху рисовать. Впрочем, не знаю, зачем я решил это так подчеркнуть, по сути не так и важно).

А так, если в проекте уже не сложилось иного, то не использую.
 
Последнее редактирование:

fixxxer

К.О.
Партнер клуба
я это и говорю, никакой
На самом деле, я немного слукавил - разница, возможно, есть, только она не показана. (А возможно, и нет, но это будет немножечко архитектурный хак). Но в рамках этого обсуждения не суть важно - это была иллюстрация к той цитате из PoEAA, а на практике все равно будет Lazy Load и вариант с коллекцией :)

без proxy $userMapper->country($user)->getLabel(); просто звучит несколько иначе, но и в твоем варианте эта строчка $userMapper->country($user) также присутствует
Где ты у меня ее нашел?
Посмотри еще раз внимательно на мой исходный пример. Там нет никакого $userMapper->country.
 

WMix

герр M:)ller
Партнер клуба
Там нет никакого $userMapper->country.
userMapper->country vs countryFinder->getCountry, есть ли разница? в моем случае используется relation в твоем crud'овский-read, обе не являются частью user-entity, имеют в глубинах connection к базе, вернут смаппеный обьект
 

fixxxer

К.О.
Партнер клуба
Разница в том, что UserMapper - явное обращение к инфраструктуре, а CountryFinder - абстракция, как и Collection.
 

WMix

герр M:)ller
Партнер клуба
явное обращение к инфраструктуре --- это же у тебя в Proxy инжектится причем тут домен?
 

Вурдалак

Продвинутый новичок
Перейдите на более расширенный язык, который включает в себя write и read models, тогда должно стать проще.
В write model никаких lazy loading, а значит никаких прокси.
В read model могут быть proxy, lazy loading и там будут простые query services, а не «DataMapper» и «ActiveRecord».

Если вы смешиваете write и read, то получаете просто abomination, который так или иначе будет напичкан каким-то дерьмом явно или неявно.
 

fixxxer

К.О.
Партнер клуба
В write model никаких lazy loading, а значит никаких прокси.
Не, ну я бы не был так категоричен. Допустим, есть aggregate root, в котором есть метод вида f(childCollection.state, args) -> newState, а всем остальным методам childCollection.state не важен. Не городить же из-за этого domain service.
 

Вурдалак

Продвинутый новичок
Не, ну я бы не был так категоричен. Допустим, есть aggregate root, в котором есть метод вида f(childCollection.state, args) -> newState, а всем остальным методам childCollection.state не важен. Не городить же из-за этого domain service.
Необходимость в lazy loading подразумевает, что получение этого объекта — тяжёлая операция, что вряд ли должно быть правдой, потому что aggregate root с огромным количеством данных на практике никто не делает. То, что изменение будет чуть-чуть дольше меня не смущает. Я по хардкору всегда все данные выгружаю.

Причём описанный тобой кейс мне видится так, что возможно (хотя и необязательно) лучше было бы выделить эту логику в другой AR, который уже по событиям влияет на описанный тобой. Но здесь нужно конкретный пример рассматривать.

Да и я нередко теперь практикую ES, а там каждое изменение влечёт за собой иногда по несколько тысяч событий (имеется в виду, при накатывании прошлых), но работает для write model всё равно быстро. То есть, необходимости в lazy loading я не встречал.

Помимо прочего, lazy loading опасен тем, что может теоретически привести AR в неконсистетное состояние (но это будет скорее признаком проблемы с транзакцией/локами, поэтому в принципе это не аргумент).
 
Последнее редактирование:

fixxxer

К.О.
Партнер клуба
Причём описанный тобой кейс мне видится так, что возможно (хотя и необязательно) лучше было бы выделить эту логику в другой AR, который уже по событиям влияет на описанный тобой. Но здесь нужно конкретный пример рассматривать.
Да любой AR с большой коллекцией, при изменении которой надо делать вычисления. Скажем, нечто подобное Route в trip-planner, только с таким доменом, где количеством legs может быть огромным.
 

Вурдалак

Продвинутый новичок
Да любой AR с большой коллекцией, при изменении которой надо делать вычисления. Скажем, нечто подобное Route в trip-planner, только с таким доменом, где количеством legs может быть огромным.
Я не могу рассматривать абстрактные примеры, сорри. «Просто не держите его так».
 
Сверху