Сервисный слой, как правильно?

Обсуждаем, как правильно строить приложения
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: Сервисный слой, как правильно?

Сообщение zelenin »

SiZE писал(а):
zelenin писал(а):

Код: Выделить всё

$subscriptionService->createFromForm(SubscriptionForm $form);
 
А модель Subscription в сервис как прокинуть? И может ли сервис работать с несколькими наследниками модели Subscription? И где присваивать userId? =)
зачем прокидывать? ты же новую создаешь?
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: Сервисный слой, как правильно?

Сообщение zelenin »

slavcodev писал(а):
zelenin писал(а):статических фабричных метода (так называемые именованные конструкторы (с) Верраес)
Я сразу понял про что ты, я сам читаю статьи Mathias.
Но к этому подходу отношусь неуверенно. Я считаю что оно нарушает принципы ООП, а именно инкпасуляцию,
из-за того что устанавливаются приватные свойства.
почему приватные методы? это же просто обертка над конструктором.
slavcodev писал(а):
zelenin писал(а):создание объекта на основе одного id - вещь нетрадиционная и мной ни разу не встречаемая у известных авторов
Небольшое уточнение:

Не одного ИД, а на основе данных без которых объект не может принять свое начальное состояние.
а это уже вещь стандартная, и опять можно начинаться спорить. Но я чесговоря выдохся - пишем много, да большинство по кругу, обсуждая личные предпочтения.
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: Сервисный слой, как правильно?

Сообщение zelenin »

slavcodev писал(а):
ElisDN писал(а):Ваш подход можно и со статическими конструкторами совместить
Возможно конечно, но я предпочитаю так не делать.
Причину написал выше, лично я считаю это обходом инкапсуляции через установку значений приватным свойствам не через конструктор или поведения сущности.
так где ты видишь установку приватных свойств?
Аватара пользователя
slavcodev
Сообщения: 3134
Зарегистрирован: 2009.04.02, 21:42
Откуда: Valencia
Контактная информация:

Re: Сервисный слой, как правильно?

Сообщение slavcodev »

Код: Выделить всё

final class Time
{
    private $hours, $minutes;

    // We don't remove the empty constructor because it still needs to be private
    private function __construct(){} 

    public static function fromValues($hours, $minutes)
    {
        $time = new Time;
        $time->hours = $hours;
        $time->minutes = $minutes;
        return $time;
    }
} 
hours, minutes - являются приватными свойства объекта (конкретного экземпляра класса), а статический метод это поведение класса, т.е. всех экземпляров данного класса. Имхо статические классы могут только менять такие же статические свойства, но не могут менять приватные свойства инстанциированного объекта. Свойства объекта должны меняться через поведения, интерфейс объекта.

Это ИМХО, могу ошибаться, как говорится "Хотите верьте, хотите нет" :)
Жду Yii 3!
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: Сервисный слой, как правильно?

Сообщение zelenin »

slavcodev писал(а):

Код: Выделить всё

final class Time
{
    private $hours, $minutes;

    // We don't remove the empty constructor because it still needs to be private
    private function __construct(){} 

    public static function fromValues($hours, $minutes)
    {
        $time = new Time;
        $time->hours = $hours;
        $time->minutes = $minutes;
        return $time;
    }
}
hours, minutes - являются приватными свойства объекта (конкретного экземпляра класса), а статический метод это поведение класса, т.е. всех экземпляров данного класса. Имхо статические классы могут только менять такие же статические свойства, но не могут менять приватные свойства инстанциированного объекта. Свойства объекта должны меняться через поведения, интерфейс объекта.

Это ИМХО, могу ошибаться, как говорится "Хотите верьте, хотите нет" :)
этот пример я кстати упустил у верраеса - был уверен, что к приватным свойствам нельзя из статики обращаться.
Тут я согласен насчет хака, но сам хаки в виду не имел.
Аватара пользователя
ElisDN
Сообщения: 5845
Зарегистрирован: 2012.10.07, 10:24
Контактная информация:

Re: Сервисный слой, как правильно?

Сообщение ElisDN »

slavcodev писал(а):hours, minutes - являются приватными свойства объекта (конкретного экземпляра класса), а статический метод это поведение класса, т.е. всех экземпляров данного класса. Имхо статические классы могут только менять такие же статические свойства, но не могут менять приватные свойства инстанциированного объекта. Свойства объекта должны меняться через поведения, интерфейс объекта.
Ну это да, некий хак для обхода отсутствия перегрузки конструкторов.

