Case study 01
E-commerce data
Shopify to data warehouse: a sync that survives being re-run
The problem
A retail analytics platform needed sales data from merchants’ e-commerce and POS systems — Shopify, Clover, Qu, Poynt — landing in one warehouse schema its reporting and ML layer could query. Each platform models orders, refunds, payments and inventory differently. Every merchant needed their own set of tables. And the whole thing had to run unattended.
The constraint
The hard requirement wasn’t throughput, it was correctness under repetition. Syncs get interrupted. Cron overlaps itself. A merchant reinstalls the app. Someone re-runs yesterday’s job by hand. Every one of those has to end with the same rows in the warehouse — not duplicates, not gaps, not silent drift that surfaces three weeks later in a report nobody trusts any more.
What I built
An embedded Shopify app handling OAuth and the install/uninstall lifecycle, feeding a multi-tenant ETL service in PHP 8 and PostgreSQL.
- Per-merchant provisioning — a registry entry plus DDL for 24 tables, including fact tables of up to 130 columns, created from nothing on first install
- A sync engine built around idempotency: per-vertical transactions, commit before marker, watermarks with a min-rule, lease locks so two runs can’t overlap, tombstones for deletes, and delete-then-insert rather than blind appends
- Data pulled in-memory over the Shopify GraphQL Admin API and mapped straight through, with no intermediate raw database to drift out of date
- A cron scheduler that returns honest exit codes, and a read-only ops endpoint exposing per-action health
The outcome
The pipeline is verified by a drill, not by hope: install, sync, uninstall, reinstall, deliberately drop the tables, re-provision, refill — and the resulting facts come out byte-identical to the snapshot taken before. A merchant onboarded from a completely clean slate has all 24 tables created and filled in about a minute. 46 automated tests cover the mapping and sync contracts.
What it means for a client
If you have platform data that needs to land somewhere reliable, the interesting work isn’t the happy path — it’s what happens on the fifth re-run at 3am. That’s the part I build for.