Tabela de pedidos de comércio eletrônico. Salvar preços ou usar uma tabela de auditoria / histórico?

13

Estou projetando meu primeiro esquema de comércio eletrônico. Eu tenho lido sobre o assunto há um tempo e estou um pouco confuso sobre a relação entre um order_line_iteme umproduct

A productpode ser comprada. Tem vários detalhes, mas o mais importante é unit_price.

Um order_line_itempossui uma chave estrangeira para o product_idadquirido, o quantityadquirido e unit_priceno momento em que o cliente comprou o produto.

A maior parte do que li diz que o unit_priceon order_line_itemdeve ser explicitamente adicionado (ou seja, não referenciado pelo product_id). Faz sentido, pois a loja poderá alterar o preço no futuro, o que atrapalharia os relatórios de pedidos, rastreamento, integridade etc.

O que eu não entendo é por que salvar diretamente o unit_pricevalor no arquivo order_line_item?

Não seria melhor criar uma tabela de auditoria / histórico que documenta a unit_pricealteração de uma product?

Quando um order_line_itemé criado, a chave estrangeira da product_audittabela é adicionada e o preço pode ser recuperado (por referência) a partir daí.

Parece-me que há muitos aspectos positivos em usar essa abordagem (menos duplicação de dados, histórico de alterações de preços, etc.), então por que não é usada com mais frequência? Não encontrei um exemplo de esquema de comércio eletrônico que use essa abordagem. Estou perdendo alguma coisa?

UDPATE: Parece que minha pergunta está relacionada à dimensão que muda lentamente . Ainda estou confuso, pois a Slowly Changing Dimension está relacionada ao data warehouse e aos OLAPs. Portanto, os tipos de dimensão de alteração lenta podem ser aplicados ao meu principal banco de dados de processos de transações comerciais (OLTP)? Gostaria de saber se estou misturando muitos conceitos, gostaria muito de receber algumas orientações.

Gaz_Edge
fonte
Na verdade, eu faria as duas coisas: armazenar o preço de venda no order_line e armazenar um histórico dos preços dos produtos. Porque ambos servem a propósitos diferentes. Armazenar o preço de venda tornará as consultas de pedidos muito mais fáceis e rápidas. Por outro lado, convém recuperar preços antigos mesmo para produtos que não foram vendidos.
A_horse_with_no_name
Certamente, tornar as consultas de pedidos mais fáceis não pode ser o único fator por trás da economia do preço final sempre. Afinal, é um banco de dados relacional - as consultas não seriam muito mais difíceis.
Gaz_Edge
3
Armazenar o preço de venda na linha do pedido não é "não relacional" (e considero normalizado, assim como os preços de venda é um atributo direto da linha do pedido na minha opinião). A arte de usar um banco de dados relacional é conhecer os limites. Se você estiver executando uma loja com 100 produtos e 5 pedidos por dia, armazenar e recuperar preços antigos não é um problema. Se você estiver administrando um mercado com milhões de produtos, milhares de revendedores e centenas de pedidos por minuto, consultar esse histórico será um problema.
A_horse_with_no_name
Onde você armazenaria os preços históricos? Pelo que estou lendo, precisaria de dois bancos de dados. Um para OLTP e outro para OLAP. A história vai no OLAP?
Gaz_Edge 02/02

Respostas:

10

Como você identificou, armazenar o preço no pedido facilita a implementação técnica. Existem várias razões comerciais pelas quais isso pode ser benéfico.

Além das transações na web, muitas empresas oferecem suporte a vendas por outros canais, por exemplo:

  • Pelo telefone
  • Agentes de vendas "na estrada"
  • Em um local físico (por exemplo, loja, escritório)

Nesses casos, o pedido pode ser inserido no sistema em algum momento após a transação. Nessas circunstâncias, pode ser difícil impossibilitar a identificação correta de qual registro histórico de preços deve ser usado - armazenar o preço unitário diretamente no pedido é a única opção viável.

Vários canais costumam trazer outro desafio - preços diferentes para o mesmo produto. Sobretaxas para pedidos por telefone são comuns - e alguns clientes podem negociar um desconto. Você pode representar todos os preços possíveis para todos os canais no esquema do produto, mas incorporá-lo nas tabelas de pedidos pode se tornar (muito) complexo.

Em qualquer lugar em que a negociação é permitida, fica muito difícil vincular o histórico de preços ao preço do pedido acordado (a menos que os agentes tenham limites de negociação muito estreitos). Você precisa armazenar o preço no próprio pedido.

