A reverse-Taobao operator rarely sells on one surface. The same Guochao sneaker might need to live on a self-hosted Shopify store, a Coupang listing for Korean buyers, a WooCommerce site, and a Base storefront — all at once. The moment you maintain those listings by hand, they drift: one price updated here, a sold-out flag forgotten there, and a customer orders something you can no longer fulfill.
This is a short engineering note on how we keep one product catalog coherent across four very different channel APIs.
The core problem: four schemas, one source of truth
Each platform models a "product" differently. Shopify wants variants with inventory_item_ids; Coupang expects Korean-locale titles and its own vendorId; Woo uses post meta; Base has its own listing shape. None of them should own the truth. We keep a single internal product record and treat each channel as a projection.
A plain event log, not direct writes
We never call four APIs in a row from the admin save handler. Instead, an admin edit emits one domain event: ProductUpdated { sku, fields }. A worker consumes it and fans out to each channel adapter. If Coupang is down, the event stays in the queue; the other three still get updated. Retries are per-channel, so one flaky API can't block the rest.
Adapters translate, they don't decide
Each channel adapter has one job: map our canonical product to that platform's schema. The Shopify adapter sets price and inventory_quantity; the Coupang adapter translates the title and maps our sku to its sellerProductId. Crucially, the adapter does not decide what to sync — only how. Business rules (which fields are channel-specific, which are global) live in one policy module.
Idempotent upserts by external id
Every push is keyed by the platform's external listing id we stored at creation time. Re-running the same event updates the same listing instead of creating a duplicate. This matters more than people expect: a double-delivered event used to spawn ghost listings until we made the upsert strictly idempotent on the external id.
Stock as a derived value, not a thing you set
We don't push "stock = 12" to four places. We push the source inventory change, and each adapter computes the channel-appropriate quantity (some channels want total, some want available-after-reserved). When a Taocarts order reserves stock, the same event lowers the number everywhere within seconds.
What we'd do differently
Early on we let each channel store its own copy of the price, and reconciling them became a nightly cron nightmare. Moving price to the source record and making channels read-only projections removed an entire class of "why are these different" tickets.
If you're selling across multiple storefronts, resist the urge to manage each one. Own one catalog, project it outward, and let events — notManual copy-paste — keep them aligned.
Tags: ecommerce, webdev, automation, programming
Top comments (0)