Preferindo Normalização do Banco de Dados vs. Transparência do Esquema?

10

Um novo requisito surgiu em uma antiga base de código, que basicamente permite a comunicação direta (interna) entre duas classes de usuários anteriormente não diretamente relacionadas (armazenadas em tabelas diferentes com esquema completamente diferente e, infelizmente, o código não reconhece o OO, muito menos projetada, para que não haja classe pai). Como estamos ansiosos por essa configuração antiga que nunca considerou essa funcionalidade, não há garantia de que não haja colisões de PK - dado o conjunto de dados em uso, é praticamente garantido que EXISTE.

Portanto, a solução parece óbvia: mate-a com fogo e reescreva toda a bagunça Uma tabela de mapeamento. Eu recebi duas direções para as possíveis maneiras de implementar o mapa, mas não sou um DBA, por isso não tenho certeza se existem prós e contras que perdi.

Para esclarecer a abstração, considere três grupos de dados de usuários diferentes: Professores, Administração, Estudantes (Não, isso não é tarefa de casa. Promessa!)

Mapeamento 1

(professor_id, admin_id e student_id são chaves estrangeiras para suas respectivas tabelas)

| mailing_id (KEY) | professor_id | admin_id | student_id | 
-------------------------------------------------------
| 1001             |     NULL     |    87    |  NULL      |
| 1002             |     123      |   NULL   |  NULL      |
| 1003             |     NULL     |   NULL   |  123       |

O +/- dessa abordagem parece bastante pesado sobre os contras:

  • Dois campos "desperdiçados" por linha
  • Viola 2NF
  • Vulnerável para inserir / atualizar anomalias (uma linha com apenas 0-1 conjunto de campos NULL, por exemplo)

Os profissionais não têm seus próprios méritos, no entanto:

  • O mapeamento pode ser realizado com uma única pesquisa
  • Determine facilmente os dados de "origem" de um determinado usuário no mailing_id

Verdade seja dita, no meu intestino, eu não gosto dessa idéia.

Mapeamento 2

(suponha que MSG_ * sejam constantes definidas, tipos de enumeração ou outro identificador adequado)

| mailing_id (KEY)  | user_type (UNIQUE1) | internal_id (UNIQUE2)| 
------------------------------------------------------------------
| 1001              | MSG_ADMIN          | 87                    |
| 1002              | MSG_PROF           | 123                   |
| 1003              | MSG_STUDENT        | 123                   |

Com essa configuração, e um índice composto exclusivo de {user_type, internal_id} as coisas ficam muito mais limpas, o 3NF é mantido e o código do aplicativo não precisa verificar anomalias de I / U.

Por outro lado, há um pouco de perda de transparência na determinação das tabelas de origem do usuário que precisam ser tratadas fora do banco de dados, equivalendo basicamente a um mapeamento no nível de aplicativo dos valores de user_type para as tabelas. No momento, estou (com bastante força) inclinando-me para esse segundo mapeamento, pois a desvantagem é um pouco menor.

MAS estou dolorosamente ciente de minhas próprias limitações e tenho certeza de que provavelmente perdi vantagens ou obstáculos em ambas as direções, por isso volto a mentes mais sábias do que as minhas.

GeminiDomino
fonte
2
Você pode achar as idéias de Martin Fowler sobre papéis uma leitura interessante.
Marjan Venema
Foi, de fato, interessante. Infelizmente não muito insight sobre o meu problema específico
GeminiDomino
Você vai conseguir professores que se tornam administradores e estudantes que conseguem empregos na administração ou até voltam 10 anos depois como professores. Você provavelmente já os tem. Você vai mantê-los separados ou tentar se unir?
Elin
Os papéis são apenas exemplos, mas entendo o seu ponto. Na prática, mesmo se os usuários trocassem de função, eles permaneceriam como registros separados de qualquer maneira.
GeminiDomino
Seria ótimo se você reformulasse o primeiro parágrafo. É um pouco incerto. Quero dizer, é óbvio que há um problema, mas não está claro o que é.
Tulains Córdova