Со статическими методами классический вариант с полным конструктором выглядит тоже не очень:

Код: Выделить всё

class User
{
    private function __construct($email, ..., $role)
    {
        $this->email = $email;
        ...
        $this->role = $role;
    }

    public static function draft($email, $password, $confirmationToken)
    {
        $user = new self($email, null, $password, null, null, 'pending-confirmation', $confirmationToken, 'User');
        $user->recordEvent(new TestUserWasCreatedEvent($user));
        return $user;
    }

    public static function register($email, $name, $password, $confirmationToken)
    {
        $user = new self($email, $name, $password, new DateTime(), new DateTime(), 'pending-confirmation', $confirmationToken, 'User');
        $user->recordEvent(new NewUserWasRegisteredEvent($user));
        return $user;
    }
}
так как здесь тоже затык с событиями, поведением и валидацией.

А ваш вариант с динамическими draft() и register() полностью избавляет от этих проблем.
Аватара пользователя
ElisDN
Сообщения: 5845
Зарегистрирован: 2012.10.07, 10:24
Контактная информация:

Re: Сервисный слой, как правильно?

Сообщение ElisDN »

slavcodev писал(а):но не могут менять приватные свойства инстанциированного объекта. Свойства объекта должны меняться через поведения, интерфейс объекта.
Как симбиоз статики и конструктора сейчас додумался до такого варианта:

Код: Выделить всё

class User
{
    private $id;
    private $email;
    private $status;
    private $role;

    private function __construct($email)
    {
        $this->email = $email;
        $this->status = 'inactive';
        $this->role = 'User';
    }

    public static function createDraft($email, $password, $confirmationToken)
    {
        $user = new self($email);
        $user->draft($password, $confirmationToken);
        return $user;
    }

    private function draft($password, $confirmationToken)
    {
        $this->password = $password;
        $this->confirmationToken = $confirmationToken;
        $this->createdTime = new DateTime();
        $this->status = 'pending-confirmation';
        $this->recordEvent(new TestUserWasCreatedEvent($this));
    }
    
    public function getId() {return $this->email };
    public function getStatus() {return $this->status };
    public function getRole() {return $this->role };
} 
Это придаёт атомарность действию регистрации (не даёт создать и сохранить пустого пользователя вне use case), не нарушает инкапсуляцию полей и избавляет от необходимости имевшихся там проверок и эксепшенов.
Последний раз редактировалось ElisDN 2016.08.04, 00:46, всего редактировалось 1 раз.
Аватара пользователя
ElisDN
Сообщения: 5845
Зарегистрирован: 2012.10.07, 10:24
Контактная информация:

Re: Сервисный слой, как правильно?

Сообщение ElisDN »

slavcodev писал(а):

Код: Выделить всё

class UserRepository
{
  function save(User $user)
  {
    // сохраняем пользователя в базу данных (имплементация упущена)
    // после чего обрабатываем события произошедшие с ним,
    // отсылаем емитеру чтоб оповестил подписчиков из других контектов
    foreach ($user->flushEvents() as $event) {
      $this->eventEmitter->emit($event);
    }
  }
} 
Высвобождать события в репозитории опасно, если несколько сохранении вызываются подряд в транзакции внутри блока try-catch.

Надёжнее либо вызывать в хэндлере после всех сохранений:

Код: Выделить всё

public function handle (EmployeeRecruitCommand $command)
{
    $employee = ...;
    $contract = ...;
    
    $this->employeeRepository->add($employee);
    $this->contractRepository->add($contract);
    
    $this->eventDispatcher->dispatch($employee->releaseEvents());
    $this->eventDispatcher->dispatch($contract->releaseEvents());
} 
Либо сделать декоратор TransactionalCommandBus и в нём обрабатывать все события из всех сущностей из identityMap (если используется):

Код: Выделить всё

class TransactionalCommandBus implements CommandBusInterface
{
    private $next;
    private $transactionManager;
    private $eventDispatcher;
    private $identityMap;

    public function execute($command)
    {
        $transaction = $this->transactionManager->begin();
        try {
            $this->next->execute($command);
            $transaction->commit();
            $this->eventDispatcher->dispatch($this->identityMap->releaseAllEvents());
        } catch (\Exception $e) {
            $transaction->rollback();
            throw $e;
        }
    }
} 
Либо сделать Emitter с записью и с отложенным исполнением:

Код: Выделить всё

class TransactionalCommandBus implements CommandBusInterface
{
    private $next;
    private $transactionManager;
    private $eventEmitter;

