Database
 sql >> Base de Dados >  >> RDS >> Database

Gerenciando funções e status em um sistema


Há muitas maneiras de resolver um problema, e esse é o caso da administração de funções e status de usuário em sistemas de software. Neste artigo, você encontrará uma evolução simples dessa ideia, bem como algumas dicas úteis e exemplos de código.

Ideia básica


Na maioria dos sistemas, geralmente é necessário ter funções e status do usuário .

As funções estão relacionadas aos direitos que os usuários têm ao usar um sistema após o login bem-sucedido. Exemplos de funções são “funcionário de call center”, “gerente de call center”, “funcionário de back office”, “gerente de back office” ou “gerente”. Geralmente, isso significa que um usuário terá acesso a alguma funcionalidade se tiver a função apropriada. É aconselhável assumir que um usuário pode ter várias funções ao mesmo tempo.

Os status são muito mais rígidos e determinam se o usuário tem ou não direito de fazer login no sistema. Um usuário pode ter apenas um status de uma vez. Exemplos de status seriam:“trabalhando”, “de férias”, “de licença médica”, “contrato encerrado”.

Quando alteramos o status de um usuário, ainda podemos manter todas as funções relacionadas a esse usuário inalteradas. Isso é muito útil porque na maioria das vezes queremos alterar apenas o status do usuário. Se um usuário que trabalha como funcionário de call center sair de férias, podemos simplesmente alterar seu status para “de férias” e devolvê-lo ao status “trabalhando” quando ele voltar.

Testar funções e status durante o login nos permite decidir o que acontecerá. Por exemplo, talvez queiramos proibir o login mesmo que o nome de usuário e a senha estejam corretos. Poderíamos fazê-lo se o status atual do usuário não implicar que ele esteja trabalhando ou se o usuário não tiver nenhuma função no sistema.

Em todos os modelos abaixo, as tabelas status e role são os mesmos.

status tem os campos id e status_name e o atributo is_active . Se o atributo is_active estiver definido como "True", isso significa que o usuário que possui esse status está trabalhando no momento. Por exemplo, o status “working” teria o atributo is_active com valor True, enquanto outras (“de férias”, “de licença médica”, “contrato encerrado”) teriam valor False.

A tabela de funções tem apenas dois campos:id e role_name .

A user_account table é igual a user_account tabela apresentada neste artigo. Somente no primeiro modelo a user_account contém dois atributos extras (role_id e status_id ).

Alguns modelos serão apresentados. Todos eles funcionam e podem ser usados, mas têm suas vantagens e desvantagens.

Modelo Simples


A primeira ideia pode ser simplesmente adicionar relacionamentos de chave estrangeira à user_account tabela, referenciando em tabelas status e role . Ambos role_id e status_id são obrigatórios.




Isso é bastante simples de projetar e também de lidar com dados com consultas, mas tem algumas desvantagens:

  1. Não mantemos nenhum dado histórico (ou futuro).

    Quando alteramos o status ou função, simplesmente atualizamos status_id e role_id na user_account tabela. Isso funcionará bem por enquanto, então, quando fizermos uma alteração, ela refletirá no sistema. Tudo bem se não precisarmos saber como os status e as funções mudaram historicamente. Além disso, há um problema em que não podemos adicionar futuro função ou status sem adicionar tabelas extras a este modelo. Uma situação em que provavelmente gostaríamos de ter essa opção é quando sabemos que alguém estará de férias a partir da próxima segunda-feira. Outro exemplo é quando temos um novo funcionário; talvez queiramos inserir seu status e função agora e que se torne válido em algum momento no futuro.

    Há também uma complicação caso tenhamos eventos agendados que usam funções e status. Eventos que preparam dados para o próximo dia útil geralmente são executados enquanto a maioria dos usuários não usa o sistema (por exemplo, durante a noite). Portanto, se alguém não trabalhar amanhã, teremos que esperar até o final do dia atual e alterar suas funções e status conforme apropriado. Por exemplo, se tivermos funcionários que trabalham atualmente e têm a função de “funcionário de call center”, eles receberão uma lista de clientes para os quais devem ligar. Se alguém por engano tiver esse status e função, ele também receberá seus clientes e teremos que gastar tempo corrigindo isso.

  2. O usuário pode ter apenas uma função por vez.

    Geralmente, os usuários devem poder ter mais de uma função no sistema. Talvez no momento em que você está projetando o banco de dados não haja necessidade de algo assim. Tenha em mente que podem ocorrer mudanças no fluxo de trabalho/processo. Por exemplo, em algum momento o cliente pode decidir mesclar duas funções em uma. Uma solução possível é criar uma nova função e atribuir a ela todas as funcionalidades das funções anteriores. A outra solução (se os usuários puderem ter mais de uma função) seria que o cliente simplesmente atribua ambas as funções aos usuários que precisam delas. Claro que essa segunda solução é mais prática e dá ao cliente a capacidade de ajustar o sistema às suas necessidades mais rapidamente (o que não é suportado por este modelo).

