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_item
e umproduct
A product
pode ser comprada. Tem vários detalhes, mas o mais importante é unit_price
.
Um order_line_item
possui uma chave estrangeira para o product_id
adquirido, o quantity
adquirido e unit_price
no momento em que o cliente comprou o produto.
A maior parte do que li diz que o unit_price
on order_line_item
deve 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_price
valor no arquivo order_line_item
?
Não seria melhor criar uma tabela de auditoria / histórico que documenta a unit_price
alteração de uma product
?
Quando um order_line_item
é criado, a chave estrangeira da product_audit
tabela é 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.
fonte
Respostas:
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:
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!
fonte
Vou acrescentar alguns pontos práticos que já vi.
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.
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.
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.
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.
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.
fonte
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!
fonte
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 umelasticsearch
servidor / instância que possua relatórios no estilo BigData e que seja facilmente escalonado em muitos milhões de documentos +.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.
fonte
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.
fonte