Modelo transacional para um e-commerce de médio porte (~120 mil pedidos/mês), desenhado para PostgreSQL 16. Normalização pragmática: 3FN no núcleo transacional, desnormalização deliberada apenas no snapshot de preço do item (preço muda; o pedido não pode mudar junto).
Diagrama (ERD)
Renderizando diagrama…
Decisões que importam
unit_price_snapshotno item do pedido — o preço vigente na compra é fato histórico; sem o snapshot, todo relatório de receita passada fica errado após o primeiro reajuste.- Estoque por movimento, não por saldo —
stock_movementsé apêndice-only; o saldo é uma soma. Auditável por construção e imune a corrida de update. - Sem soft delete no transacional — pedido não se apaga; cancela (
status). Menos índice parcial, menos armadilha de query.
DDL (Postgres)
CREATE TABLE customers (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
email TEXT NOT NULL UNIQUE,
full_name TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE products (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
sku TEXT NOT NULL UNIQUE,
name TEXT NOT NULL,
current_price NUMERIC(12,2) NOT NULL CHECK (current_price >= 0)
);
CREATE TABLE orders (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
customer_id BIGINT NOT NULL REFERENCES customers(id),
status TEXT NOT NULL DEFAULT 'created',
total_amount NUMERIC(12,2) NOT NULL DEFAULT 0,
placed_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX idx_orders_customer ON orders (customer_id, placed_at DESC);
CREATE TABLE order_items (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
order_id BIGINT NOT NULL REFERENCES orders(id),
product_id BIGINT NOT NULL REFERENCES products(id),
quantity INT NOT NULL CHECK (quantity > 0),
unit_price_snapshot NUMERIC(12,2) NOT NULL
);
CREATE INDEX idx_order_items_order ON order_items (order_id);
CREATE TABLE payments (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
order_id BIGINT NOT NULL REFERENCES orders(id),
method TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'pending',
amount NUMERIC(12,2) NOT NULL
);
CREATE TABLE stock_movements (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
product_id BIGINT NOT NULL REFERENCES products(id),
delta INT NOT NULL,
reason TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX idx_stock_movements_product ON stock_movements (product_id, created_at DESC);
✅ DDL validado no PostgreSQL — executado num banco real durante a auditoria (e revertido); sintaxe e referências conferidas por máquina.