Como determinar o que deve obter seu próprio controlador?

10

Estou usando o padrão MVC no meu aplicativo da Web criado com PHP.

Estou sempre lutando para determinar se preciso de um novo controlador dedicado para um conjunto de ações ou se devo colocá-los dentro de um controlador já existente.

Existem boas regras práticas a seguir ao criar controladores?

Por exemplo, eu posso ter:

AuthenticationController com ações:

  • index() para exibir o formulário de login.
  • submit() para lidar com o envio de formulários.
  • logout(), auto-explicativo.

OU

LoginController com ações:

  • index() para exibir o formulário de login.
  • submit() para lidar com o envio de formulários.

LogoutController com ação:

  • index() para lidar com o logout.

OU

AccountController com ações:

  • loginGet() para exibir o formulário de login.
  • loginPost() para lidar com o envio do formulário de login.
  • logoutGet() para lidar com o logout.
  • registerGet() para exibir o formulário de inscrição.
  • registerPost() para lidar com o envio de formulários.

    E quaisquer outras ações envolvidas em uma conta.

Kid Diamond
fonte
Talvez dê uma olhada no design RESTful. Ele não resolve todos os problemas desse tipo, mas fornece uma direção muito boa de como pensar sobre isso.
thorsten müller

Respostas:

3

Para encontrar o agrupamento certo para os controladores, pense nos testes .

(Mesmo que você não faça nenhum teste, pensar em como você testaria seus controladores fornecerá idéias muito boas sobre como estruturá-los.)

Um AuthenticationControllernão pode ser testado por si só, porque contém apenas a funcionalidade de logon e logout, mas seu código de teste precisará, de alguma forma, criar contas falsas para fins de teste antes de poder testar um login bem-sucedido. Você pode ignorar o subsistema em teste e ir diretamente ao seu modelo para a criação das contas de teste, mas terá um teste frágil em suas mãos: se o modelo for alterado, será necessário modificar não apenas o código dos testes o modelo, mas também o código que testa o controlador, mesmo que a interface e o comportamento do controlador permaneçam inalterados. Isso não é razoável.

A LoginControlleré inadequado pelos mesmos motivos: você não pode testá-lo sem criar contas primeiro, e há ainda mais coisas que não pode testar, como, por exemplo, impedir logons duplicados, mas permitir que um usuário efetue login após o logout. (Como esse controlador não possui funcionalidade de logoff).

Um AccountControllerfornecerá tudo o que você precisa para realizar seus testes: você pode criar uma conta de teste e tentar fazer login, você pode excluir a conta e garantir que não pode mais fazer login, alterar a senha e garantir que o senha correta deve ser usada para fazer login, etc.

Para concluir: para escrever até o menor conjunto de testes, você precisará disponibilizar toda a funcionalidade do AccountControllermesmo. Subdividi-lo para controladores menores parece estar gerando controladores deficientes com funcionalidade insuficiente para um teste adequado. Essa é uma indicação muito boa de que a funcionalidade de AccountControlleré a menor subdivisão que faz sentido.

E, de um modo geral, a abordagem "pensar no teste" funcionará não apenas nesse cenário específico, mas em qualquer cenário semelhante que você encontrar no futuro.

Mike Nakis
fonte
1

A resposta não é tão óbvia

Deixe-me esclarecer algumas coisas antes de fazer qualquer declaração de resposta. Em primeiro lugar:

Qual é o controlador?

O controlador é uma parte do sistema que controla a solicitação - após o envio. Assim, podemos defini-lo como um conjunto de ações relacionadas a ... o quê?

Qual é o escopo do controlador?

E isso é mais ou menos parte quando teremos qualquer resposta. O que você acha? É um controlador de coisas (por exemplo, uma conta) ou um controlador de ações? É claro que é um controlador de algum modelo ou algo mais abstrato que fornece ações sobre ele.

A resposta é...

Controlador de autenticação com ações:

  • index () para exibir o formulário de login.
  • submit () para lidar com o envio do formulário.
  • logout (), auto-explicativo.

Nah, autenticação é um processo. Não siga por esse caminho.

Controlador de login com ações:

  • index () para exibir o formulário de login.
  • submit () para lidar com o envio do formulário.

O mesmo aqui. Login - ação. Melhor não crie um controlador de ação (você não tem um modelo correlacionado).

AccountController com ações:

  • loginGet () para exibir o formulário de login.
  • loginPost () para lidar com o envio do formulário de login.
  • logoutGet () para lidar com o logout.
  • registerGet () para exibir o formulário de registro.
  • registerPost () para lidar com o envio de formulários.

Muito bom, mas não estou convencido de que vale a pena criar esse controlador de baixo nível (o controlador é a própria abstração). De qualquer forma, a criação de métodos com * Get ou * Post não é clara.

Alguma sugestão?

Sim, considere:

AccountController:

  • login (AccountModel)
  • logout (AccountModel)
  • registrar (AccountModel)
  • índice()

E modelo relacionado a ele, ofc Classe de conta. Isso lhe dará a oportunidade de mover seu par de modelo-controlador para outro lugar (se for necessário) e criar um código claro (é óbvio o que o login()método significa). Stincking para modelar é realmente famoso, especialmente com aplicativos CRUD e talvez seja uma maneira de você.

Dawid Pura
fonte
1

Os controladores geralmente são criados para um determinado recurso (uma classe de entidade, uma tabela no banco de dados), mas também podem ser criados para agrupar ações que são responsáveis ​​por uma determinada parte do aplicativo. Nos seus exemplos, isso seria um controlador que lida com a segurança do aplicativo:

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 : não coloque as ações relacionadas à segurança e as ações de perfil de usuário no mesmo controlador; pode fazer sentido porque eles estão relacionados ao usuário, mas um deve lidar com autenticação e o outro com atualizações de email, nome etc.

