Composable ecommerce
Armi
Technical leadership on a multi-commerce platform: Commerce Tools as the engine, Checkout and Merchant Center as Next.js products, domain kept at the edges.
Fullstack Technical Lead · Armirene · January 2024 — March 2026
- Commerce engine
- Commerce Tools
- Product surfaces
- Checkout + Merchant Center
- Identity
- Clerk
Overview
Armi is a multi-commerce ecommerce platform. Commerce Tools holds catalog, cart, customers, and orders. The products people use are Checkout and Merchant Center — Next.js applications with TypeScript, Apollo/GraphQL, and a shared UI kit.
The work was to lead those surfaces technically: keep the commerce engine from owning the application model, migrate legacy apps toward a cleaner checkout and admin, and make delivery repeatable with Docker and Kubernetes.
Checkout v2 models money, currency, address, and payment method as domain values — not as leftover form fields scattered through React state.
Problem
A composable commerce stack fails when every screen talks to Commerce Tools in its own dialect. Catalog, checkout, and admin then encode three different orders, three different customers, and no place to change a rule once.
Legacy Merchant Center and Checkout accumulated GraphQL, context, and vendor SDK calls until the purchase path and the operations path could not evolve independently.
Partners and multiple commerces (including ARMI Partners) need isolation at the commerce-id boundary. Treating “the store” as a global singleton does not survive a second tenant.
Constraints
Catalog, price, cart, and order writes that matter commercially go through Commerce Tools. Custom types and attributes (for example customer marketing preferences) have to exist in each CT project — merchant-center, checkout, and environments are not interchangeable.
Identity is Clerk. Merchant-center users resolve against Commercetools custom fields (clerkId) and, when enabled, an Armi user service. The application cannot pretend CT is the identity provider.
Operational events — such as relaunching an order from Merchant Center — need a fact on the bus (RabbitMQ), not a silent admin mutation, if downstream systems are to see what happened.
Architecture
Storefront checkout and Merchant Center are separate Next.js applications. Both speak GraphQL/Apollo toward Commerce Tools and internal services. Clean architecture keeps use cases and domain values independent of the CT SDK.
Checkout v2 is where that rule is most visible: Money, Currency, Address, and PaymentMethod are modeled in the domain, then adapted to Commerce Tools. Replacing a payment integration should not rewrite the cart.
A merchant-service owns commerce records (partners, commerce ids). Docker and Kubernetes are the delivery substrate — not the architecture.
Architecture
Armi — composable commerce
Checkout
Merchant Center
Next.js
Apollo / GraphQL
Domain
Money, order, address
Commerce Tools
Clerk
Merchant service
RabbitMQ
Order events
Docker
Kubernetes
Checkout and Merchant Center as product edges. Commerce Tools owns commercial state. Clerk owns identity. RabbitMQ carries operational facts such as order relaunch.
Key decisions
Personal experience
Commerce Tools as the engine, not the application
CT holds commercial state. Checkout and Merchant Center hold the product. Vendor types stay in adapters so a custom field change in CT does not rewrite the purchase model.
Personal experience
Checkout v2 as a domain, not a form wizard
Money, currency, address, and payment method are values the domain can stand behind. That is what makes tax, shipping, and payment integrations replacements instead of rewrites.
Personal experience
Separate Merchant Center and Checkout
Operations and purchase do not share a release train or a UI shell. They share contracts and the commerce engine. Legacy apps in armi/apps/ are the system being left behind.
Personal experience
Emit facts when operations change an order
Relaunching an order from Merchant Center publishes an event so traceability is not trapped in an admin click. Competing consumers only help if that work is partitionable.
Trade-offs
Gained
A commerce engine that can serve more than one storefront, and product apps that can change without waiting on a monolith deploy. Payment and identity stay replaceable at the adapter.
Sacrificed
Every custom attribute must exist in each Commerce Tools project. Environment drift (dev / staging / prod) becomes a product bug. GraphQL convenience can hide how many CT calls a page actually makes.
Personal experience
Technologies
Results
Checkout and Merchant Center are the product edges on Commerce Tools, with Clerk for identity and a merchant-service for commerce/partner records.
Checkout v2 carries an explicit domain for money and payment instead of a single form state tree. Operational actions such as order relaunch leave an event, not only a mutation.
Conversion and latency numbers are not published here.
Lessons learned
Composable commerce fails at the edges: payments, inventory, identity, and the custom types you forgot to create in staging. Treat those edges as adapters with contracts.
A GraphQL page that fans out to Commerce Tools is still a latency budget. The query is not the architecture.
Kubernetes does not fix a checkout that cannot explain its own money. Model the purchase first; schedule the process second.