    public function execute($command)
    {
        $transaction = $this->transactionManager->begin();
        try {
            $this->next->execute($command);
            $transaction->commit();
            $this->eventEmitter->emitRecordedEvents();
        } catch (\Exception $e) {
            $transaction->rollback();
            throw $e;
        }
    }
} 
Последний раз редактировалось ElisDN 2016.07.25, 18:27, всего редактировалось 2 раза.
Аватара пользователя
slavcodev
Сообщения: 3134
Зарегистрирован: 2009.04.02, 21:42
Откуда: Valencia
Контактная информация:

Re: Сервисный слой, как правильно?

Сообщение slavcodev »

ElisDN писал(а):
slavcodev писал(а):

Код: Выделить всё

class UserRepository
{
  function save(User $user)
  {
    // сохраняем пользователя в базу данных (имплементация упущена)
    // после чего обрабатываем события произошедшие с ним,
    // отсылаем емитеру чтоб оповестил подписчиков из других контектов
    foreach ($user->flushEvents() as $event) {
      $this->eventEmitter->emit($event);
    }
  }
}
Высвобождать события в репозитории опасно, если несколько сохранении вызываются подряд в транзакции внутри блока try-catch.

Надёжнее либо вызывать в хэндлере после всех сохранений
Может и опасно. А может опаснее обрабатывать события в разных хендлерах,
можно забыть, прогер после вас просто не будет знать об этой особенности приложения.
И вообще советов на все случае жизни нет, иначе я и ты сидели бы без работы, все бы придумали до нас.

Мы же говорим о ДДД? Стоит ли думать тогда о тонкостях БД при проектировании?

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

При проблеме с транзакцией сохранения в БД двух сущностей,
вполне возможно что границы между bounded contexts слишком слабы, спроектированы не достаточно закрыто и независимо.
Не знаю домена, можно предположить что $contract это не отдельный AR, а сущность связанная с $employee,
так что будет только сохранение $employee в сервисах.

В любом случае твой вариант не работает в системе над которой я работаю, т.к. сохранение в базу занимается хендлер ивент имитера (так называемые - проекции).
Жду Yii 3!
Аватара пользователя
slavcodev
Сообщения: 3134
Зарегистрирован: 2009.04.02, 21:42
Откуда: Valencia
Контактная информация:

Re: Сервисный слой, как правильно?

Сообщение slavcodev »

ElisDN писал(а):
slavcodev писал(а):но не могут менять приватные свойства инстанциированного объекта. Свойства объекта должны меняться через поведения, интерфейс объекта.
Как симбиоз статики и конструктора сейчас додумался до такого варианта:

Код: Выделить всё

class User
{
    private $id;
    private $email;
    private $status;
    private $role;

    private function __construct($email)
    {
        $this->email = $email;
        $this->status = 'inactive';
        $this->role = 'User';
    }

    public static function createDraft($email, $password, $confirmationToken)
    {
        $user = new self($email);
        $user->draft($password, $confirmationToken);
        return $user;
    }

    private function draft($password, $confirmationToken)
    {
        $this->password = $password;
        $this->confirmationToken = $confirmationToken;
        $this->createdTime = new DateTime();
        $this->status = 'pending-confirmation';
        $this->recordEvent(new TestUserWasCreatedEvent($this));
    }
    
    public function getId() {return $this->email };
    public function getStatus() {return $this->status };
    public function getRole() {return $this->role };
}
Это придаёт атомарность действию регистрации (не даёт создать и сохранить пустого пользователя вне use case), не нарушает инкапсуляцию и избавляет от необходимости имевшихся там проверок и эксепшенов.
Это вариант практически ничем не лучше варианта Varraes, ты используешь приватные методы, он приватные свойства из публичного статического метода. Да ООП это позволяет, но как я узнал недавно, в народе это называют антипаттерном "Паблик Морозова".
Поэтому мне лично не подходит.

Проблема с сохранением пустого юзера, решается тривиально, проверка наличия произошедших с этой сущностью событий.
Жду Yii 3!
Аватара пользователя
SiZE
Сообщения: 2813
Зарегистрирован: 2011.09.21, 12:39
Откуда: Perm
Контактная информация:

Re: Сервисный слой, как правильно?

Сообщение SiZE »

Я правильно понимаю, что просто начать с малого и внедрить сервисный слой в Й2 не получится?