Respostas:

1

Sua segunda ideia é a correta. Essa abordagem permite fazer todo o mapeamento necessário para integrar seus três espaços-chave em colisão.

É importante ressaltar que ele permite que o banco de dados imponha a maior parte da consistência necessária, usando restrições declarativas .

Você já tem mais código do que deseja, portanto, não adicione mais do que o absolutamente necessário para manter sua lista de chaves integrada consistente. Deixe seu mecanismo de banco de dados fazer o que foi criado para fazer.

O "filho problemático" que está causando desconforto no Mapeamento 2 é a USER_TYPEcoluna. Esta coluna é importante porque você precisa garantir que INTERNAL_IDapareça apenas uma vez por tipo de usuário. A única vez em que você precisa de qualquer código que tenha conhecimento USER_TYPEé o código que insere e exclui da sua tabela de mapeamento. Isso pode ser localizado muito bem. Suponho que você criará um único ponto no seu código em que o conteúdo da tabela de mapeamento é mantido. Uma coluna extra neste local onde os dados são gravados não é grande coisa. O que você realmente deseja evitar é adicionar a coluna extra em todos os lugares em que os dados são lidos .

O código nos seus sub-aplicativos que precisa usar o mapeamento pode ser totalmente ignorante, USER_TYPEsimplesmente fornecendo a cada sub-aplicativo uma visualização que filtra os mapeamentos até o tipo de usuário específico do aplicativo.

Joel Brown
fonte
3

Por experiência, minha recomendação é escolher consistência em vez de elegância ou "melhores práticas". Isso é para corresponder ao design existente e combinar com TRÊS tabelas de correspondência (uma para cada função) com uma mailing_id, user_idestrutura de campo simples .

É deselegante, mas tem algumas vantagens ...

  1. Combinar a estrutura existente será mais fácil para qualquer pessoa que trabalhe nesse esquema antes que ele seja colocado em pastagem.
  2. Você não tem campos desperdiçados e não está pedindo ao banco de dados que combine coisas que não existirão.
  3. Como cada tabela será apenas uma para a outra, será relativamente fácil fazer uma visualização que amarre todos os dados para suas rotinas usarem.

Tenho certeza que muitos outros discordarão dessa abordagem, mas os principais objetivos da normalização e das práticas recomendadas são tornar o código mais consistente, para que seja mais fácil seguir e depurar ... e obviamente trazer toda a base de código para o zero provavelmente não é viável.

James Snell
fonte
O problema dessa abordagem é que o banco de dados não pode impor exclusividade nos IDs de correspondência, que é o principal objetivo do mapeamento em primeiro lugar: caso contrário, emparelhar os campos de identificação individuais de cada tabela com um indicador "tipo de usuário" feito sem nenhuma alteração.
GeminiDomino
Eu vejo o que você está falando, mas, tendo trabalhado nesse tipo de sistema, dei uma opção que você pode não ter considerado. Na minha opinião, o ID de correspondência precisaria de algum conteúdo para se referir a algum lugar (o que foi enviado ou como encontrar o documento); portanto, o ID de correspondência deve ser uma chave estrangeira, o que significa que os problemas de exclusividade seriam resolvidos em outros lugares. Enquanto eu o leio, as tabelas de dados do aluno de administração e de profs que estão sendo vinculadas podem ter estruturas diferentes, então não consigo ver o campo de tipo de usuário agregando valor. Os desenvolvedores originais devem ter atingido esse problema, o que eles fizeram?
James Snell
O campo "tipo de usuário" determinaria qual tabela associar a esse registro específico. Teria que ser tratado no nível do aplicativo de qualquer maneira e, como ESTÃO em tabelas diferentes, não há uma boa maneira de torná-lo uma restrição de chave estrangeira. Infelizmente, os desenvolvedores originais não consideraram esse problema, e é por isso que está se tornando uma bagunça. :)
GeminiDomino