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

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

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

Сообщение zelenin »

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

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

Сообщение ElisDN »

slavcodev писал(а):Так все таки можно геттеры и сеттеры или нет? :)
Если лень продумать структуру и внедрить VO, то делайте сеттеры.
Аватара пользователя
ElisDN
Сообщения: 5845
Зарегистрирован: 2012.10.07, 10:24
Контактная информация:

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

Сообщение ElisDN »

slavcodev писал(а):тут defineAsUser, defineAsAdmin, defineCreateTime и тд, все это ИМХО обычные сеттеры анемичной модели. До ДДД тут далеко.
Ну да. Там бездумные сеттеры.
nootropil
Сообщения: 46
Зарегистрирован: 2015.11.21, 18:45

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

Сообщение nootropil »

zelenin писал(а):надо понимать, что ddd красиво ложится на богатую бизнес-процессами модель, а на crud ложится тяжко, как мы видим.
Думаю, что crud так или иначе везде присутствует. Как можно делать, например, ERP-систему без crud операций? А бизнес логики в ERP много. Другое дело какой процент этот crud в проекте занимает.
nootropil
Сообщения: 46
Зарегистрирован: 2015.11.21, 18:45

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

Сообщение nootropil »

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


PS Замечания приняты.
Аватара пользователя
slavcodev
Сообщения: 3134
Зарегистрирован: 2009.04.02, 21:42
Откуда: Valencia
Контактная информация:

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

Сообщение slavcodev »

@nootropil, Я очень даже дружелюбный :)
Жду Yii 3!
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

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

Сообщение zelenin »

я поясню о чем речь.
ddd-домен строится на основе Ubiquitous Language. То есть с использованием поведенческого наименования методов, без высовывания служебных атрибутов наружу итд.
В идеале модель формируется на основе рассказа бизнеса о данном домене: Юзер - это клиент сайта, имеющий имя, статус и роль. При регистрации юзер получает статус not-active и роль user. Админ сайта может активировать юзера. Активация - это смена статуса на active с одновременным присваиванием роли client. Также админ может забанить клиента без смены роли (статус ban).
Разработчик выслушал, перевел рассказ бизнеса в модель:

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

<?php

class User
{
    private $name;
    private $status;
    private $role;

    public function __construct($name, $status, $role)
    {
        $this->name = $name;
        $this->status = $status;
        $this->role = $role;
    }

    public static function register($name)
    {
        return new User($name, 'not-active', 'user');
    }

    public function activate()
    {
        $this->status = 'active';
        $this->role = 'client';
    }

    public function ban()
    {
        $this->status = 'ban';
    }

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

    public function role()
    {
        return $this->role;
    }

    public function status()
    {
        return $this->status;
    }
}
эта модель - модель, обладающая поведением, - противопоставляется анемичной модели:

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

<?php

class User
{
    private $name;
    private $status;
    private $role;

    public function __construct($name, $status, $role)
    {
        $this->name = $name;
        $this->status = $status;
        $this->role = $role;
    }

    public function getName()
    {
        return $this->name;
    }

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

    public function getStatus()
    {
        return $this->status;
    }

    public function setStatus($status)
    {
        $this->status = $status;
    }

    public function getRole()
    {
        return $this->role;
    }

    public function setRole($role)
    {
        $this->role = $role;
    }
}
И методы типа ban или defineRole, меняющие одно свойство (по сути сеттер), не нарушает парадигмы ddd, если отражает некое поведение бизнес-кейса (например запрос к апи api.domain.com/user/changeRole).
Про crud: если вся работа над User сводится к редактированию формы с именем, статусом и ролью, то это типичная crud-задача, и здесь трудно с ddd разбежаться - максимум можно объединить сеттеры changeStatus/changeRole/changeName в метод update($name, $role, $status). И опять же это не нарушает парадигмы - это просто такой бизнес-кейс.

Не надо теоретизировать в стиле "До ДДД тут далеко" - надо взглянуть на задачу, уточнить и предложить решение. Тема вполне исследовательская.
Аватара пользователя
slavcodev
Сообщения: 3134
Зарегистрирован: 2009.04.02, 21:42
Откуда: Valencia
Контактная информация:

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

Сообщение slavcodev »