Я зациклился на полученной информации. Просто с сервисами сразу хочется и ДТО, и события, и репозитории, но на все время нет. Как в классическом Й2 ограничится для начала ими?
Последний раз редактировалось SiZE 2016.07.26, 06:59, всего редактировалось 1 раз.
Аватара пользователя
ElisDN
Сообщения: 5845
Зарегистрирован: 2012.10.07, 10:24
Контактная информация:

Re: Сервисный слой, как правильно?

Сообщение ElisDN »

slavcodev писал(а):Поэтому мне лично не подходит.
Поразмышлял на эту тему пару дней. Поэкспериментирую тоже без статических методов.

И касательно эмиттера сразу не сообразил, что это могут быть сообщения ES для проекций, а не просто доменные события.
Аватара пользователя
ElisDN
Сообщения: 5845
Зарегистрирован: 2012.10.07, 10:24
Контактная информация:

Re: Сервисный слой, как правильно?

Сообщение ElisDN »

SiZE писал(а):Я правильно понимаю, что просто начать с малого и внедрить сервисный слой в Й2 не получится?
Архитектура придумана для упрощения сложного кода, а не для усложнения простого. Если Вас всё устраивает, если не пишете тесты, не требуется подключать разные БД и в проекте нет сложных и неприятных мест, которые портят жизнь, то заморачиваться не нужно.
SiZE писал(а):Я зациклился на полученной информации. Просто с репозиториями сразу хочется и ДТО, и события, и репозитории, но на все время нет.
Отличие подхода с проектированием от обычного лапшекода - необходимость много думать. Если есть рамки "некогда думать, быстрей фигачь в продакшн", то времени не будет никогда.
SiZE писал(а):Как в классическом Й2 ограничится для начала ими?
Если всё же захочется, то перечитайте книги по ООП и рефакторингу и постепенно начинайте применять советы из них.
Аватара пользователя
slavcodev
Сообщения: 3134
Зарегистрирован: 2009.04.02, 21:42
Откуда: Valencia
Контактная информация:

Re: Сервисный слой, как правильно?

Сообщение slavcodev »

SiZE писал(а):Я правильно понимаю, что просто начать с малого и внедрить сервисный слой в Й2 не получится?
"Сервисный слой" как раз не проблема вообще, проблема в правильно проектировании модели.

Рецепт "Сервисный слой"

1. Создаем по классы на один use case (Варианты использования):

[*] Регистрация пользователя *
[*] Активация пользователя
[*] Просмотр профиля пользователя
[*] Удаление пользователя **

* - Не важно кто инициатор (какое приложение: веб форма, REST API, консоль), сервис ничего не должен о слое выше,
т.е. о том кто его вызвал, он получил данные и обработал. Важно бизнес логика, при выполнении. Например если из веб формы и REST API создание одинаково (не важно что формат данных разный) это один сервис, а из консоли можно создать только первого юзера (админа) - это уже совсем другой юз кейс, поэтому лучше сделать отдельным сервисом, чем меньше IF в коде, тем лучше.

** - Если есть такой юз кейс. Не путать с удалением модели из базы, это может быть действие внутренее. Другими словами по одному юз кейсу на каждое действие на вашу систему из вне (HTTP запрос, запуск консольной команды)

2. Проектирование сервисов:

[*] Зависимости сервиса - в конструктор (другие сервисы, компоненты приложения), настройки сервиса (какие-то общие свойства)
[*] Данные от контроллера - в метод (данные понятные сервису, контролер должен постараться и привести свои данные к этому формату)
[*] Результат - данные ожидаемые при успехе (при регистрации - пользователя, при активации - пользователя, при удалении - ничего).

Забудьте про возвращение булевого результата. Bool - это такой же тип данных как и другие, и используется для данных.
Есть такой принцип в ООП - Tell, don't ask (простите за еще один термин, за еще одну проблему в голове). Запустив команду ждите результат (то что команда вычислила, создала) либо исключение.
Бул используйте только при запросе каких-то данные (isValid(), isActive(), canUserDance())


3. Зона ответственности. Каждый слой знает только как работать с ниже стоящим:

[*] Контролер знает как вызвать инициализировать сервис и как запустить, с какими параметрами
[*] Сервис ничего не знает о контролере, или кто его там вызвал
[*] Сервис знает как работать с моделями либо с другими сервисами того же слоя либо из слоя ниже
[*] Модель ничего не знает о существовании сервиса или кто ее пользуется

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

