Estamos desenvolvendo uma plataforma para cartões pré-pagos, que basicamente contém dados sobre os cartões e seu saldo, pagamentos, etc.
Até agora tínhamos uma entidade Cartão que possui uma coleção de entidade Conta, e cada Conta possui um Valor, que atualiza a cada Depósito/Retirada.
Há um debate agora na equipe; alguém nos disse que isso quebra as 12 regras de Codd e que atualizar seu valor a cada pagamento é um problema.
Isto é realmente um problema?
Se for, como podemos corrigir isso?
Sim, isso não é normalizado, mas ocasionalmente projetos não normalizados vencem por motivos de desempenho.
No entanto, eu provavelmente abordaria isso de maneira um pouco diferente, por razões de segurança. (Isenção de responsabilidade: atualmente não trabalho nem nunca trabalhei no setor financeiro. Estou apenas jogando isso fora.)
Tenha uma tabela de saldos lançados nos cartões. Isso teria uma linha inserida para cada conta, indicando o saldo lançado no fechamento de cada período (dia, semana, mês ou o que for apropriado). Indexe esta tabela por número de conta e data.
Use outra tabela para armazenar transações pendentes, que são inseridas na hora. Ao final de cada período, execute uma rotina que adicione as transações não contabilizadas ao último saldo final da conta para calcular o novo saldo. Marque as transações pendentes como lançadas ou observe as datas para determinar o que ainda está pendente.
Dessa forma, você tem um meio de calcular o saldo do cartão na hora, sem precisar somar todo o histórico da conta, e colocando o recálculo do saldo em uma rotina de lançamento dedicada, você pode garantir que a segurança da transação desse recálculo seja limitada a um único local (e também limitar a segurança na tabela de saldos para que apenas a rotina de lançamento possa gravar nela).
Em seguida, mantenha o máximo de dados históricos necessários para auditoria, atendimento ao cliente e requisitos de desempenho.
Por outro lado, há um problema que encontramos com frequência no software de contabilidade. Parafraseado:
A resposta, claro, é não, você não. Existem algumas abordagens aqui. Um é armazenar o valor calculado. Não recomendo essa abordagem porque os bugs de software que causam valores incorretos são muito difíceis de rastrear e, portanto, evitaria essa abordagem.
Uma maneira melhor de fazer isso é o que chamo de abordagem agregada de instantâneo de log. Nessa abordagem nossos pagamentos e utilizações são inserções e nunca atualizamos esses valores. Periodicamente, agregamos os dados durante um período de tempo e inserimos um registro instantâneo calculado que representa os dados no momento em que o instantâneo se tornou válido (geralmente um período de tempo antes do presente).
Agora, isso não quebra as regras de Codd porque, com o tempo, os instantâneos podem ser menos do que perfeitamente dependentes dos dados de pagamento/uso inseridos. Se tivermos instantâneos de trabalho, podemos decidir limpar dados de 10 anos sem afetar nossa capacidade de calcular saldos atuais sob demanda.
Por motivos de desempenho, na maioria dos casos, devemos armazenar o saldo atual - caso contrário, calculá-lo na hora pode se tornar proibitivamente lento.
Nós armazenamos totais pré-calculados em nosso sistema. Para garantir que os números estejam sempre corretos, usamos restrições. A seguinte solução foi copiada do meu blog. Ele descreve um inventário, que é essencialmente o mesmo problema:
Calcular totais em execução é notoriamente lento, seja com um cursor ou com uma junção triangular. É muito tentador desnormalizar, armazenar totais correntes em uma coluna, especialmente se você a selecionar com frequência. No entanto, como sempre, quando você desnormaliza, precisa garantir a integridade de seus dados desnormalizados. Felizmente, você pode garantir a integridade dos totais em execução com restrições – desde que todas as suas restrições sejam confiáveis, todos os seus totais em execução estão corretos. Além disso, dessa forma, você pode garantir facilmente que o saldo atual (totais acumulados) nunca seja negativo - a aplicação por outros métodos também pode ser muito lenta. O script a seguir demonstra a técnica.
Esta é uma pergunta muito boa.
Supondo que você tenha uma tabela de transações que armazene cada débito/crédito, não há nada de errado com seu design. Na verdade, trabalhei com sistemas de telecomunicações pré-pagos que funcionaram exatamente dessa maneira.
A principal coisa que você precisa fazer é garantir que está fazendo uma revisão
SELECT ... FOR UPDATE
do saldo enquanto fazINSERT
o débito/crédito. Isso garantirá o equilíbrio correto se algo der errado (porque toda a transação será revertida).Como outros apontaram, você precisará de um instantâneo dos saldos em períodos específicos de tempo para verificar se todas as transações em um determinado período são somadas com os saldos de início/fim do período corretamente. Grave uma tarefa em lote que seja executada à meia-noite no final do período (mês/semana/dia) para fazer isso.
O saldo é um valor calculado com base em certas regras de negócios; portanto, sim, você não deseja manter o saldo, mas sim calculá-lo a partir das transações no cartão e, portanto, na conta.
Você deseja acompanhar todas as transações no cartão para auditoria e relatórios de extratos e até mesmo dados de diferentes sistemas posteriormente.
Resumindo - calcule todos os valores que precisam ser calculados como e quando você precisar