Mesmo que você suporte apenas transações na Web e tenha uma estrutura de preços relativamente simples, ainda há um problema interessante a ser superado - como os aumentos de preços devem ser tratados nas transações de vôo? A empresa insiste em que o cliente pague aumentos ou cumpra o preço original (quando o produto foi adicionado à cesta)? Se for o último, a implementação técnica é complicada - você precisa encontrar uma maneira de garantir a manutenção correta da versão do preço na sessão.

Finalmente, muitas empresas estão começando a usar preços altamente dinâmicos. Pode não haver um preço fixo para um determinado produto - ele é sempre calculado em tempo de execução com base em fatores como hora do dia, demanda do produto e assim por diante. Nesses casos, o preço não pode ser armazenado no produto em primeiro lugar!

Chris Saxon
fonte
3

Vou acrescentar alguns pontos práticos que já vi.

  1. Os produtos são transitórios.

    O que eles podem significar hoje, pode não ser o mesmo que costumavam significar há um ano. O mesmo código de sku (e, portanto, o product_id), pode se referir a diferentes variantes / tipos do produto em diferentes estágios.

    Nem todo mundo entende todas as preocupações em questão; portanto, um usuário pode alterar os atributos do produto original em vez de criar um novo por sua própria ignorância. Muitas vezes, isso pode acontecer por causa do plano em que o usuário está (Hey! Eu posso ter apenas 100 sku, então por que não continuar mudando os mais antigos em vez de atualizar o plano) Então, veja, em muitos carros , um produto nunca significará a mesma coisa para sempre.

  2. Preços diferentes com base nas condições de pedido e envio

    Como o usuário @Chris mencionou, preços diferentes podem ser aplicáveis ​​em diferentes cenários.

    Na maioria dos carrinhos, você encontrará pelo menos três campos diferentes sendo armazenados - o preço unitário, o valor do desconto e o preço com desconto. Nos mais avançados, você encontrará mais 2 - preço unitário com imposto, preço com desconto com imposto. Você pode encontrar mais alguns campos para descrever as cobranças da forma de envio e as cobranças adicionais da forma de pagamento. As porcentagens de impostos podem variar de acordo com o estado, o produto, o país, o método de envio e assim por diante, assim como os demais custos. Da mesma forma, os descontos podem variar dependendo da geografia, promoções, época da venda e assim por diante. Portanto, há informações que podem ser obtidas apenas no nível do pedido e essas informações combinadas não podem ser geradas a partir de dados apenas na tabela de produtos.

  3. Separação de preocupações

    Muitos carrinhos são implementados de uma maneira, para que equipes diferentes possam ter controle sobre diferentes partes dos dados. Alguém que gerencia o sistema de pedidos nem sempre precisa saber o que todos os produtos estão em estoque, quais eram os preços em diferentes momentos, quais são as alternativas para um determinado sku e assim por diante. Manter os dados relacionados ao produto junto com os dados do pedido ajuda a obter uma separação de preocupações. Isso também pode ser verdade nos estágios de desenvolvimento, se equipes diferentes gerenciam partes diferentes do sistema.

  4. Escalabilidade mais fácil em vários sistemas

    Muitas vezes, o sistema de gerenciamento de pedidos, o mecanismo de regras, o mecanismo de catálogo e o sistema de gerenciamento de conteúdo são todos criados / mantidos como sistemas separados. Isso ajuda a otimizar para várias condições de carga e gerar inteligência especializada para cada sistema. Um sistema, portanto, não pode ser resgatado devido à indisponibilidade de informações de outro sistema.

  5. Desenvolvimento e tempo de execução mais rápidos

    Eu usei o termo "tempo de desenvolvimento" aqui, embora o uso de "tempo de depuração" seja mais adequado. Sempre que algum novo desenvolvimento estiver acontecendo, será mais rápido se os dados necessários estiverem disponíveis sem adicionar complexidade própria, porque, então, haverá ciclos de depuração comparativamente menores.

    Imagine que você foi solicitado a gerar relatórios sob demanda para descontos oferecidos no dia-a-dia por um determinado mês, meio ano atrás. Se você tiver o preço original, o preço com desconto em 1-2 tabelas, juntamente com o pedido e os detalhes do item, isso é bastante simples. Se, no entanto, você precisar buscar preços de outra tabela e, em seguida, os descontos aplicáveis ​​de outra tabela e descobrir os detalhes, o tempo de desenvolvimento e de execução será maior.