Com os controladores criados para recursos (digamos Task), você teria as ações CRUD usuais :

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()
}

Obviamente, você tem a possibilidade de adicionar recursos relacionados ao mesmo controlador. Digamos, por exemplo, que você tenha a entidade Businesse cada uma tenha várias BusinessServiceentidades. Um controlador para ele pode ficar assim:

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);
}

Essa abordagem faz sentido quando as entidades filhas relacionadas não podem existir sem a entidade pai.

Estas são as minhas recomendações:

  • criar controladores com base em um grupo de operações relacionadas (lidar com certas responsabilidades, como segurança ou operações CRUD em recursos etc.);
  • para controladores baseados em recursos, não adicione ações desnecessárias (se você não deve atualizar o recurso, não adicione a ação de atualização);
  • você pode adicionar ações "personalizadas" para simplificar as coisas (por exemplo, uma Subscriptionentidade que tenha uma disponibilidade com base em um número limitado de entradas, você pode adicionar uma nova ação ao controlador nomeado use()que tem o único objetivo de subtrair uma entrada da Subscription)
  • mantenha as coisas simples - não sobrecarregue seu controlador com um grande número de ações e lógica complexa, tente simplificar as coisas diminuindo o número de ações ou fazendo dois controladores;
  • se você estiver usando uma estrutura focada em MVC, siga as diretrizes de práticas recomendadas (se houver).

Alguns recursos para leitura adicional aqui .

qretzu
fonte
0

Eu vejo duas "forças" antagônicas de design (que não são exclusivas dos controladores):

  • coesão - os controladores devem agrupar ações relacionadas
  • simplicidade - os controladores devem ser o menor possível para gerenciar sua complexidade

Do ponto de vista da coesão, todas as três ações (login, logout, registro) estão relacionadas, mas o login e logout são muito mais do que o registro. Eles são relacionados semanticamente (um é uma inversão do outro) e, possivelmente, também usarão os mesmos objetos de serviço (suas implementações também são coesas).

Meu primeiro instict seria agrupar o login e o logout em um controlador. Mas se as implementações dos controladores de logon e logout não forem tão simples (por exemplo, o logon possui captcha, mais métodos de autenticação etc.), eu não teria problema em dividi-las em LoginController e LogoutController para manter a simplicidade. Onde esse limite de complexidade (quando você deve começar a dividir o controlador) se encontra é um pouco pessoal.

Lembre-se também de que, independentemente do design do código, você pode (e deve) refatorá-lo à medida que ele muda. Nesse caso, é bastante comum começar com um design simples (tenha um AuthenticationController) e, com o tempo, você receberá mais requisitos que complicarão o código. Depois de cruzar o limite de complexidade, você deve refatorá-lo para dois controladores.

BTW, seu código sugere que você está desconectando o usuário com a solicitação GET. Essa é uma péssima idéia, já que o HTTP GET deve ser nulo-potencial (não deve modificar o estado do aplicativo).

qbd
fonte
0

Aqui estão algumas regras práticas:

  • Organize por assunto ou tópico, com o nome do controlador sendo o nome do tópico.

  • Lembre-se de que o nome do controlador aparecerá na URL, visível para os usuários, portanto, de preferência, deve fazer sentido para eles.

Na situação mencionada (autenticação), a equipe MVC já escreveu o controlador para você. Abra o Visual Studio 2013 e clique em

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

AccountController.cs contém todos os métodos para gerenciar contas de usuário:

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

Portanto, eles organizaram por tópico "Contas de usuário e autenticação", com o nome de tópico visível "Conta".


fonte
0

Terminologia

Eu acredito que é um grande equívoco chamar uma classe que contém alguns métodos relacionados a HTTP de "controlador".

Controller é um método que lida com solicitação, mas não uma classe que contém esses métodos . Assim, index(), submit(), logout()são controladores.

A classe que contém esse tipo de método é denominada "controlador" apenas porque constitui um grupo de controladores e está desempenhando um papel de espaço para nome "nível inferior". Na linguagem FP (como Haskell), seria apenas um módulo. É uma boa prática manter essas classes "controladoras" o mais sem estado possível nas linguagens OOP, exceto as referências a serviços e outras coisas de todo o programa.

A resposta

Com a terminologia resolvida, a pergunta é "como devemos separar os controladores em namespaces / módulos?" Penso que a resposta é: os controladores dentro de um único espaço para nome / módulo devem lidar com o mesmo tipo de dados . Por exemplo, UserControllerlida principalmente com instâncias de Userclasse, mas ocasionalmente toca em outras coisas relacionadas, se necessário.

Como login, logoute outras ações desse tipo estão lidando principalmente com a sessão, provavelmente é melhor colocá-las dentro SessionController, e o indexcontroller, que apenas imprime um formulário, deve ser colocado LoginPageController, pois obviamente trata da página de login. Faz um pouco de sentido colocar a renderização HTML e o gerenciamento de sessões em uma única classe, o que violaria o SRP e provavelmente um monte de outras boas práticas.

Princípio geral

Quando tiver problemas para decidir onde colocar um pedaço de código, comece pelos dados (e tipos) com os quais você lida.

scriptin
fonte
2
Desculpe, essas são ações, não Controladores :)
JK01
@ JK01 É assim que você os chama. É terminologia, você sabe. E existem frameworks que chamam essas funções de "controladores" (ou "manipuladores"), uma vez que existem muitos frameworks que não os organizam em classes, pois os namespaces / módulos já são suficientes. Você pode usar os termos que quiser, são apenas palavras, mas acho que ter menos termos é melhor.
Script #