¿Cómo determinar qué debería tener su propio controlador respectivo?

10

Estoy usando el patrón MVC en mi aplicación web creada con PHP.

Siempre me cuesta determinar si necesito un nuevo controlador dedicado para un conjunto de acciones o si debo ubicarlos dentro de un controlador ya existente.

¿Hay alguna buena regla a seguir al crear controladores?

Por ejemplo, puedo tener:

AuthenticationController con acciones:

  • index() para mostrar el formulario de inicio de sesión.
  • submit() para manejar el envío de formularios.
  • logout(), Autoexplicativo.

O

LoginController con acciones:

  • index() para mostrar el formulario de inicio de sesión.
  • submit() para manejar el envío de formularios.

LogoutController con acción:

  • index() para manejar el cierre de sesión.

O

AccountController con acciones:

  • loginGet() para mostrar el formulario de inicio de sesión.
  • loginPost() para manejar el envío del formulario de inicio de sesión.
  • logoutGet() para manejar el cierre de sesión.
  • registerGet() para mostrar el formulario de registro.
  • registerPost() para manejar el envío de formularios.

    Y cualquier otra acción relacionada con una cuenta.

Kid Diamond
fuente
Quizás eche un vistazo al diseño RESTful. No resuelve todos los problemas de este tipo, pero le da una muy buena dirección sobre cómo pensarlo.
thorsten müller

Respuestas:

3

Para encontrar la agrupación correcta para los controladores, piense en las pruebas .

(Incluso si en realidad no realiza ninguna prueba, pensar en cómo realizaría la prueba de sus controladores le dará una muy buena idea de cómo estructurarlos).

Un AuthenticationControllerno es comprobable por sí mismo, porque solo contiene funcionalidades para iniciar y cerrar sesión, pero su código de prueba necesitará crear cuentas falsas para propósitos de prueba antes de que pueda probar un inicio de sesión exitoso. Puede omitir el subsistema bajo prueba e ir directamente a su modelo para la creación de las cuentas de prueba, pero luego tendrá una prueba frágil en sus manos: si el modelo cambia, tendrá que modificar no solo el código de las pruebas el modelo, pero también el código que prueba el controlador, a pesar de que la interfaz y el comportamiento del controlador no han cambiado. Eso no es razonable.

A LoginControllerno es adecuado por las mismas razones: no puede probarlo sin crear cuentas primero, y hay aún más cosas que no puede probar, como por ejemplo evitar inicios de sesión duplicados pero luego permitir que un usuario inicie sesión después de haber cerrado sesión. (Dado que este controlador no tiene funcionalidad de cierre de sesión).

Y AccountControllerle dará todo lo que necesita para hacer su prueba: puede crear una cuenta de prueba y luego intentar iniciar sesión, puede eliminar la cuenta y luego asegurarse de que ya no puede iniciar sesión, puede cambiar la contraseña y asegurarse de que la contraseña correcta debe usarse para iniciar sesión, etc.

Para concluir: para escribir incluso el conjunto de pruebas más pequeño, necesitará poner a su disposición toda la funcionalidad del AccountControllermismo. Subdividirlo en controladores más pequeños parece estar produciendo controladores para discapacitados con una funcionalidad insuficiente para una prueba adecuada. Esta es una muy buena indicación de que la funcionalidad de AccountControlleres la subdivisión más pequeña que tiene sentido.

Y en términos generales, el enfoque de "pensar en la prueba" funcionará no solo en este escenario en particular, sino en cualquier escenario similar que se encuentre en el futuro.

Mike Nakis
fuente
1

La respuesta no es tan obvia

Permítanme aclarar algunas cosas antes de hacer cualquier declaración de respuesta. Ante todo:

¿Qué es el controlador?

El controlador es una parte del sistema que controla la solicitud, después del despacho. Por lo tanto, podemos definirlo como un conjunto de acciones relacionadas con ... ¿qué?

¿Cuál es el alcance del controlador?

Y eso es más o menos parte cuando tendremos alguna respuesta. ¿Qué piensas? ¿Es un controlador de cosas (por ejemplo, una cuenta) o un controlador de acciones? Por supuesto, es un controlador de algún modelo o algo más abstracto que proporciona acciones sobre él.

La respuesta es...

AuthenticationController con acciones:

  • index () para mostrar el formulario de inicio de sesión.
  • submit () para manejar el envío del formulario.
  • cerrar sesión (), se explica por sí mismo.

No, la autenticación es un proceso. No vayas por ese camino.