Um bom design deve tentar otimizar tanto para o futuro quanto para o presente.

mu 無
fonte
2

Pode acabar custando mais em armazenamento, mas eu prefiro alojar todos os detalhes relevantes da venda com a transação, para que, por qualquer motivo, nossa trilha de auditoria seja interrompida ou um administrador substitua as garantias em vigor, os detalhes do venda como: moeda usada, preço unitário, quantidade, impostos aplicados e qual o valor a que chegaram etc. estão todos disponíveis. Geralmente armazeno isso como XML para que possa ser flexível de uma venda para outra.


EDIT: Para expandir o que eu estava dizendo brevemente acima, no meu comentário de acompanhamento abaixo, e o que @a_horse_with_no_name abordou acima, a redundância nos dados de transação não é apenas importante, mas também é necessária em escala.

Estou assumindo que você está desenvolvendo usando OOP e, portanto, provavelmente deve ter um objeto de transação e um objeto de produto abrangente e / ou um objeto de preço. Na minha própria experiência pessoal, prefiro ser detalhado em minha história, o armazenamento é relativamente caro.

O que fizemos foi criar um histórico de objetos que você possa facilitar usando um RDBMS existente ou algum tipo de armazenamento de valor de chave NOSQL (ou melhor ainda, um RDBMS que permita NoSQL como conexões como handlersocket ou memcache) e armazenamos o histórico de objetos Dessa forma, com todos os detalhes e alterações de preços em um só lugar, de maneira fácil e rápida. Se você é sério, pode até usar DIFFs para economizar armazenamento e armazenar apenas as alterações adiante, embora ele tenha suas próprias advertências. Isso deve cuidar do seu histórico, e a vantagem dos objetos serializados é que seu sistema será capaz de recuperá-los como os objetos em que foram armazenados. Isso cuida da história.

No que diz respeito à minha sugestão, armazenar os detalhes da transação como impostos, moeda etc. com a própria transação significa que você não precisa procurar em outros lugares esses detalhes, seu objeto de transação estará ciente de suas propriedades e suas visualizações poderão cuidar da apresentação os dados variados como achar melhor. Você obtém acesso rápido ao instantâneo e tem o benefício adicional de registros redundantes e verificáveis.

Vale a pena, confie em mim!

oucil
fonte
isso realmente não faz sentido dizer que você não o usa porque pode ser excluído. Você pode substituir qualquer dado. Qualquer estratégia deve manter os backups
Gaz_Edge
@Gaz_Edge Eles não são mutuamente exclusivos, você ainda pode utilizar uma trilha de auditoria e armazenar os detalhes com transações, a redundância nesse caso é justificada. Nunca se deixe com dados tão importantes quanto a um único ponto de falha. No nosso caso, eu uso um armazenamento centralizado de objetos como mecanismo de histórico, em vez de tentar criar um histórico por tipo de objeto (como uma transação ou produto nesse caso). Terei o objeto de transação inteiro no histórico, mas todos os detalhes mais importantes do próprio registro. Isso é totalmente à parte dos objetos do produto na história.
oucil
e se eu quisesse gerar relatórios sobre pedidos? Como quantos produtos x foram comprados? Ou quantos pedidos acima do valor y? Salvando como meio XML você perde a capacidade de consultar os dados, se não me engano
Gaz_Edge
@Gaz_Edge Não é verdade, se você está falando sobre MySQL, você SELECT ExtractValue(field_name, '/x/path/');pode filtrar coisas como todas as transações em uma moeda específica ou todas as transações com um determinado valor mínimo de imposto ou qualquer outra coisa. Relatórios de maior escala podem ser feitos a partir do histórico do objeto. Para relatórios de maior escala, você pode configurar um elasticsearchservidor / instância que possua relatórios no estilo BigData e que seja facilmente escalonado em muitos milhões de documentos +.
oucil
@Gaz_Edge Também devo mencionar que os relatórios de que você está falando (compras acima do valor, vendas de produtos etc.) são relatórios comuns e devem ter valores armazenados como valores colunares no registro da transação para um processamento mais rápido. Qualquer coisa importante, mas não necessariamente relatada com frequência, pode ser inserida no XML. Os dados da captura instantânea são realmente apenas para duas coisas: 1. o problema de Dimensão de mudança lenta e 2. Validação e comparação quando um cliente reclama, e você precisa ver rapidamente quem está certo. Não é para uso diário.
oucil
1