Por outro lado, este modelo também tem uma grande vantagem sobre os outros. É simples e, portanto, as consultas para alterar status e funções também seriam simples. Além disso, uma consulta que verifica se o usuário tem direitos de login no sistema é muito mais simples do que em outros casos:

select user_account.id, user_account.role_id
from user_account
left join status on user_account.status_id = status.id
where status.is_user_working = True
and user_account.user_name = @user_name
and user_account.password_hash_algorithm = @password;

@user_name e @password são variáveis ​​de um formulário de entrada enquanto a consulta retorna o id do usuário e o role_id que ele possui. Nos casos em que nome_usuário ou senha não são válidos, o par nome_usuário e senha não existe ou o usuário tem um status atribuído que não está ativo, a consulta não retornará nenhum resultado. Dessa forma, podemos proibir o login.

Este modelo pode ser usado nos casos em que:

  • temos certeza de que não haverá mudanças no processo que exijam que os usuários tenham mais de uma função
  • não precisamos rastrear alterações de funções/status no histórico
  • não esperamos ter muita administração de função/status.

Componente de tempo adicionado


Se precisarmos rastrear a função e o histórico de status de um usuário, devemos adicionar muitos relacionamentos entre a user_account e role e a user_account e status . Claro que removeremos role_id e status_id da user_account tabela. As novas tabelas no modelo são user_has_role e user_has_status e todos os campos neles, exceto os horários de término, são obrigatórios.




A tabela user_has_role contém dados sobre todas as funções que os usuários já tiveram no sistema. A chave alternativa é (user_account_id , role_id , role_start_time ) porque não faz sentido atribuir a mesma função ao mesmo tempo a um usuário mais de uma vez.

A tabela user_has_status contém dados sobre todos os status que os usuários já tiveram no sistema. A chave alternativa aqui é (user_account_id , status_start_time ) porque um usuário não pode ter dois status que comecem exatamente ao mesmo tempo.

A hora de início não pode ser nula porque quando inserimos uma nova função/status, sabemos o momento a partir do qual ela começará. A hora de término pode ser nula caso não saibamos quando a função/status terminaria (por exemplo, a função é válida a partir de amanhã até que algo aconteça no futuro).

Além de ter um histórico completo, agora podemos adicionar status e funções no futuro. Mas isso cria complicações porque temos que verificar a sobreposição quando fazemos uma inserção ou atualização.

Por exemplo, o usuário pode ter apenas um status por vez. Antes de inserir um novo status, temos que comparar a hora de início e a hora de término de um novo status com todos os status existentes para esse usuário no banco de dados. Podemos usar uma consulta como esta:

select *
from user_has_status
where user_has_status.user_account_id = @user_account_id
and 
(
# test if @start_time included in interval of some previous status
(user_has_status.status_start_time <= @start_time and ifnull(user_has_status.status_end_time, "2200-01-01") >= @start_time)
or
# test if @end_time included in interval of some previous status  
(user_has_status.status_start_time <= @end_time and ifnull(user_has_status.status_end_time, "2200-01-01") >= ifnull(@end_time, "2199-12-31"))  
or  
# if @end_time is null we cannot have any statuses after @start_time
(@end_time is null and user_has_status.status_start_time >= @start_time)  
or
# new status "includes" old satus (@start_time <= user_has_status.status_start_time <= @end_time)
(user_has_status.status_start_time >= @start_time and user_has_status.status_start_time <= ifnull(@end_time, "2199-12-31"))  
)