LoginController con acciones:

  • index () para mostrar el formulario de inicio de sesión.
  • submit () para manejar el envío del formulario.

Igual que aquí. Inicio de sesión - acción. Mejor no cree un controlador de acción (no tiene un modelo correlacionado).

AccountController con acciones:

  • loginGet () para mostrar el formulario de inicio de sesión.
  • loginPost () para manejar el envío del formulario de inicio de sesión.
  • logoutGet () para manejar el cierre de sesión.
  • registerGet () para mostrar el formulario de registro.
  • registerPost () para manejar el envío de formularios.

Bastante bien, pero no estoy convencido de que valga la pena construir ese controlador de bajo nivel (el controlador es la abstracción en sí mismo). De todos modos, la creación de métodos con * Get o * Post no está clara.

¿Cualquier sugerencia?

Sí, considérelo:

AccountController:

  • iniciar sesión (AccountModel)
  • cerrar sesión (AccountModel)
  • registrarse (AccountModel)
  • índice()

Y modelo relacionado con él, ofc Clase de cuenta. Le dará la oportunidad de mover su par modelo-controlador a otro lugar (si es necesario) y crear un código claro (es obvio lo login()que significa el método). Apestar al modelo es realmente famoso, especialmente con las aplicaciones CRUD y tal vez sea una forma para ti.

Dawid Pura
fuente
1

Los controladores generalmente se crean para un determinado recurso (una clase de entidad, una tabla en la base de datos), pero también se pueden crear para agrupar acciones que son responsables de una determinada parte de la aplicación. En sus ejemplos, ese sería un controlador que maneja la seguridad de la aplicación:

class SecurityController
{
    // can handle both the login page display and
    // the login page submission
    login(); 

    logout();

    register();

    // optional: confirm account after registration
    confirm();

    // displays the forgot password page
    forgotPassword();

    // displays the reset password page
    // and handle the form submission
    resetPassword();
}

Nota : no coloque las acciones relacionadas con la seguridad y las acciones del perfil de usuario en el mismo controlador; puede tener sentido porque están relacionados con el usuario, pero uno debe manejar la autenticación y el otro debe manejar las actualizaciones de correo electrónico, nombre, etc.

Con los controladores creados para los recursos (digamos Task), tendría las acciones CRUD habituales :

class TasksController
{
    // usually displays a paginated list of tasks
    index();

    // displays a certain task, based on an identifier
    show(id);

    // displays page with form and
    // handles form submission for creating
    // new tasks
    create();

    // same as create(), but for changing records
    update(id);     

    // displays confirmation message
    // and handles submissions in case of confirmation
    delete()
}

Por supuesto, tiene la posibilidad de agregar recursos relacionados al mismo controlador. Digamos, por ejemplo, que tiene la entidad Business, y cada una tiene varias BusinessServiceentidades. Un controlador para esto podría verse así:

class BusinessController
{
    index();

    show(id);

    create();

    update(id);

    delete();

    // display the business services for a certain business
    listBusinessServices(businessId);

    // displays a certain business service
    showBusinessService(id);

    // create a new business service for a certain business
    createBusinessService(businessId);

    // updates a certain business service
    updateBusinessService(id);

    // deletes a certain business service
    deleteBusinessService(id);
}

Este enfoque tiene sentido cuando las entidades secundarias relacionadas no pueden existir sin la entidad principal.

Estas son mis recomendaciones:

  • crear controladores basados ​​en un grupo de operaciones relacionadas (manejo de ciertas responsabilidades como seguridad u operaciones CRUD en recursos, etc.);
  • para controladores basados ​​en recursos, no agregue acciones innecesarias (si se supone que no debe actualizar el recurso, no agregue la acción de actualización);
  • puede agregar acciones "personalizadas" para simplificar las cosas (por ejemplo, tiene una Subscriptionentidad que tiene una disponibilidad basada en un número limitado de entradas, puede agregar una nueva acción al controlador nombrado use()que tiene el único propósito de restar una entrada de la Subscription)
  • mantenga las cosas simples: no desordene su controlador con una gran cantidad de acciones y una lógica compleja, intente simplificar las cosas disminuyendo la cantidad de acciones o haciendo dos controladores;
  • Si está utilizando un marco centrado en MVC, siga sus pautas de mejores prácticas (si lo tienen).

Algunos recursos para leer más aquí .

qretzu
fuente
0

