Skip to main content

Composable Architecture: Building Flexible Enterprise Systems with MACH and API-First Design

Created: March 18, 2026 Larry Qu 4 min read

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

Comments

Share this article

Scan to read on mobile

👍 Was this article helpful?