Introduction
Composable architecture treats every business capability (catalog, cart, checkout, search, CMS, personalization) as an independent, replaceable component that communicates through well-defined APIs. Unlike monolithic suites where upgrading the CMS requires touching every other system, composable systems let teams swap, upgrade, or add components without cascading dependencies.
The MACH Alliance defines the technical foundation: Microservices, API-first, Cloud-native SaaS, and Headless. This guide provides concrete implementation patterns: a GraphQL federation layer that composes multiple backend APIs into a single endpoint, Kubernetes deployment of composed services with service mesh, and a complete e-commerce example demonstrating how catalog, cart, and checkout services compose together.
Architecture Overview
flowchart TD
subgraph Frontends["Headless Frontends"]
Web[Web App<br/>Next.js]
Mobile[Mobile App<br/>React Native]
POS[POS Terminal]
end
subgraph Composition["API Composition Layer"]
GQL[GraphQL Federation<br/>Apollo Router]
end
subgraph Services["Composable Services"]
Catalog[Catalog Service<br/>commercetools / Custom]
Cart[Cart Service<br/>Redis-backed]
Checkout[Checkout Service<br/>PCI-compliant]
CMS[CMS / Content<br/>Contentful / Strapi]
Search[Search Service<br/>Algolia / Meilisearch]
Personalize[Personalization<br/>Dynamic Yield / Custom]
end
subgraph Infrastructure["Cloud-Native Infrastructure"]
K8s[Kubernetes]
Mesh[Service Mesh<br/>Istio Ambient]
Obs[Observability<br/>Prometheus + Grafana]
end
Web --> GQL
Mobile --> GQL
POS --> GQL
GQL --> Catalog
GQL --> Cart
GQL --> Checkout
GQL --> CMS
GQL --> Search
GQL --> Personalize
Services --> K8s
K8s --> Mesh
K8s --> Obs
API Composition with GraphQL Federation
GraphQL Federation lets you compose multiple backend GraphQL services into a single unified graph. Each service owns its domain types and extends types from other services:
Catalog Service (Products)
# products.graphql — owned by catalog service
type Product @key(fields: "id") {
id: ID!
name: String!
price: Float!
currency: String!
category: String
inStock: Boolean
}
extend type Query {
products(category: String): [Product!]!
product(id: ID!): Product
}
Cart Service (extends Product type)
# cart.graphql — cart service extends Product with cart-specific data
type CartItem @key(fields: "id") {
id: ID!
productId: ID!
quantity: Int!
}
extend type Product @key(fields: "id") {
id: ID! @external
cartQuantity: Int @requires(fields: "id")
}
extend type Query {
cart(userId: ID!): [CartItem!]!
}
Apollo Router Configuration
# router.yaml — Apollo Federation Router
supergraph:
listen: 0.0.0.0:4000
cors:
origins:
- https://www.example.com
- https://admin.example.com
rhai:
scripts: ./rhai-scripts
main: main.rhai
headers:
all:
request:
- propagate:
named: "authorization"
# Rate limiting per subgraph
subgraphs:
products:
routing_url: http://catalog-service:4001/graphql
cart:
routing_url: http://cart-service:4002/graphql
checkout:
routing_url: http://checkout-service:4003/graphql
Example: E-Commerce Checkout Flow
A checkout transaction spans three services: cart (read items), catalog (get prices), and checkout (process payment). The frontend makes a single GraphQL query:
# Frontend calls the federation router with one query
mutation Checkout {
checkout {
createOrder(input: { paymentMethod: "card" }) {
orderId
total
items {
product { name price }
quantity
}
status
}
}
}
The router resolves this by calling cart → catalog → checkout in sequence, but the frontend sees only one round trip.
Kubernetes Deployment
Each composable service is an independently deployable Kubernetes workload:
# catalog-service.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: catalog-service
labels:
app: catalog
service: composable-commerce
spec:
replicas: 2
selector:
matchLabels:
app: catalog
template:
metadata:
labels:
app: catalog
spec:
containers:
- name: catalog
image: registry.example.com/catalog:v1.24.0
ports:
- containerPort: 4001
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: catalog-db
key: url
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /health
port: 4001
---
apiVersion: v1
kind: Service
metadata:
name: catalog-service
spec:
selector:
app: catalog
ports:
- port: 4001
targetPort: 4001
CI/CD Pipeline (GitHub Actions)
# .github/workflows/deploy-service.yml
name: Deploy Composable Service
on:
push:
paths:
- 'services/catalog/**'
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build and push Docker image
run: |
docker build -t registry.example.com/catalog:${{ github.sha }} ./services/catalog
docker push registry.example.com/catalog:${{ github.sha }}
- name: Deploy to Kubernetes
run: |
kubectl set image deployment/catalog-service \
catalog=registry.example.com/catalog:${{ github.sha }}
kubectl rollout status deployment/catalog-service
Each service is deployed independently. A cart update does not require redeploying the catalog or checkout services.
Composable vs Monolithic Decision Framework
| Factor | Monolithic Suite | Composable (MACH) |
|---|---|---|
| Deployment | One deploy affects everything | Independent per service |
| Upgrade risk | Full regression test needed | Test only changed service |
| Vendor lock-in | Single vendor ecosystem | Best-of-breed per capability |
| Time to market | Slower (coordinated releases) | Faster (independent teams) |
| Operational complexity | Lower (single system) | Higher (multiple services) |
| Best for | Small teams, simple requirements | Large teams, complex requirements |
Resources
- Apollo Federation Documentation — GraphQL API composition
- MACH Alliance — Microservices, API-first, Cloud-native, Headless standards
- Kubernetes Service Mesh (Istio) — Composable service networking
- commercetools Composable Commerce — Headless commerce platform
- Contentful Headless CMS — API-first content management
Comments