model view controller - Dónde ubicar la lógica de negocios en Symfony2?
model-view-controller (4)
Después de leer muchos posts y recursos de Stack Overflow, todavía tengo algunos problemas sobre la famosa pregunta sobre "¿dónde poner la lógica empresarial?" Leyendo StackOverflow Question y una entrada de blog , creo que he entendido bien el tema de la separación de código.
Supongamos que tengo un formulario web donde puede agregar un usuario que se agregará a un db. Este ejemplo involucra estos conceptos:
- Formar
- Controlador
- Entidad
- Servicio
- Repositorio
Si no me perdí algo, debes crear una entidad con algunas propiedades, getters, setters, etc. para que persista en un db. Si desea buscar o escribir esa entidad, usará entityManager
y, para la consulta "no canónica", entityRepository
(que es donde puede ajustarse a su consulta de "lenguaje de consulta").
Ahora debe definir un servicio (que es una clase PHP con una instancia "diferida") para toda la lógica comercial; este es el lugar para poner el código "pesado". Una vez que haya grabado el servicio en su aplicación, puede usarlo en casi todas partes y eso implica la reutilización del código, etc.
Cuando renderiza y publica un formulario, lo vincula con su entidad (y, por supuesto, con restricciones) y utiliza todos los conceptos definidos anteriormente para unir todos.
Entonces, el "viejo yo" escribiría la acción de un controlador de esta manera:
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" ha refactorizado el código de esta manera:
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));
}
Donde $um
es un servicio personalizado donde se almacena todo el código que no se puede ver desde la pieza de código n. ° 1 hasta la pieza de código n. ° 2.
Asi que aqui están mis preguntas:
- ¿Obtuve finalmente la esencia de symfony2 y {M} VC en general?
- ¿El refactor es bueno? Si no, ¿cuál sería una mejor manera?
Post Scriptum : Sé que puedo usar FOSUserBundle para la tienda de usuario y la autenticación, pero este es un ejemplo básico para aprender cómo trabajar con Symfony. Además, mi servicio fue inyectado con ORM.Doctrine. * Para que funcione (solo una nota para quien lea esta pregunta con la misma confusión)
¿El refactor es bueno? Si no, ¿cuál sería una mejor manera?
Una de las mejores prácticas marco es usar convertidores param para invocar directamente a una entidad desde la solicitud del usuario .
Ejemplo de la documentación de 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)
{
}
Más sobre convertidores param :
http://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/converters.html
Hay dos enfoques principales con respecto a dónde ubicar la lógica comercial: la arquitectura SOA y la arquitectura basada en dominio. Si sus objetos de negocios (entidades) son anémicos, quiero decir, si no tienen lógica comercial, solo captadores y establecedores, entonces preferirán SOA. Sin embargo, si construye la lógica comercial dentro de sus objetos comerciales, entonces preferirá la otra. Adam Bien analiza estos enfoques:
Diseño impulsado por dominio con Java EE 6: http://www.javaworld.com/javaworld/jw-05-2009/jw-05-domain-driven-design.html
Arquitecturas de servicios Lean con Java EE 6: http://www.javaworld.com/javaworld/jw-04-2009/jw-04-lean-soa-with-javaee6.html
Es Java, pero puedes entender la idea.
Me doy cuenta de que esta es una vieja pregunta, pero dado que tenía un problema similar, quería compartir mi experiencia, con la esperanza de que podría ser de ayuda para alguien. Mi sugerencia sería introducir un bus de comando y comenzar a usar el patrón de comando. El flujo de trabajo es más o menos así:
- El controlador recibe la solicitud y la traduce a un comando (se puede usar un formulario para hacer eso, y es posible que necesite algo de DTO para mover datos de una capa a otra).
- El controlador envía ese comando al bus de comando
- El bus de comando busca un controlador y maneja el comando
- El controlador puede generar la respuesta en función de lo que necesita.
Algún código basado en tu ejemplo:
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()]
);
}
Entonces su controlador de comando (que es realmente parte de la capa de servicio) sería responsable de crear al usuario. Esto tiene varias ventajas:
- Sus controladores tienen muchas menos probabilidades de hincharse, porque tienen poca o ninguna lógica
- Su lógica de negocio está separada de la lógica de la aplicación (HTTP)
- Tu código se vuelve más testable
- Puede reutilizar el mismo controlador de comandos pero con datos procedentes de un puerto diferente (por ejemplo, CLI)
También hay un par de inconvenientes:
- la cantidad de clases que necesita para aplicar este patrón es mayor y generalmente se amplía linealmente con la cantidad de funciones que expone su aplicación
- hay más piezas móviles y es un poco más difícil razonar, por lo que la curva de aprendizaje para un equipo puede ser un poco más pronunciada.
Un par de buses de comando que vale la pena mencionar:
https://github.com/thephpleague/tactician https://github.com/SimpleBus/MessageBus
Robert C. Martin (el chico del código limpio) dice en su nueva arquitectura limpia de libros, que debe poner su lógica de negocios independientemente de su framerwork, porque el marco cambiará con el tiempo.
De modo que puede poner su lógica de negocios en una carpeta separada como App / Core o App / Manager y evitar aquí las clases de Symfony:
<?php
namespace App/Core;
class UserManager extends BaseManager implements ManagerInterface
{
}