@start_time e @end_time são variáveis ​​que contêm a hora de início e a hora de término de um status que queremos inserir e @user_account_id é o ID do usuário para o qual o inserimos. @end_time pode ser nulo e devemos tratá-lo na consulta. Para este propósito, os valores nulos são testados com o ifnull() função. Se o valor for nulo, um valor de data alto será atribuído (alto o suficiente para que, quando alguém perceber um erro na consulta, já estejamos fora :). A consulta verifica todas as combinações de hora de início e hora de término para um novo status em comparação com a hora de início e a hora de término dos status existentes. Se a consulta retornar algum registro, temos sobreposição com status existentes e devemos proibir a inserção do novo status. Também seria bom gerar um erro personalizado.

Se quisermos verificar a lista de funções e status atuais (direitos do usuário), simplesmente testamos usando a hora de início e a hora de término.

select user_account.id, user_has_role.id
from user_account
left join user_has_role on user_has_role.user_account_id = user_account.id
left join user_has_status on user_account.id = user_has_status.user_account_id
left join status on user_has_status.status_id = status.id
where user_account.user_name = @user_name
and user_account.password_hash_algorithm = @password
and user_has_role.role_start_time <= @time and ifnull(user_has_role.role_end_time,"2200-01-01") >= @time
and user_has_status.status_start_time <= @time and ifnull(user_has_status.status_end_time,"2200-01-01") >= @time
and status.is_user_working = True

@user_name e @password são variáveis ​​do formulário de entrada enquanto @time pode ser definido como Agora(). Quando um usuário tenta fazer login, queremos verificar seus direitos naquele momento. O resultado é uma lista de todas as funções que um usuário possui no sistema caso o nome_do_usuário e a senha correspondam e o usuário tenha atualmente um status ativo. Se o usuário tiver um status ativo, mas nenhuma função atribuída, a consulta não retornará nada.

Essa consulta é mais simples que a da seção 3 e esse modelo nos permite ter um histórico de status e papéis. Além disso, podemos gerenciar status e funções para o futuro e tudo funcionará bem.

Modelo final


Esta é apenas uma ideia de como o modelo anterior poderia ser alterado se quiséssemos melhorar o desempenho. Como um usuário pode ter apenas um status ativo por vez, podemos adicionar status_id na user_account tabela (current_status_id ). Dessa forma, podemos testar o valor desse atributo e não precisaremos ingressar no user_has_status tabela. A consulta modificada ficaria assim:

select user_account.id, user_has_role.id
from user_account
left join user_has_role on user_has_role.user_account_id = user_account.id
left join status on user_account.current_status_id = status.id
where user_account.user_name = @user_name
and user_account.password_hash_algorithm = @password
and user_has_role.role_start_time <= @time and ifnull(user_has_role.role_end_time,"2200-01-01") >= @time
and status.is_user_working = True





Obviamente, isso simplifica a consulta e leva a um melhor desempenho, mas há um problema maior que precisaria ser resolvido. O current_status_id na user_account tabela deve ser verificada e alterada se necessário nas seguintes situações:
  • em cada inserção/atualização/exclusão em user_has_status tabela
  • todos os dias em um evento agendado, devemos verificar se o status de alguém mudou (status ativo atual expirou ou/e algum status futuro se tornou ativo) e atualizá-lo adequadamente

Seria sensato salvar valores que as consultas usarão com frequência. Dessa forma, evitaremos fazer as mesmas verificações repetidas vezes e dividiremos o trabalho. Aqui, evitaremos participar do user_has_status tabela e faremos alterações em current_status_id apenas quando eles acontecem (inserir/atualizar/excluir) ou quando o sistema não está em uso (eventos agendados geralmente são executados quando a maioria dos usuários não usa o sistema). Talvez, neste caso, não ganharíamos muito com current_status_id mas veja isso como uma ideia que pode ajudar em situações semelhantes.