Principio de Responsabilidad Única en los controladores: Request Handlers

Manu Pijierro
5 min readFeb 5, 2018

Cuando se desarrolla una aplicación en la que se utilice alguno de los muchos frameworks MVC que existen en el mercado, las clases que representan los controladores (controllers), suelen ser grandes olvidadas en cuanto al cuidado y detalle de su programación.

Resumiendo, la función de un controller sería gestionar la petición HTTP recibida y retornar una respuesta adecuada. Esa gestión de la petición no debería incluir ninguna lógica de negocio, es decir, el controller únicamente debería obtener la información necesaria de la petición (request) para resolver el problema, encapsularla y enviarla a las clases o componentes que si que resolverán la petición de forma adecuada y estos, a su vez, le devolverán al controller la información para construir la respuesta.

El objetivo es tener desacopladas nuestra lógica de negocio de la entrada de datos a la misma y las ventajas recaen, por ejemplo, en una mayor y más fácil reutilización de código de nuestra aplicación ya que estará reimplementada de forma que podremos resolver peticiones sin que nos importe el mecanismo de entrada de las mismas, esto es: HTTP, Apis, consola de comandos o incluso tests, lo cual, es otra de las ventajas ya que aumentamos la testeabilidad de nuestros modelos y lógica de negocio.

Dicho lo anterior, ¿qué tal si hacemos cumplir el principio de responsabilidad única en los controladores? no sé si históricamente, pero yo al menos, desde siempre, he visto (y también hecho) controladores que han tenido multitud de responsabilidades aglutinando en su interior no sólo código con lógica de negocio, si no además multitud de métodos que daban pie a resolver procesos distintos en un solo controlador, rompiendo por completo el principio de responsabilidad única y generando código muy acoplado difícil de mantener. ¿No sería más simple tener un controlador para resolver una petición?

Request Handlers

Este término lo leí por primera vez casi de casualidad por un enlace de Twitter en un post de Jens Segers, titulado Goodbye Controllers, hello request handlers, y al cual hace honor este post. En el mismo se hace referencia a todo lo anterior y a como ‘convertir’ nuestros controladores cargados de código y responsabilidades en controladores con una única responsabilidad. El concepto de request handler es muy simple y se trata básicamente de un controlador con una única acción. El concepto es similar al patrón Action-Domain-Responder propuesto por Paul M. Jones como alternativa al patrón MVC.

En este patrón, el Action es la lógica que conecta al Domain con la respuesta, invocando al Domain con las entradas recogidas de la petición HTTP y luego invocando al Responder con los datos necesarios para construir una respuesta HTTP. El Domain, es la entrada a la lógica de negocio que representa el core o dominio de nuestra aplicación. Este, es el que realiza el tratamiento de los datos y, entre otros, podría ser una capa de servicio, de aplicación…etc. El Responder se trataría de la lógica de presentación, es decir, la lógica que tiene que construir la respuesta HTTP con los datos recibidos del Action. Digamos que es la encargada de manejarse con los código de estado HTTP, headers, cookies, el propio contenido, plantillas, vistas…etc.

Principio de Responsabilidad Única en controladores

Para buscar, conseguir y mantener el primero de los principios SOLID, el principio de responsabilidad única, podríamos ayudarnos definir el controller con un solo método público que sería el encargado de resolver el caso de uso para el que estuviera pensado. Como propuesta para implementar y facilitar un Request Handler con PHP, podemos utilizar uno de los métodos mágicos que nos aporta el lenguaje: __invoke()

Las clases ‘invokables’, son clases convertidas en Callbacks/Callables, es decir, pueden ser llamadas como función.

Esto que puede parecer muy simple, podría ayudarnos a mantener el principio de responsabilidad única, ya que si una clase implementa el método __invoke() podríamos estar declarando de forma implícita cual es la acción, funcionalidad o responsabilidad de esa clase. Cuando una clase, quitando los getters y setters, tiene más de un método público, puede ser un síntoma de que la clase tiene más de una responsabilidad. Trabajando con __invoke(), como hemos dicho, podríamos ser conscientes de que no hay que añadir otro método público ya que solo deberíamos tener __invoke(). Esto nos haría cumplir más fácilmente con el principio de responsabilidad única, lo que nos llevaría a tener un controlador por acción o por caso de uso con un único método __invoke().

El ejemplo siguiente muestra como se desgranaría por ejemplo un controlador llamado UserController con varios métodos (responsabilidades) en su interior para mostrar el formulario y actualizar un usuario. Mirando el código resultante, podemos observar dos clases (dos request handlers) con una única responsabilidad cada uno.

Otro ejemplo podría ponerlo con el código del último proyecto que estoy actualmente implementando, del que hablaré muy pronto y en el cual he seguido esta patrón de request handlers, de forma que mis controladores quedan más o menos de este estilo.

En mi opinión mucho más limpio que los que escribía antes.

Single Action Controller

En frameworks como Laravel o Slim, esta estrategia de implementación de controladores con métodos __invoke() es conocida como Single Action Controller.

La ventaja de esto, a parte de las comentadas anteriormente, es que al crear las rutas de nuestra aplicación no hace falta indicar el método del controlador al que queremos llamar.

Llamada a controladores que NO implementan el método __invoke()

Route::get('user/{id}', 'UserController@show');

Llamadas a controladores que SI implementan invoke()

Route::get('user/{id}', 'ShowProfile');

Bola Extra

Revisando los comentarios de la entrada de Jens Segers, varios programadores dejan indicado que convendría revisar el patrón PORTO, en el cual según su propia descripción, indica:

Porto SAP, es un patrón de arquitectura de software, diseñado para ayudar a los desarrolladores a organizar su código para conseguir una alta mantenibilidad. Su principal objetivo es organizar la lógica de negocio para su posterior reutilización. Porto es una alternativa al estándar MVC.

Hasta aquí esta entrada que espero os haya parecido interesante y os ayude a darle una vuelta de tuerca a como enfocáis el diseño e implementación de vuestros controladores.

Chimpún.

Bibliografía

Goodbye controllers, hello request handlers, de Jens Segers.

Patrón Action Domain Responder, de Paul M. Jones.

--

--