zelenin писал(а):Поэтому надо меньше теоретизировать в стиле "До ДДД тут далеко", а взглянуть на задачу, уточнить и предложить решение. Тема вполне исследовательская.
Согласен, я просто увидев "defineCreatedTime" и "defineUpdatedTime" , что 99% не может быть частью UL, сделал вывод, что там везде сеттеры, просто названия их без суффикса "set", что не делает их не сеттерами. И наоборот модель вполне может иметь метод (поведение), начинающийся с "set".

@nootropil, еще мелкие мысли

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

"getIsActive" - имя не самое подходящее, "isActive" - лучше. Убедиться можно чтением кода
"if ($user->getIsActive())" - "if user get is active doing something"
"if ($user->isActive())" - "if user is active doing something"
Жду Yii 3!
nootropil
Сообщения: 46
Зарегистрирован: 2015.11.21, 18:45

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

Сообщение nootropil »

В итоге пришел (пока) к такому варианту:
https://bitbucket.org/nootropil/studyin ... ?at=master

Возможно это не DDD (нет ни VO ни IM и т.д.), а просто использование нескольких шаблонов проектирование. Представленная модель без поведений, но это только начало её существоания :D

Замечания приветствую!

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

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

Сообщение zelenin »

https://bitbucket.org/nootropil/studyin ... ty.php-178
вот тут можно как раз ввести VO Company и Address, что сократит пополам кол-во аргументов.

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

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

Сообщение ElisDN »

nootropil писал(а):В итоге пришел (пока) к такому варианту...
Проблема большого количества полей возникает из-за недостаточного анализа структуры.

В данном примере у Вас всё в одной куче, как наследие ActiveRecord:

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

final class Counterparty implements Entity
{
    private $id;
    private $companyName;
    private $companyFullName;
    private $propertyForm;
    private $companyType;
    private $description;
    private $status;
    private $department;
    private $contactPerson;
    private $address;
    private $phone;
    private $fax;
    private $email;
    private $secondEmail;
    private $registeredOffice;
    private $taxIdentificationNumber;
    private $giro;
    private $rcbic;
    private $correspondentAccount;
    private $coreStateRegistrationNumber;
    private $currency;
    private $manager;
    private $city;
    private $parentCompany;
}
При работе с сущности как с просто классами нужно победить в себе ограничения ActiveRecord, требующие одноуровневость полей, и привыкнуть к полной свободе действий.

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

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

public function changeStageProps(...)
{
    $this->registeredOffice = $registeredOffice;
    $this->taxIdentificationNumber = $taxIdentificationNumber;
    $this->giro = $giro;
    $this->rcbic = $rcbic;
    $this->correspondentAccount = $correspondentAccount;
    $this->coreStateRegistrationNumber = $coreStateRegistrationNumber;
}

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

public function changeContacts(...)
{
    $this->cityId = $cityId;
    $this->address = $address;
    $this->phone = $phone;
    $this->fax = $fax;
    $this->email = $email;
    $this->secondEmail = $secondEmail;
}
Также можно выделить неявную группу, опираясь на то, что несколько полей названы со словом company*:

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

private $companyName;
private $companyFullName;
private $propertyForm;
private $companyType;
private $parentCompany;
В итоге при более подробном анализе можно разбить поля на смысловые группы:

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

final class Counterparty implements Entity
{
    // Общее
    private $id;    
    private $description;
    private $status;
    private $department;    
    private $currency;
    private $manager;
    
    // Company
    private $companyName;
    private $companyFullName;
    private $propertyForm;
    private $companyType;
    private $parentCompanyId;
    private $parentCompany;
    
    // Contact
    private $city;
    private $address;
    private $phone;
    private $fax;
    private $email;
    private $secondEmail;
    private $contactPerson;
    
    // Stage
    private $registeredOffice;
    private $taxIdentificationNumber;
    private $giro;
    private $rcbic;
    private $correspondentAccount;
    private $coreStateRegistrationNumber;
}
И выделить эти группы (избавившись от префиксов вроде company*) в отдельные структуры.

В итоге сущность станет примерно такой:

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

final class Counterparty implements Entity
{
    private $id;
    private $description;
    private $status;
    private $department;
    private $currency;
    private $manager;
    private $company;      final class Company {
                                         private $name;
                                         private $fullName;
                                         private $propertyForm;
                                         private $type;
                                         private $parent;
                                     }
    private $contact;       final class Contact {    
                                         private $city;
                                         private $address;
                                         private $phone;
                                         private $fax;
                                         private $email;
                                         private $secondEmail;
                                         private $person;
                                     }    
    private $stage;          final class Stage  {
                                         private $registeredOffice;
                                         private $taxIdentificationNumber;
                                         private $giro;
                                         private $rcbic;
                                         private $correspondentAccount;
                                         private $coreStateRegistrationNumber;
                                     }
}
Теперь конструктор становится намного проще и прятнее:

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

