Skip to content

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

Next.jsReactTypeScriptGraphQLApolloCommerce ToolsClerkRabbitMQDockerKubernetesTailwind CSS
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

Next.jsReactTypeScriptGraphQLApolloCommerce ToolsClerkRabbitMQDockerKubernetesTailwind CSS

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.