Где разместить бизнес-логику в Symfony2?
После прочтения большого количества сообщений и ресурсов, у меня все еще есть некоторые проблемы с известным вопросом о том, "где поставить бизнес-логику"? Чтение qaru.site/info/236277/... и Сообщение в блоге, я считаю, что понял эту проблему хорошо разделяемого кода.
Предположим, у меня есть веб-форма, где вы можете добавить пользователя, который будет добавлен в db. Этот пример включает в себя следующие понятия:
- Форма
- контроллер
- Entity
- Сервис
- Repository
Если я ничего не пропустил, вам нужно создать сущность с некоторыми свойствами, геттерами, сеттерами и т.д., чтобы он сохранялся в db. Если вы хотите получить или записать этот объект, вы будете использовать entityManager
и для "неканонического" запроса entityRepository
(то есть, где вы можете поместить свой запрос "запрос" ).
Теперь вам нужно определить службу (то есть класс PHP с "ленивым" экземпляром) для всей бизнес-логики; это место, где можно поставить "тяжелый" код. После того, как вы зарегистрировали услугу в своем приложении, вы можете использовать ее почти везде, и это связано с повторным использованием кода и т.д.
При рендеринге и отправке формы вы связываете ее с вашей сущностью (и с ограничениями, конечно), и используйте все понятия, определенные выше, чтобы собрать все вместе.
Итак, "old-me" будет писать действие контроллера таким образом:
public function indexAction(Request $request)
{
$modified = False;
if($request->getMethod() == 'POST'){ // submit, so have to modify data
$em = $this->getDoctrine()->getEntityManager();
$parameters = $request->request->get('User'); //form retriving
$id = $parameters['id'];
$user = $em->getRepository('SestanteUserBundle:User')->find($id);
$form = $this->createForm(new UserType(), $user);
$form->bindRequest($request);
$em->flush();
$modified = True;
}
$users = $this->getDoctrine()->getEntityManager()->getRepository('SestanteUserBundle:User')->findAll();
return $this->render('SestanteUserBundle:Default:index.html.twig',array('users'=>$users));
}
"New-me" имеет реорганизованный код следующим образом:
public function indexAction(Request $request)
{
$um = $this->get('user_manager');
$modified = False;
if($request->getMethod() == 'POST'){ // submit, so have to modify data
$user = $um->getUserById($request,False);
$form = $this->createForm(new UserType(), $user);
$form->bindRequest($request);
$um->flushAll();
$modified = True;
}
$users = $um->showAllUser();
return $this->render('SestanteUserBundle:Default:index.html.twig',array('users'=>$users));
}
Где $um
- это настраиваемая услуга, в которой сохраняется весь код, который вы не видите из фрагмента кода # 1 с номером кода # 2.
Итак, вот мои вопросы:
- Получил ли я, наконец, сущность symfony2 и {M} VC вообще?
- Является ли рефактор хорошим? Если нет, то что было бы лучше?
Post Scriptum. Я знаю, что я могу использовать хранилище FOSUserBundle для пользователя и аутентификацию, но это основной пример, чтобы научить себя работе с Symfony.
Кроме того, моя служба была введена ORM.Doctrine. * Для работы (просто записка для тех, кто читал этот вопрос с той же путаницей)
Ответы
Ответ 1
Существует два основных подхода к тому, где можно разместить бизнес-логику: архитектуру SOA и архитектуру, управляемую доменом. Если ваши бизнес-объекты (субъекты) анемичны, я имею в виду, если у них нет бизнес-логики, просто геттеры и сеттеры, тогда вы предпочтете SOA. Однако, если вы строите бизнес-логику внутри своих бизнес-объектов, тогда вы предпочтете другую. Адам Бьен обсуждает эти подходы:
Проект с поддержкой домена с Java EE 6: http://www.javaworld.com/javaworld/jw-05-2009/jw-05-domain-driven-design.html
Архитектуры старых серверов с Java EE 6: http://www.javaworld.com/javaworld/jw-04-2009/jw-04-lean-soa-with-javaee6.html
Его Java, но вы можете получить эту идею.
Ответ 2
Я понимаю, что это старый вопрос, но поскольку у меня была аналогичная проблема, я хотел поделиться своим опытом, надеясь, что это может помочь кому-то.
Мое предложение состояло в том, чтобы ввести командную шину и начать использовать шаблон команды. Рабочий процесс примерно такой:
- Контроллер получает запрос и переводит его в команду (форма может быть использована для этого, и вам может понадобиться некоторый DTO для переноса данных с одного уровня на другой)
- Контроллер отправляет эту команду на командную шину
- Командная шина просматривает обработчик и обрабатывает команду
- Затем контроллер может генерировать ответ на основе того, что ему нужно.
Некоторый код, основанный на вашем примере:
public function indexAction(Request $request)
{
$command = new CreateUser();
$form = $this->createForm(new CreateUserFormType(), $command);
$form->submit($request);
if ($form->isValid()) {
$this->get('command_bus')->handle();
}
return $this->render(
'SestanteUserBundle:Default:index.html.twig',
['users' => $this->get('user_manager')->showAllUser()]
);
}
Тогда ваш обработчик команд (который действительно является частью уровня сервиса) будет отвечать за создание пользователя. Это имеет несколько преимуществ:
- У ваших контроллеров гораздо меньше шансов стать раздутыми, потому что у них мало логики.
- Ваша бизнес-логика отделена от логики приложения (HTTP)
- Ваш код станет более проверяемым
- Вы можете повторно использовать один и тот же обработчик команд, но с данными, поступающими из другого порта (например, CLI).
Есть также несколько недостатков:
- количество классов, необходимых для применения этого шаблона, выше и обычно линейно масштабируется с количеством функций, которые предоставляет ваше приложение.
- Есть больше движущихся частей, и это немного сложнее рассуждать, поэтому кривая обучения для команды может быть немного круче.
Несколько командных шин стоит отметить:
https://github.com/thephpleague/tactician
https://github.com/SimpleBus/MessageBus
Ответ 3
Является ли рефактор хорошим? Если нет, что было бы лучше?
Одна из лучших практик в кармане - использование преобразователей параметров для непосредственного вызова объекта из пользовательского запроса.
Пример из документации Symfony:
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
/**
* @Route("/blog/{id}")
* @ParamConverter("post", class="SensioBlogBundle:Post")
*/
public function showAction(Post $post)
{
}
Подробнее о преобразователях параметров:
http://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/converters.html