Flagship system
CommerceOS
Multi-Tenant Commerce · Architecture
A multi-tenant, headless commerce platform whose backend is deliberately vertical-neutral — the same engine powers a fashion boutique, a grocery, a pharmacy or a B2B wholesaler, with each store's identity expressed as data rather than code.
- 11
- Technologies
- 2
- Domains
- 6
- Capabilities
- —
- Public links
Who it's for
Businesses of any vertical — fashion, grocery, pharmacy, B2B wholesale — that need a real commerce backend without building one from scratch.
Where it fits
As the headless engine behind one store or a whole portfolio of tenants: one deployment, many isolated storefronts, any frontend.
The hard part
The problem
Commerce backends usually hardcode the business type — a grocery engine, a fashion engine, a pharmacy engine. Adding a new vertical means forking the code and threading business-type conditionals through the domain, which rots fast.
Key constraints
The core had to stay strictly vertical-neutral: never a single business-type conditional branch anywhere in the backend. Independent stores had to run on one deployment without ever seeing each other's data, and prices had to resolve correctly per market — not just per currency.
Architecture overview
Clean Architecture with CI-enforced dependency boundaries. The domain models the commerce network (tenant → catalogue → category → product → variant) with dynamic attributes and pluggable product-type strategies. Every external integration — payment, email, storage, search — sits behind an abstract service with swappable adapters resolved per tenant, so the core depends only on interfaces.
One engine · isolated tenants
every tenant isolated at the query layer — one engine, zero data bleed
How it works
What's actually in it.
- 01Multi-tenant isolation enforced at the query layer — every tenant-scoped row carries a tenant identifier.
- 02Catalogue engine end to end: products, a variant engine with an enforced generation ceiling, dynamic attributes, brands, collections, tags and SEO / JSON-LD / sitemap generation.
- 03Global-market pricing engine with a batched, N+1-safe read path folding price into product detail and variants.
- 04Ports-and-adapters for Stripe/PayPal, SMTP/SES, Local/S3, Postgres-FTS/OpenSearch, resolved per tenant.
- 05API-first: standard response envelope, OpenAPI schema and versioned REST contract so the UI can be replaced with zero backend change.
- 06Clean Architecture with import-linter-enforced boundaries, strict mypy with zero suppressions, UUIDv7 keys and feature flags.
Engineering decisions
Market modelled as distinct from currency
Naïvely, price is 'amount + currency'. But the same currency can span markets with different pricing, and a market can be served without a locked local price. CommerceOS models market as a first-class concept with explicit locked prices, an FX reference-currency fallback and a deterministic resolver that never borrows a price between markets.
The trade-off
Modelling market separately from currency adds concepts and a resolver most shops never need. For a single-country store it is overkill — but it is the difference between a platform that expands globally and one that has to be re-architected the first time it does.
The obvious approach
Build the store for its first vertical and add a `business_type` flag when a second one appears.
Why I didn't choose it
Every flag multiplies conditional branches across the domain; the code becomes a maze of 'if pharmacy… else grocery…' that no one can safely change.
My approach
Express identity as data and configuration — dynamic attributes and pluggable product-type strategies — so a new vertical is a configuration change, never a code fork.
Trade-offs
More upfront abstraction and discipline (import-linter, strict mypy) than a hardcoded store, repaid the first time a genuinely different vertical ships without touching the core.
Technical highlights
- A new vertical is a configuration change, not a code fork.
- Per-tenant adapter resolution keeps the core dependent only on interfaces.
- Governance via a constitution, ADRs and per-phase validation reports.
Claims & evidence
No business-type conditional branch exists anywhere in the backend — vertical identity is data, not code.
Qualitative · CommerceOS design constraint
Every statement above carries its evidence class — nothing is invented.
Tech stack
- Python 3.12
- Django 5.2
- Django REST Framework
- Django Channels
- Celery
- Redis
- PostgreSQL
- Next.js 15
- TypeScript
- TanStack Query
- Docker
Want to go deeper on CommerceOS?
I'm happy to walk through the architecture and the decisions behind it.