Aisle Commerce
A multi-tenant storefront platform — twelve bounded-context services behind a host-resolving gateway that signs every internal request, with an event bus that fails loudly rather than dropping payments.
- 12
- Domain services
- 8
- Domain events
- HMAC
- Internal request auth
- SSR
- Storefront rendering
- Role
- Author
- Year
- 2026
- Status
- Private
- Type
- Multi-tenant commerce SaaS
01Overview
Aisle Commerce is a SaaS platform where each merchant gets their own storefront on their own domain. It is a Node monorepo: an Express SSR web app, an API gateway, twelve domain services, and a shared runtime package the services all build on.
The gateway is the interesting component. A request arrives and it decides whether the host is a platform host or a storefront host, asks store-service to resolve the domain, verifies the caller's token, then builds signed internal headers carrying request, actor and store context before proxying to the right service. Every downstream service validates that HMAC signature before it trusts any of it.
Services split by bounded context and each own their persistence: user, store, compliance, customer, product, cart, order, payment, billing, support, chat and notification, on ports 4101 through 4112.
02The problem
Multi-tenancy is where commerce platforms leak. Every query, every cache key and every file path has to carry a tenant, and the moment one service trusts a tenant identifier that arrived in a header from the internet, one merchant can read another's orders.
The usual answer is to pass a tenant ID between services and hope nothing forges it. That works until a service is exposed directly, or an internal network stops being trustworthy, and then the tenant boundary was never real.
The second problem is events. Order confirmation depends on payment success arriving. A bus that silently drops a PAYMENT_SUCCEEDED event produces paid orders that never confirm and inventory that stays reserved forever — a failure nobody notices until reconciliation.
03Architecture
- 01
Gateway as the only trusted boundary
Host resolution, token verification and internal header signing all happen in one place. Services never parse a client token; they verify an HMAC signature from the gateway. Tenant context becomes something a service can prove rather than something it has to trust.
- 02
Twelve services, one shared runtime
packages/shared carries environment loading, database bootstrap, internal auth signing, JWT handling, HTTP helpers, logging and event delivery. Each service owns its domain and its data; none of them reimplements the plumbing.
- 03
Events that refuse to disappear
RabbitMQ is optional. Without it the shared bus falls back to a Redis-backed durable queue with retry and dead-letter handling. If neither can accept an event, publishing fails loudly rather than dropping it — the correct behaviour when the event is PAYMENT_SUCCEEDED.
- 04
The flows that matter are event-driven
USER_REGISTERED provisions a trial subscription in billing-service. PAYMENT_SUCCEEDED confirms the order and commits reserved inventory; PAYMENT_FAILED marks it failed and releases the reservation. Inventory is reserved at checkout and only committed on payment, so a failed card does not sell stock that is still on the shelf.
- 05
Compliance as its own context
KYC, KYB, document handling and the review workflow live in compliance-service rather than being scattered through onboarding. A merchant's verification state is one service's answer, not a boolean copied into four tables.
04Key decisions
Sign internal requests, do not trust the network
Signed internal headers cost a verification on every hop and remove an entire class of tenant-confusion bug. Given that the thing being isolated is one merchant's revenue from another's, that is a trade worth making before it is needed rather than after.
SSR rather than a single-page storefront
Storefronts live or die on how fast the first product page paints and how well it indexes. Express SSR gives both without shipping a client framework to a shopper on a phone.
Optional broker, mandatory delivery
Requiring RabbitMQ locally would make the platform painful to run; dropping events when it is absent would make it dangerous in production. A Redis fallback with dead-lettering keeps development easy and keeps production honest.
Documented gaps
The repository carries a gap document alongside the architecture and API docs, and the service inventory marks status per component. Knowing which consumers are still reserved for future use is part of knowing what the system does.
05Stack
- Node.js
- Express
- MySQL
- Redis
- RabbitMQ
- Socket.IO
- JWT
- HMAC