Кстати говоря, внешние экшены Yii, являются такими сервисами :)
Жду Yii 3!
Аватара пользователя
SiZE
Сообщения: 2813
Зарегистрирован: 2011.09.21, 12:39
Откуда: Perm
Контактная информация:

Re: Сервисный слой, как правильно?

Сообщение SiZE »

Спасибо.
chesar
Сообщения: 514
Зарегистрирован: 2013.04.10, 17:49

Re: Сервисный слой, как правильно?

Сообщение chesar »

Уместно ли использовать внутри модели хелперы?

Код: Выделить всё

class UserDomainModel
{
  private $login;

  private $email;

  private $password;

  private $restoreСode;

  private $confirmationСode;

  private $type;

  private $visible;

  public static function createFromOrder(Order $order)
  {
    $model = new self($order->email, $order->email);
    $model->generatePasswordHash();
    $model->confirmation_code = Utils::generatePassword(32);
    $model->visible = 0;
    $model->type = 'user';
    return $model;
  }
    
  public function __construct($login, $email, $password = null)
  {
    $this->login = $login;
    $this->email = $email;
    $this->password = $password;
    $this->visible = 1;
  }

  public function generatePasswordHash()
  {
    $password = $this->password === null ? Utils::generatePassword() : $this->password;
    $this->passwordРash = md5($this->login . $password . 'salrt');
  }

  public function activate()
  {
    $this->confirmationСode = Utils::generatePassword(32);
    $this->visible = 1;
  }
}
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: Сервисный слой, как правильно?

Сообщение zelenin »

в данном случае уместно назвать это domain service

Код: Выделить всё

public function generatePasswordHash(PasswordHashGeneratorInterface $hasher) 
Аватара пользователя
ElisDN
Сообщения: 5845
Зарегистрирован: 2012.10.07, 10:24
Контактная информация:

Re: Сервисный слой, как правильно?

Сообщение ElisDN »

chesar писал(а):Уместно ли использовать внутри модели хелперы?
Во-первых, наличие класса вроде Utils говорит о том, что в нём бессмысленно накидана большая куча несвязанных методов вроде:

Код: Выделить всё

class Utils
{
    public static function hashPassword(...)
    public static function validatePassword(...)
    public static function watermarkImage(...)
    public static function showGravatar(...)
}
С человеческой точки зрения логичнее разбить группы методов по смыслу и разнести в классы с нормальными именами:

Код: Выделить всё

class PasswordHasher
{
    public static function hash(...)
    public static function validate(...)
}

class Image
{
    public static function watermark(...)
}

class Gravatar
{
    public static function show(...)
}
Во-вторых, видна проблема в именовании или в заложенном смысле:

Код: Выделить всё

$this->confirmationСode = Utils::generatePassword();
Почему-то для генерации кода подтверждения используется генератор пароля. Если кто-то поменяет алгоритм генерации пароля, то заодно поменяется и генерация кодов. Чтобы не было таких сюрпризов и недопониманий, просто делаем для кодов отдельный метод, который так и называем генератором кодов:

Код: Выделить всё

$this->confirmationСode = Utils::generateСonfirmationСode();
И в него не нужно будет даже передавать параметр 32, так как он теперь используется только для одной цели.

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

Код: Выделить всё

class PasswordHasher
{
    public static function hash($password)
    {
        return Yii::$app->security->generatePasswordHash($password);
    }
    
    public static function validate($password, $hash)
    {
        return Yii::$app->security->validatePassword($password, $hash);
    }
}
и вызываете его из модели:

Код: Выделить всё

public function generatePassword($password)
{
    $this->passwordРash = PasswordHasher::hash($password);
}
то каждый тест на логин или регистрацию будет занимать полсекунды времени из-за медленного хеширования.

В-четвёртых, при использовании статики снаружи нам не понятно, куда наша модель сама лезет и зачем. Часто в Yii2 любят ещё и письма из модели посылать, и Yii::$app->user или Yii::$app->request дёргать. Такая самовольность весьма неприятна.

Как это победить? Если рассмотреть пример со статическими методами:

Код: Выделить всё

public function signup($email, $password)
{
    $this->email = $email;
    $this->passwordHash = PasswordHasher::hash($password);
    $this->confirmationСode = TokenGenerator::generate();
    $this->active = 0;
}
то, как уже говорили, их просто так не подменишь, моки не проставишь и модель не проконтролируешь.

Простейший эффективный вариант - вынести генерацию из модели и передавать в модель уже готовые хеши и токены:

Код: Выделить всё