Veo dos "fuerzas" de diseño antagónicas (que no son exclusivas de los controladores):

  • cohesión: los controladores deben agrupar acciones relacionadas
  • simplicidad: los controladores deben ser lo más pequeños posible para gestionar su complejidad

Desde el punto de vista de la cohesión, las tres acciones (inicio de sesión, cierre de sesión, registro) están relacionadas, pero el inicio y cierre de sesión es mucho más que el registro. Están relacionados semánticamente (uno es una inversión del otro) y posiblemente también usarán los mismos objetos de servicio (sus implementaciones también son coherentes).

Mi primer instinto sería agrupar el inicio y cierre de sesión en un controlador. Pero si las implementaciones del controlador de inicio y cierre de sesión no son tan simples (por ejemplo, el inicio de sesión tiene captcha, más métodos de autenticación, etc.), no tendría ningún problema en dividirlos en LoginController y LogoutController para mantener la simplicidad. Donde se encuentra este umbral de complejidad (cuando debería comenzar a dividir el controlador) es un poco personal.

Recuerde también que, independientemente de lo que diseñe su código inicialmente, puede (y debería) refactorizarlo a medida que cambia. En este caso, es bastante típico comenzar con un diseño simple (tener un AuthenticationController) y con el tiempo recibirá más requisitos que complicarán el código. Una vez que cruza el umbral de complejidad, debe refactorizarlo a dos controladores.

Por cierto, su código sugiere que está cerrando la sesión del usuario con la solicitud GET. Esa es una mala idea ya que HTTP GET debería ser nullipotente (no debería modificar el estado de la aplicación).

qbd
fuente
0

Aquí hay algunas reglas generales:

  • Organice por tema o tema, siendo el nombre del controlador el nombre del tema.

  • Recuerde que el nombre del controlador aparecerá en la URL, visible para sus usuarios, por lo que preferiblemente debería tener sentido para ellos.

En la situación que menciona (autenticación), el equipo de MVC ya ha escrito el controlador para usted. Abra Visual Studio 2013 y luego haga clic en

File / New / Project... 
Search installed templates for "ASP.NET MVC4 Web Application"
Choose "Internet Application" / OK.

AccountController.cs contiene todos los métodos para administrar cuentas de usuario:

Login()
Logoff()
Register()
Disassociate()
Manage()
ExternalLogin()

Entonces se han organizado por tema "Cuentas de usuario y autenticación", con el nombre visible del tema "Cuenta".


fuente
0

Terminología

Creo que es una gran idea errónea llamar a una clase que contiene algunos métodos relacionados con HTTP un "controlador".

El controlador es un método que maneja la solicitud, pero no una clase que contenga dichos métodos . Así, index(), submit(), logout()son controladores.

La clase que contiene ese tipo de métodos se denomina "controlador" simplemente porque constituye un grupo de controladores y desempeña un papel de espacio de nombres de "nivel inferior". En lenguaje FP (como Haskell) sería solo un módulo. Es una buena práctica mantener esas clases de "controlador" lo más sin estado posible en los lenguajes OOP, excepto las referencias a servicios y otras cosas de todo el programa.

La respuesta

Con la terminología ordenada, la pregunta es "¿cómo debemos separar los controladores en espacios de nombres / módulos?" Creo que la respuesta es: los controladores dentro de un solo espacio de nombres / módulo deben tratar con el mismo tipo de datos . Por ejemplo, UserControllertrata principalmente con instancias de Userclase, pero ocasionalmente toca otras cosas relacionadas, si es necesario.

Dado que login, logouty otras acciones similares se ocupan principalmente de la sesión, probablemente sea mejor ponerlas dentro SessionController, y el indexcontrolador, que solo imprime un formulario, debe colocarse LoginPageController, ya que obviamente se trata de la página de inicio de sesión. Tiene un poco de sentido incluir el procesamiento de HTML y la gestión de sesiones en una sola clase, y eso violaría SRP y probablemente un montón de otras buenas prácticas.

Principio general

Cuando tenga problemas para decidir dónde colocar un fragmento de código, comience con los datos (y tipos) con los que trata.

scriptin
fuente
2
Lo sentimos, esas son acciones, no controladores :)
JK01
@ JK01 Esos son los que ustedes llaman. Es terminología, ya sabes. Y hay marcos que llaman a esas funciones "controladores" (o "controladores"), ya que hay muchos marcos que no los organizan en clases, ya que los espacios de nombres / módulos ya son suficientes. Puede usar los términos que desee, son solo palabras, pero creo que tener menos términos es mejor.
scriptin