Meu voto seria armazenar o preço unitário no item de linha e acompanhar o histórico de preços de seus produtos em uma tabela separada. Minha justificativa é para maior flexibilidade.

Mesmo que sua estrutura de preços seja rígida e bem definida e não permita as variações que o @Chris Saxon mencionou acima, você se sente à vontade para que sempre seja assim? Mesmo se você estiver confiante, por que se pintar em um canto? Eu acho que seria uma boa ideia armazenar isso nos detalhes do item de linha, porque não consigo pensar em um motivo convincente para mantê-lo separado.

Quanto ao armazenamento do histórico de preços, existe um valor definitivo em armazená-lo separadamente, pois pode haver alterações no preço de um item e ninguém o comprou. Definitivamente, seria uma informação útil para saber se uma alteração de preço foi ineficaz. Como você mencionou, este é um caso de uso clássico de uma Dimensão de alteração lenta do tipo 2 em um cenário de armazém de dados. Normalmente, qualquer alteração de preço em sua tabela de produtos seria capturada e uma nova linha seria adicionada à tabela de dimensões com o preço atualizado e um carimbo de data / hora para indicar quando essa alteração ocorreu. A linha anterior deve ter sua data de término atualizada para indicar que não é mais o preço efetivo. Portanto, uma abordagem seria rastrear esse tipo de alteração em um data warehouse.

No entanto, se você não deseja se preocupar em projetar um esquema de armazém de dados e um processo ETL ao mesmo tempo em que cria seu banco de dados de comércio eletrônico OLTP, esse histórico certamente pode ser capturado em nosso banco de dados de comércio eletrônico. Isso pode ser feito conforme descrito com a criação de uma tabela product_audit separada que fica pendurada na tabela do produto e contém datas de início e término para quando a versão de um produto estava em vigor. Isso também pode ser feito na própria tabela do produto, adicionando datas de início e término à tabela para indicar qual produto está ativo no momento. No entanto, dependendo do número de produtos e do número ou alterações de preços sofridos por sua empresa, isso pode tornar a tabela de produtos muito maior do que o planejado e causar problemas de desempenho da consulta posteriormente.

Por fim, separar seu histórico de preços do preço unitário real no item de linha definitivamente poderia oferecer outras oportunidades analíticas para ver quando um produto foi vendido a um preço que estava acima ou abaixo do preço listado no momento.

njkroes
fonte
obrigado pela resposta. Acho que a estratégia que vou seguir será projetar o OLTP de acordo com o livro. Na verdade, gosto da ideia de ter um data warehouse para gerar relatórios. Embora adicione algum trabalho adicional (criando um novo esquema), o esquema pode ser adaptado para OLAP. É como evitar um cenário em que estou tentando encaixar uma estaca quadrada em um buraco redondo.
Gaz_Edge
@Gaz_Edge: Estou em uma situação semelhante em termos de tomar uma decisão para o meu novo projeto. Você pode compartilhar seus pensamentos sobre qual abordagem de design você adotou um ano atrás? Funcionou bem?
Coder Absolute
@CoderAbsolute minha solução original foi muito orientada para 'banco de dados'. Meu aplicativo agora está centrado em uma arquitetura orientada a serviços. Agora tenho muitos pequenos esquemas de banco de dados dissociados em vez de um fortemente acoplado. A necessidade de 3N em um esquema massivo agora se foi. Agora, basta adicionar o preço unitário diretamente ao pedido. Manter alterações históricas nos preços dos produtos agora desapareceu, pois não era um requisito comercial. Espero que ajude.
Gaz_Edge 24/10/2015
0

Eu concordo totalmente com a idéia principal de manter juntas as informações relacionadas ao pedido (contexto). Apenas uma pequena observação secundária de que essa situação surgirá apenas quando você estiver projetando seu aplicativo com muita ênfase no banco de dados e tudo gira em torno do grande banco de dados gordo. Se você mudar de ponto de vista olhando o domínio do problema de um ângulo diferente, observará claramente que a ordem é um instantâneo capturado de um evento muito especial no ciclo de vida do seu aplicativo. Quando você lida com problemas com base no contexto, os problemas do banco de dados se tornam secundários e a complexidade da qual todos têm medo de consultar e gerar relatórios será tratada de maneira uniforme no modelo de domínio.

Waku-2
fonte
Alguns pequenos exemplos tornariam sua resposta muito mais fácil de entender. Especialmente frases como 'order é um instantâneo capturado de um evento muito especial no ciclo de vida do seu aplicativo'.
Dezso