public function signup($email, $passwordHash, $confirmationСode)
{
    $this->email = $email;
    $this->passwordHash = $passwordHash;
    $this->confirmationСode = $confirmationСode;
    $this->active = 0;
}
и генерировать и проверять хеши снаружи в хендлере. Модель становится абсолютно независима от других сервисов и ей не важно, кто и что ей передаёт.

А если не хочется делать хеши и токены доступными снаружи через геттеры, то есть третий подход с инъекцией зависимых компонетов в сам метод. Мы передаём нужные сервисы в метод, а сама модель вызывает из них то, что ей нужно:

Код: Выделить всё

public function signup(
    $email,
    $password,
    PasswordHasherInterface $passwordHasher,
    TokenGeneratorInterface $tokenGenerator
)
{
    $this->email = $email;
    $this->passwordHash = $passwordHasher->hash($password);
    $this->confirmationСode = $tokenGenerator->generate();
    $this->active = 0;
}
Теперь passwordHash можно оставить приватным и не делать для него публичный геттер. Модель всё рассчитает с помощью предоставленных ей генераторов. То есть суть та же, как со статическими методами, но методы уже не статические. Соответственно, эти передаваемые объекты в тестах можно менять.

Для рабочего кода можем написать фреймворковский хешер:

Код: Выделить всё

class YiiPasswordHasher implements PasswordHasherInterface
{
    public function hash($password)
    {
        return Yii::$app->security->generatePasswordHash($password);
    }
    
    public function validate($password, $hash)
    {
        return Yii::$app->security->validatePassword($password, $hash);
    }
}
и настроить его использование в контейнере:

Код: Выделить всё

Yii::$container->set('app\services\PasswordHasherInterface', 'app\services\YiiPasswordHasher');
А для тестов сделаем лёгкую md5-заглушку:

Код: Выделить всё

class TestPasswordHasher implements PasswordHasherInterface
{
    public function hash($password)
    {
        return md5($password);
    }
    
    public function validate($password, $hash)
    {
       return  md5($password) === $hash;
    }
}
и будем использовать лёгкий и быстрый TestPasswordHasher вместо тяжёлого и медленного оригинального PasswordHasher:

Код: Выделить всё

$passwordHasher = new TestPasswordHasher();
$tokenGenerator = new TestConfirmCodeGenerator();
$user->signup($email, $password, $passwordHasher, $tokenGenerator);
и сотня тестов выполнится у нас за пару секунд вместо пары минут.

И, как бонус, на заглушках такой код можно будет тестировать без поднятия Yii::$app на голом PHPUnit_Framework_TestCase. Это ещё ускорит процесс.

Какова мораль? В статических хелперах можно оставить очень лёгкие, чистые и неизменяемые вещи, независимые от фреймворка. Например, обёртка StringHelper::toUpper($string) над ms_strtoupper. А зависимые от фреймворка, лезущие в сеть или в базу, либо слишком долгие вещи проще выносить в обычные классы, чтобы в любой момент можно было их подменить/перекомпоновать/закешировать и т.п., что со статическими методами нативно сделать никак не получится.
Аватара пользователя
S c
Сообщения: 883
Зарегистрирован: 2012.04.11, 14:46

Re: Сервисный слой, как правильно?

Сообщение S c »

Простой вопрос - связи (наши relations) где должны определятся\получаться? в модели либо в сервисе?
К примеру есть User у него есть связь Auth (которая belongs to User по user_id). Вот разделил я пользователя на
1) User (модель)
2) UserService (в сервисе есть методы add\delete\fethById\populate (это типа map(User user, array|UserDTO data) и прочие, только сервис знает, какой репозиторий использовать, PgSqlRepository или FileRepository и все в таком духе)
3) UserRepositoryPgSql

и вот появилась сущность Auth. вот имея объект user (объект модели User) хочу получить его Auth. В сервисе создать метод UserService.getAuth(user) ?
Аватара пользователя
ElisDN
Сообщения: 5845
Зарегистрирован: 2012.10.07, 10:24
Контактная информация:

Re: Сервисный слой, как правильно?

Сообщение ElisDN »

S c писал(а):Простой вопрос - связи (наши relations) где должны определятся\получаться? в модели либо в сервисе?
К примеру есть User у него есть связь Auth (которая belongs to User по user_id).
Связи никуда не деваются. Auth так и будет в $this->auth у User.
Последний раз редактировалось ElisDN 2016.08.04, 10:15, всего редактировалось 1 раз.
Закрыто