private function __construct(
    string $id,
    string $description,
    int $status,
    $department,
    $manager,
    $currency,
    Company $company,
    Contact $contact,
    Stage $stage
)
Аватара пользователя
SiZE
Сообщения: 2813
Зарегистрирован: 2011.09.21, 12:39
Откуда: Perm
Контактная информация:

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

Сообщение SiZE »

http://www.slideshare.net/aaronsaray/en ... d-services лайтовенькое пояснение про сервисы.
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

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

Сообщение zelenin »

SiZE писал(а):http://www.slideshare.net/aaronsaray/en ... d-services лайтовенькое пояснение про сервисы.
маппер из слайда я бы еще обернул в репозиторий, который вместо маппера инджектил бы в сервис - и вот теперь у нас полное игнорирование источника данных. Мы можем в репозитории поменять маппер, а сервисы продолжат работу как будто бы ничего не изменилось.
Аватара пользователя
ElisDN
Сообщения: 5845
Зарегистрирован: 2012.10.07, 10:24
Контактная информация:

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

Сообщение ElisDN »

SiZE писал(а):лайтовенькое пояснение про сервисы.
Предпочитаю потяжелее: http://www.slideshare.net/rosstuck/mode ... hobgoblins
Melodic
Сообщения: 87
Зарегистрирован: 2016.05.11, 17:43
Откуда: Луганск

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

Сообщение Melodic »

Не большой вопрос по CommandBus.

Может ли хэндлер напрямую работать с моделью или хенедлер должен вызывать какие то методы сервисов?

К примеру как правильней будет?

Так, где всё происходит в хендлере

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

class CreateUserHandler
{

    private $userRepository;

    public function __construct(UserRepositoryInterface $userRepositoryInterface)
    {
        $this->userRepository = $userRepositoryInterface;
    }

    public function handle(CreateUserCommand $command)
    {
        $user = new User($this->userRepository->nextIdentity());
        $user->setLogin($command->login);
        $user->setPassword($command->password);
        $user->changeRole($command->role);
        $this->userRepository->save($user);
    }
}
или так , где всё происходит в сервисе

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

class CreateUserHandler
{

    private $userService;

    public function __construct(UserServiceInterface $userServiceInterface)
    {
        $this->userService = $userServiceInterface;
    }

    public function handle(CreateUserCommand $command)
    {
        $this->userService->register($command->login,$command->password,$command->role);
    }
}
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

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

Сообщение zelenin »

и так и так норм, но хэндлер по сути это и есть сервис сервисного слоя, только реализующий паттерн Command. Поэтому если есть логическая необходимость какой-то функционал инкапсулировать в сервис, то ок, а иначе оверхед.
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

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

Сообщение zelenin »

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

$user = new User($this->userRepository->nextIdentity());
$user->setLogin($command->login);
$user->setPassword($command->password);
$user->changeRole($command->role);
$this->userRepository->save($user); 
лучше так:

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

$user = User::register($this->userRepository->nextIdentity(), $command->login, $command->password, $command->role);
// или еще лучше
$user = $this->userFactory->register($this->userRepository->nextIdentity(), $command->login, $command->password, $command->role);

$this->userRepository->save($user);
 
Аватара пользователя
slavcodev
Сообщения: 3134
Зарегистрирован: 2009.04.02, 21:42
Откуда: Valencia
Контактная информация:

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

Сообщение slavcodev »

zelenin писал(а):лучше так
Чем лучше?
Жду Yii 3!
Melodic
Сообщения: 87
Зарегистрирован: 2016.05.11, 17:43
Откуда: Луганск

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

Сообщение Melodic »

Ещё вопрос есть команда логина LoginCommand.
Есть событие LoginEvent, которое выбрасывается после успешного логина. Так вот, кто должен его бросать? Хендлер LoginHandler или сервис authService (который отвечает за вход и его метод login() вызывается в хендлере)?
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

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

Сообщение zelenin »

slavcodev писал(а):
zelenin писал(а):лучше так
Чем лучше?
во-первых, перекладываем ответственность за создание модели на фабрику
во-вторых, убираем несоответствие в семантике типа changeRole, где на самом деле не смена, а установка роли происходит
Закрыто