Slide 1

Slide 1 text

Schema Design for E-Commerce Replacing Transactions with MongoDB

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

Schema for e-commerce

Slide 4

Slide 4 text

ERD

Slide 5

Slide 5 text

E-Commerce schemas

Slide 6

Slide 6 text

Checkout • Collect data (shipping, billing) • Make sure cart information is accurate (prices) • Check if stock is accurate • Finalise order

Slide 7

Slide 7 text

Finalise order • Data becomes immutable • Shipping data must remain same • Billing data must remain same • Product data must remain same

Slide 8

Slide 8 text

Let’s fill a cart • Create Order in “cart” state • Add OrderItem with product information and quantity • Add OrderItemUnit

Slide 9

Slide 9 text

Change the cart • Create or remove OrderItemUnit • Update quantity and totals in OrderItem • Update quantity and totals in Order • Recompute promotions and adjustments (e.g. shipping)

Slide 10

Slide 10 text

Work with carts START TRANSACTION; INSERT INTO sylius_order (id, customer, items_total) VALUES (1, 45, 2998); INSERT INTO sylius_order_item (id, order_id, variant, price, quantity) VALUES (1, 1, 1381, 1499, 2); INSERT INTO sylius_order_item_unit (id, order_item_id, shipped) VALUES ('1381-1', 1, false), ('1381-2', 1, false); COMMIT;

Slide 11

Slide 11 text

Increase quantity START TRANSACTION; INSERT INTO sylius_order_item_unit VALUES ('1381-3', 1, false); UPDATE sylius_order_item SET quantity = 3 WHERE id = 1; UPDATE sylius_order SET items_total = 4497; COMMIT;

Slide 12

Slide 12 text

Decrease quantity START TRANSACTION; DELETE FROM sylius_order_item_unit WHERE id = '1381-3'; UPDATE sylius_order_item SET quantity = quantity - 1 WHERE id = 1; UPDATE sylius_order SET items_total = items_total - 1499; COMMIT;

Slide 13

Slide 13 text

The Transaction is a LIE

Slide 14

Slide 14 text

You don’t NEED transactions

Slide 15

Slide 15 text

Denormalise your schema

Slide 16

Slide 16 text

Cart { "id": 123, "customer": 45, "state": "cart", "items": [ { "variant": 1381, "price": 1499, "quantity": 2, "units": [ { "id": "1381-1", "shipped": false }, { "id": "1381-2", "shipped": false } ] } ], "items_total": 2998 }

Slide 17

Slide 17 text

Order { "id": 123, "customer": { "id": 45, "name": "Andreas Braun", "shippingAddress": { "address": "", "postCode": "", "city": "", "country": "Germany" }, "billingAddress": { "address": "", "postCode": "", "city": "", "country": "Germany" } }, "state": "new", "items": [ { "variant": 1381, "price": 1499, "quantity": 2, "units": [ { "id": "1381-1", "shipped": true }, { "id": "1381-2", "shipped": true } ] } ], "items_total": 2998, "payments": [ { "amount": 2998, "state": "completed", "payment_method": { "type": "credit_card", "details": { "cardHolder": "Andreas Braun", "creditCard": "xxxxxxxxxxxx1234", "expiration": "09/24", "gateway": "easy-payment-service", "identifier": "afae87a0" } }, "billingAddress": { "address": "", "postCode": "", "city": "", "country": "Germany" } } ], "shipments": [ { "units": ["1381-1", "1381-2"], "state": "registered", "shipment_method": { "type": "dhl", "trackingNumber": "12345678a" }, "shippingAddress": { "address": "", "postCode": "", "city": "", "country": "Germany" }, "history": [ { "date": "2019-11-21T14:23:04+01:00", "state": "registered" } ] } ] }

Slide 18

Slide 18 text

Order - Customer { "customer": { "id": 45, "name": "Andreas Braun", "shippingAddress": { "address": "", "postCode": "", "city": "", "country": "Germany" }, "billingAddress": { "address": "", "postCode": "", "city": "", "country": "Germany" } } }

