Сервисный слой, как правильно?
Re: Сервисный слой, как правильно?
надо понимать, что ddd красиво ложится на богатую бизнес-процессами модель, а на crud ложится тяжко, как мы видим.
Re: Сервисный слой, как правильно?
Если лень продумать структуру и внедрить VO, то делайте сеттеры.slavcodev писал(а):Так все таки можно геттеры и сеттеры или нет?
Re: Сервисный слой, как правильно?
Ну да. Там бездумные сеттеры.slavcodev писал(а):тут defineAsUser, defineAsAdmin, defineCreateTime и тд, все это ИМХО обычные сеттеры анемичной модели. До ДДД тут далеко.
Re: Сервисный слой, как правильно?
Думаю, что crud так или иначе везде присутствует. Как можно делать, например, ERP-систему без crud операций? А бизнес логики в ERP много. Другое дело какой процент этот crud в проекте занимает.zelenin писал(а):надо понимать, что ddd красиво ложится на богатую бизнес-процессами модель, а на crud ложится тяжко, как мы видим.
Re: Сервисный слой, как правильно?
С чего то нужно начинать, не надо так строгоslavcodev писал(а):уже не говоря о нарушении инкапсуляции, что любой может взять и вызвать изменение даты создания, даты обновления, статуса пользователя и другие нехороши вещи.
PS Замечания приняты.
Re: Сервисный слой, как правильно?
я поясню о чем речь.
ddd-домен строится на основе Ubiquitous Language. То есть с использованием поведенческого наименования методов, без высовывания служебных атрибутов наружу итд.
В идеале модель формируется на основе рассказа бизнеса о данном домене: Юзер - это клиент сайта, имеющий имя, статус и роль. При регистрации юзер получает статус not-active и роль user. Админ сайта может активировать юзера. Активация - это смена статуса на active с одновременным присваиванием роли client. Также админ может забанить клиента без смены роли (статус ban).
Разработчик выслушал, перевел рассказ бизнеса в модель:
эта модель - модель, обладающая поведением, - противопоставляется анемичной модели:
И методы типа ban или defineRole, меняющие одно свойство (по сути сеттер), не нарушает парадигмы ddd, если отражает некое поведение бизнес-кейса (например запрос к апи api.domain.com/user/changeRole).
Про crud: если вся работа над User сводится к редактированию формы с именем, статусом и ролью, то это типичная crud-задача, и здесь трудно с ddd разбежаться - максимум можно объединить сеттеры changeStatus/changeRole/changeName в метод update($name, $role, $status). И опять же это не нарушает парадигмы - это просто такой бизнес-кейс.
Не надо теоретизировать в стиле "До ДДД тут далеко" - надо взглянуть на задачу, уточнить и предложить решение. Тема вполне исследовательская.
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;
}
}
Про crud: если вся работа над User сводится к редактированию формы с именем, статусом и ролью, то это типичная crud-задача, и здесь трудно с ddd разбежаться - максимум можно объединить сеттеры changeStatus/changeRole/changeName в метод update($name, $role, $status). И опять же это не нарушает парадигмы - это просто такой бизнес-кейс.
Не надо теоретизировать в стиле "До ДДД тут далеко" - надо взглянуть на задачу, уточнить и предложить решение. Тема вполне исследовательская.
- slavcodev
- Сообщения: 3134
- Зарегистрирован: 2009.04.02, 21:42
- Откуда: Valencia
- Контактная информация:
Re: Сервисный слой, как правильно?
Согласен, я просто увидев "defineCreatedTime" и "defineUpdatedTime" , что 99% не может быть частью UL, сделал вывод, что там везде сеттеры, просто названия их без суффикса "set", что не делает их не сеттерами. И наоборот модель вполне может иметь метод (поведение), начинающийся с "set".zelenin писал(а):Поэтому надо меньше теоретизировать в стиле "До ДДД тут далеко", а взглянуть на задачу, уточнить и предложить решение. Тема вполне исследовательская.
@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!
Re: Сервисный слой, как правильно?
В итоге пришел (пока) к такому варианту:
https://bitbucket.org/nootropil/studyin ... ?at=master
Возможно это не DDD (нет ни VO ни IM и т.д.), а просто использование нескольких шаблонов проектирование. Представленная модель без поведений, но это только начало её существоания
Замечания приветствую!
PS Слишком уж больно отказываться от GridView в средних проекта, поэтому такое решение.
https://bitbucket.org/nootropil/studyin ... ?at=master
Возможно это не DDD (нет ни VO ни IM и т.д.), а просто использование нескольких шаблонов проектирование. Представленная модель без поведений, но это только начало её существоания
Замечания приветствую!
PS Слишком уж больно отказываться от GridView в средних проекта, поэтому такое решение.
Re: Сервисный слой, как правильно?
https://bitbucket.org/nootropil/studyin ... ty.php-178
вот тут можно как раз ввести VO Company и Address, что сократит пополам кол-во аргументов.
в целом что-то типа того.
вот тут можно как раз ввести VO Company и Address, что сократит пополам кол-во аргументов.
в целом что-то типа того.
Re: Сервисный слой, как правильно?
Проблема большого количества полей возникает из-за недостаточного анализа структуры.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;
}
Сейчас у Вас есть, по крайней мере, две явные группы полей, обрабатываемых отдельно в своих методах:
Код: Выделить всё
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;
}
Код: Выделить всё
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;
}
В итоге сущность станет примерно такой:
Код: Выделить всё
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
)
Re: Сервисный слой, как правильно?
http://www.slideshare.net/aaronsaray/en ... d-services лайтовенькое пояснение про сервисы.
Re: Сервисный слой, как правильно?
маппер из слайда я бы еще обернул в репозиторий, который вместо маппера инджектил бы в сервис - и вот теперь у нас полное игнорирование источника данных. Мы можем в репозитории поменять маппер, а сервисы продолжат работу как будто бы ничего не изменилось.SiZE писал(а):http://www.slideshare.net/aaronsaray/en ... d-services лайтовенькое пояснение про сервисы.
Re: Сервисный слой, как правильно?
Предпочитаю потяжелее: http://www.slideshare.net/rosstuck/mode ... hobgoblinsSiZE писал(а):лайтовенькое пояснение про сервисы.
Re: Сервисный слой, как правильно?
Не большой вопрос по 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);
}
}
Re: Сервисный слой, как правильно?
и так и так норм, но хэндлер по сути это и есть сервис сервисного слоя, только реализующий паттерн Command. Поэтому если есть логическая необходимость какой-то функционал инкапсулировать в сервис, то ок, а иначе оверхед.
Re: Сервисный слой, как правильно?
Код: Выделить всё
$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);
Re: Сервисный слой, как правильно?
Ещё вопрос есть команда логина LoginCommand.
Есть событие LoginEvent, которое выбрасывается после успешного логина. Так вот, кто должен его бросать? Хендлер LoginHandler или сервис authService (который отвечает за вход и его метод login() вызывается в хендлере)?
Есть событие LoginEvent, которое выбрасывается после успешного логина. Так вот, кто должен его бросать? Хендлер LoginHandler или сервис authService (который отвечает за вход и его метод login() вызывается в хендлере)?
Re: Сервисный слой, как правильно?
во-первых, перекладываем ответственность за создание модели на фабрикуslavcodev писал(а):Чем лучше?zelenin писал(а):лучше так
во-вторых, убираем несоответствие в семантике типа changeRole, где на самом деле не смена, а установка роли происходит