Slide 19

Slide 19 text

Order - Payments { "payments": [ { "amount": 2998, "state": "completed", "payment_method": { "type": "credit_card", "details": { "cardHolder": "Andreas Braun", "creditCard": "xxxxxxxxxxxx1234", "expiration": "09/24", "gateway": "easy-payment-service", "identifier": "afae87a0" } }, "billingAddress": { "address": "", "postCode": "", "city": "", "country": "Germany" } } ] }

Slide 20

Slide 20 text

Order - Shipments { "shipments": [ { "units": ["1381-1", "1381-2"], "state": "registered", "shipment_method": { "type": "dhl", "trackingNumber": "12345678a" }, "shippingAddress": { "address": "", "postCode": "", "city": "", "country": "Germany" }, "history": [ { "date": "2019-11-21T14:23:04+01:00", "state": "registered" } ] } ] }

Slide 21

Slide 21 text

Use MongoDB (yes, it’s ACID compliant)

Slide 22

Slide 22 text

Create cart db.order.insert({ id: 123, customer: 45, state: "cart", items: [ { variant: 1381, price: 1499, quantity: 2, units: [ { id: "1381-1", shipped: true }, { id: "1381-2", shipped: true } ] } ], items_total: 2998 });

Slide 23

Slide 23 text

Increase quantity db.order.updateOne( { id: 123 }, { $inc: { 'items.0.quantity': 1, 'items_total': 1499 }, $push: { 'items.0.units': { id: "1381-3", shipped: false } } } );

Slide 24

Slide 24 text

Decrease quantity db.order.updateOne( { id: 123 }, { $inc: { 'items.0.quantity': -1, 'items_total': -1499 }, $pull: { 'items.0.units': { id: "1381-3" } } } );

Slide 25

Slide 25 text

Use Doctrine MongoDB ODM It’s like ORM, but for MongoDB

Slide 26

Slide 26 text

What if I can’t?

Slide 27

Slide 27 text

JSON columns! CREATE TABLE sylius_order ( id INT PRIMARY KEY NOT NULL, state CHAR(20) NOT NULL DEFAULT 'cart', items_total INT NOT NULL DEFAULT 0, customer JSON, items JSON );

Slide 28

Slide 28 text

SQL with JSON INSERT INTO sylius_order (id, state, items_total, customer, items) VALUES ( 1, 'cart', 2998, '{ "id": 45 }', '[ { "variant": 1381, "price": 1499, "quantity": 2, "units": [ { "id": "1381-1", "shipped": false }, { "id": "1381-2", "shipped": false } ] } ]' );

Slide 29

Slide 29 text

Increase quantity UPDATE sylius_order SET items_total = 4497, items = JSON_ARRAY_APPEND( JSON_SET(items, '$[0].quantity', 3), '$[0].units', CAST('{ "id": "1381-3", "shipped": false }' AS JSON) ) ;

Slide 30

Slide 30 text

New cart { "id": 123, "customer": 45, "state": "cart", "items": [ { "variant": 1381, "price": 1499, "quantity": 3, "units": [ { "id": "1381-1", "shipped": false }, { "id": "1381-2", "shipped": false }, { "id": "1381-3", "shipped": false } ] } ], "items_total": 4497 }

Slide 31

Slide 31 text

Decrease quantity UPDATE sylius_order SET items_total = 2998, items = JSON_REMOVE( JSON_SET(items, '$[0].quantity', 2), '$[0].units[2]' ) ;

Slide 32

Slide 32 text

Cart { "id": 123, "customer": 45, "state": "cart", "items": [ { "variant": 1381, "price": 1499, "quantity": 2, "units": [ { "id": "1381-1", "shipped": false }, { "id": "1381-2", "shipped": false } ] } ], "items_total": 2998 }

Slide 33

Slide 33 text

Think outside the box table

Slide 34

Slide 34 text

Think outside the box table

Slide 35

Slide 35 text

Thanks! @alcaeus github.com/alcaeus symfony-devs: @alcaeus