./read "Composable Architect..."

Composable Architecture:构建灵活可插拔的现代应用

By Bowen Xiong · GitHub · X · LinkedIn

composable_architecture:构建灵活可插.md2025-10-15
./meta --show-details
Published
2025年10月15日
Reading
25 min
Words
24,299
Status
PUBLISHED

目录

  1. 什么是可组合架构
  2. 核心原理与技术
  3. 应用场景
  4. 开发实战 Demo
  5. 主流工具与框架
  6. 最佳实践
  7. 挑战与解决方案

1. 什么是可组合架构

1.1 概念理解

Composable Architecture(可组合架构) 是一种将系统拆分为独立、可插拔组件的架构理念,每个组件都可以独立选择、替换和扩展,如同乐高积木一样自由组合。

简单类比:

  • 🧱 传统单体:一栋预制房屋(改造困难)
  • 🧩 可组合架构:乐高积木(自由组合、灵活替换)

1.2 MACH 架构原则

可组合架构通常遵循 MACH 四大原则:

原则英文说明示例
MMicroservices微服务化,独立部署订单服务、用户服务、支付服务
AAPI-firstAPI 优先设计所有功能通过 REST/GraphQL API 暴露
CCloud-native云原生,弹性扩展Kubernetes、Serverless
HHeadless前后端分离Headless CMS、Headless Commerce

1.3 传统单体 vs 可组合架构

传统单体架构:

┌─────────────────────────────────────────┐
│         单体应用                         │
│  ┌─────────┬─────────┬─────────────┐   │
│  │  前端   │  业务   │  数据库      │   │
│  │  模板   │  逻辑   │  (耦合)      │   │
│  └─────────┴─────────┴─────────────┘   │
└─────────────────────────────────────────┘

问题:
❌ 紧耦合:前端和后端绑定
❌ 难扩展:修改一个功能影响全局
❌ 技术锁定:难以更换技术栈

可组合架构:

┌──────────────────────────────────────────────────┐
│              前端层(可选择)                     │
│   Next.js / React / Vue / Mobile App            │
└──────────────────┬───────────────────────────────┘
                   │ API
        ┌──────────┴──────────┐
        │  API 网关 / GraphQL  │
        └──────────┬──────────┘
                   │
    ┌──────────────┼──────────────┬──────────────┐
    │              │              │              │
┌───▼───┐     ┌───▼───┐     ┌───▼───┐     ┌───▼───┐
│Headless│     │支付   │     │搜索   │     │分析   │
│  CMS   │     │服务   │     │服务   │     │服务   │
└───────┘     └───────┘     └───────┘     └───────┘

优势:
✅ 松耦合:每个组件独立
✅ 可替换:更换任一组件不影响其他
✅ 灵活性:自由选择最佳工具

对比表格:

特性传统单体可组合架构
灵活性❌ 低(紧耦合)✅ 高(可插拔)
上线速度❌ 慢(全量部署)✅ 快(独立部署)
技术栈❌ 锁定(单一技术)✅ 自由(多语言)
扩展性❌ 垂直扩展✅ 水平扩展
团队协作⚠️ 代码冲突多✅ 独立开发
学习曲线✅ 简单⚠️ 需要分布式思维

2. 核心原理与技术

2.1 Headless CMS(无头内容管理)

传统 CMS vs Headless CMS

传统 CMS(如 WordPress):

内容管理 + 前端渲染 = 一体化

问题:
❌ 前端绑定:只能用 WordPress 主题
❌ 多渠道困难:难以同步到 App/小程序
❌ 性能受限:服务端渲染性能差

Headless CMS(如 Strapi, Contentful):

内容管理(API)→ 任意前端

优势:
✅ 内容复用:一次编辑,多端使用
✅ 前端自由:React、Vue、Mobile App 任选
✅ 性能优化:静态生成、CDN 加速

示例对比:

// 传统 CMS(WordPress)
<?php
  the_title();  // PHP 模板绑定
  the_content();
?>

// Headless CMS(Strapi + Next.js)
export async function getStaticProps() {
  const posts = await fetch('https://cms.example.com/api/posts').then(r => r.json());
  return { props: { posts } };
}

function Blog({ posts }) {
  return posts.map(post => <Article key={post.id} {...post} />);
}

2.2 API-first 设计

核心理念:先设计 API,再实现功能

工作流程:

1. 设计 API 规范(OpenAPI/GraphQL Schema)
   ↓
2. 生成 API 文档
   ↓
3. 前后端并行开发(基于契约)
   ↓
4. 自动生成 SDK 和类型

示例(OpenAPI):

# openapi.yaml
openapi: 3.0.0
info:
  title: E-commerce API
  version: 1.0.0

paths:
  /products:
    get:
      summary: 获取产品列表
      parameters:
        - name: category
          in: query
          schema:
            type: string
      responses:
        '200':
          description: 成功
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Product'

components:
  schemas:
    Product:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        price:
          type: number

自动生成代码:

# 生成 TypeScript SDK
npx openapi-typescript-codegen --input openapi.yaml --output ./src/api

# 使用
import { ProductsService } from './api';

const products = await ProductsService.getProducts({ category: 'electronics' });

2.3 微前端(Micro Frontends)

概念:将前端应用拆分为多个独立的子应用

架构模式:

┌────────────────────────────────────────┐
│      主应用(Shell / Container)       │
├────────────────────────────────────────┤
│  ┌─────────┬─────────┬─────────────┐  │
│  │ 产品    │ 购物车  │  用户中心   │  │
│  │ (React) │ (Vue)   │  (Angular)  │  │
│  └─────────┴─────────┴─────────────┘  │
└────────────────────────────────────────┘

特点:
✅ 独立开发、独立部署
✅ 技术栈不受限
✅ 团队自治

实现方式:

方案原理优点缺点
iframe嵌入独立页面完全隔离、简单性能差、通信困难
Web Components自定义元素标准化、兼容好开发体验一般
Module FederationWebpack 5 特性共享依赖、性能好需要 Webpack 5+
Single-SPA框架编排器成熟、生态好配置复杂

3. 应用场景

3.1 最适合的场景

✅ 多端内容分发

CMS 内容 → Website
          → Mobile App
          → 小程序
          → 智能手表

示例:新闻网站、电商平台

✅ 大型组织/多团队

团队 A → 产品模块(React)
团队 B → 订单模块(Vue)
团队 C → 用户模块(Angular)

各团队独立开发、部署,互不影响

✅ 技术栈迁移

旧系统(jQuery) → 逐步替换 → 新系统(React)

微前端方式逐步迁移,降低风险

✅ 高频实验/A/B 测试

快速替换组件进行测试:
支付流程 V1 → 支付流程 V2(无需重新部署整个应用)

✅ 白标产品(多租户)

相同功能 + 不同品牌/UI:

核心功能(共享)
  ↓
品牌 A UI  品牌 B UI  品牌 C UI

3.2 不太适合的场景

❌ 小型单一应用

  • 博客、个人网站(过度设计)

❌ 团队规模小

  • 1-3 人团队(维护成本高于收益)

❌ 强一致性要求

  • 复杂事务、强依赖场景

4. 开发实战 Demo

4.1 项目:构建可组合电商应用

我们将组合以下服务:

  • Headless CMS(产品内容)
  • 支付 API(Stripe)
  • 搜索服务(Algolia)
  • 前端(Next.js)

4.2 技术栈

mkdir composable-ecommerce && cd composable-ecommerce
npm init -y
npm install next react react-dom
npm install @stripe/stripe-js stripe
npm install algoliasearch
npm install swr axios

4.3 核心代码实现

1. 项目结构

composable-ecommerce/
├── pages/
│   ├── index.js          # 首页
│   ├── products/
│   │   └── [id].js       # 产品详情
│   └── api/
│       ├── products.js   # 产品 API(聚合多个服务)
│       └── checkout.js   # 支付 API
├── services/
│   ├── cms.js            # CMS 服务
│   ├── search.js         # 搜索服务
│   └── payment.js        # 支付服务
└── components/
    ├── ProductCard.js
    └── SearchBar.js

2. CMS 服务(services/cms.js)

// services/cms.js - 可替换的 CMS 服务
class CMSService {
  constructor(config) {
    this.baseURL = config.baseURL;
    this.apiKey = config.apiKey;
  }

  async getProducts() {
    // 示例:使用 Strapi / Contentful / Sanity
    // 这里用模拟数据演示可替换性
    return [
      {
        id: '1',
        name: 'iPhone 15 Pro',
        price: 7999,
        description: '最新款 iPhone',
        image: 'https://example.com/iphone.jpg',
        category: 'electronics'
      },
      {
        id: '2',
        name: 'MacBook Pro',
        price: 14999,
        description: 'M3 芯片',
        image: 'https://example.com/macbook.jpg',
        category: 'computers'
      }
    ];
  }

  async getProduct(id) {
    const products = await this.getProducts();
    return products.find(p => p.id === id);
  }
}

// 工厂模式:轻松替换 CMS
export function createCMSService(provider = 'strapi') {
  const configs = {
    strapi: {
      baseURL: process.env.STRAPI_URL,
      apiKey: process.env.STRAPI_API_KEY
    },
    contentful: {
      baseURL: process.env.CONTENTFUL_SPACE_ID,
      apiKey: process.env.CONTENTFUL_ACCESS_TOKEN
    },
    sanity: {
      baseURL: process.env.SANITY_PROJECT_ID,
      apiKey: process.env.SANITY_DATASET
    }
  };

  return new CMSService(configs[provider]);
}

export default createCMSService();

3. 搜索服务(services/search.js)

// services/search.js - 可替换的搜索服务
import algoliasearch from 'algoliasearch';

class SearchService {
  constructor(config) {
    this.client = config.client;
    this.index = config.index;
  }

  async search(query, options = {}) {
    const results = await this.index.search(query, {
      hitsPerPage: options.limit || 10,
      page: options.page || 0
    });

    return results.hits;
  }

  async facetedSearch(query, facets = {}) {
    return await this.index.search(query, {
      facetFilters: Object.entries(facets).map(([key, value]) =>
        `${key}:${value}`
      )
    });
  }
}

// 工厂模式:支持多种搜索服务
export function createSearchService(provider = 'algolia') {
  if (provider === 'algolia') {
    const client = algoliasearch(
      process.env.ALGOLIA_APP_ID,
      process.env.ALGOLIA_API_KEY
    );
    const index = client.initIndex('products');

    return new SearchService({ client, index });
  }

  // 可以添加其他搜索服务
  // if (provider === 'elasticsearch') { ... }
  // if (provider === 'meilisearch') { ... }

  throw new Error(`Unsupported search provider: ${provider}`);
}

export default createSearchService();

4. 支付服务(services/payment.js)

// services/payment.js - 可替换的支付服务
import Stripe from 'stripe';

class PaymentService {
  constructor(config) {
    this.stripe = config.client;
  }

  async createPaymentIntent(amount, currency = 'cny') {
    const paymentIntent = await this.stripe.paymentIntents.create({
      amount: amount * 100, // 转换为分
      currency,
      automatic_payment_methods: {
        enabled: true,
      },
    });

    return {
      clientSecret: paymentIntent.client_secret,
      paymentIntentId: paymentIntent.id
    };
  }

  async confirmPayment(paymentIntentId) {
    const paymentIntent = await this.stripe.paymentIntents.retrieve(
      paymentIntentId
    );

    return paymentIntent.status === 'succeeded';
  }
}

// 工厂模式:支持多种支付服务
export function createPaymentService(provider = 'stripe') {
  if (provider === 'stripe') {
    const client = new Stripe(process.env.STRIPE_SECRET_KEY);
    return new PaymentService({ client });
  }

  // 可以添加其他支付服务
  // if (provider === 'paypal') { ... }
  // if (provider === 'alipay') { ... }

  throw new Error(`Unsupported payment provider: ${provider}`);
}

export default createPaymentService();

5. API 聚合层(pages/api/products.js)

// pages/api/products.js - 聚合多个服务
import cmsService from '../../services/cms';
import searchService from '../../services/search';

export default async function handler(req, res) {
  const { method, query } = req;

  try {
    if (method === 'GET') {
      // 1. 从 CMS 获取产品数据
      let products = await cmsService.getProducts();

      // 2. 如果有搜索关键词,使用搜索服务
      if (query.q) {
        const searchResults = await searchService.search(query.q);
        const searchIds = searchResults.map(r => r.objectID);

        products = products.filter(p => searchIds.includes(p.id));
      }

      // 3. 分类过滤
      if (query.category) {
        products = products.filter(p => p.category === query.category);
      }

      // 4. 价格排序
      if (query.sort === 'price_asc') {
        products.sort((a, b) => a.price - b.price);
      } else if (query.sort === 'price_desc') {
        products.sort((a, b) => b.price - a.price);
      }

      res.status(200).json({
        success: true,
        data: products,
        meta: {
          total: products.length,
          services: {
            cms: 'strapi',
            search: query.q ? 'algolia' : 'none'
          }
        }
      });
    } else {
      res.status(405).json({ error: 'Method not allowed' });
    }
  } catch (error) {
    console.error('API Error:', error);
    res.status(500).json({ error: error.message });
  }
}

6. 支付 API(pages/api/checkout.js)

// pages/api/checkout.js
import paymentService from '../../services/payment';
import cmsService from '../../services/cms';

export default async function handler(req, res) {
  if (req.method !== 'POST') {
    return res.status(405).json({ error: 'Method not allowed' });
  }

  try {
    const { productId, quantity = 1 } = req.body;

    // 1. 从 CMS 获取产品信息
    const product = await cmsService.getProduct(productId);

    if (!product) {
      return res.status(404).json({ error: 'Product not found' });
    }

    // 2. 计算总价
    const amount = product.price * quantity;

    // 3. 创建支付意图
    const { clientSecret, paymentIntentId } = await paymentService.createPaymentIntent(
      amount
    );

    res.status(200).json({
      success: true,
      data: {
        clientSecret,
        paymentIntentId,
        amount,
        product: {
          id: product.id,
          name: product.name,
          quantity
        }
      }
    });
  } catch (error) {
    console.error('Checkout Error:', error);
    res.status(500).json({ error: error.message });
  }
}

7. 前端首页(pages/index.js)

// pages/index.js
import { useState } from 'react';
import useSWR from 'swr';

const fetcher = (url) => fetch(url).then(r => r.json());

export default function Home() {
  const [searchQuery, setSearchQuery] = useState('');
  const [category, setCategory] = useState('');

  // 使用 SWR 进行数据获取(自动缓存、重新验证)
  const { data, error, isLoading } = useSWR(
    `/api/products?q=${searchQuery}&category=${category}`,
    fetcher
  );

  const handleCheckout = async (productId) => {
    const response = await fetch('/api/checkout', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ productId, quantity: 1 })
    });

    const result = await response.json();

    if (result.success) {
      // 跳转到 Stripe 支付页面
      alert(`支付金额:¥${result.data.amount}`);
      console.log('Client Secret:', result.data.clientSecret);
    }
  };

  return (
    <div style={{ padding: '20px', maxWidth: '1200px', margin: '0 auto' }}>
      <h1>🧩 可组合电商演示</h1>

      {/* 搜索栏 */}
      <div style={{ marginBottom: '20px' }}>
        <input
          type="text"
          placeholder="搜索产品..."
          value={searchQuery}
          onChange={(e) => setSearchQuery(e.target.value)}
          style={{
            padding: '10px',
            width: '300px',
            marginRight: '10px',
            border: '1px solid #ddd'
          }}
        />

        <select
          value={category}
          onChange={(e) => setCategory(e.target.value)}
          style={{ padding: '10px', border: '1px solid #ddd' }}
        >
          <option value="">所有分类</option>
          <option value="electronics">电子产品</option>
          <option value="computers">电脑</option>
        </select>
      </div>

      {/* 服务信息 */}
      {data?.meta && (
        <div style={{
          background: '#f0f0f0',
          padding: '10px',
          marginBottom: '20px',
          borderRadius: '5px'
        }}>
          <strong>🔧 使用的服务:</strong>
          CMS: {data.meta.services.cms} |
          Search: {data.meta.services.search}
        </div>
      )}

      {/* 产品列表 */}
      {isLoading && <p>加载中...</p>}
      {error && <p>加载失败:{error.message}</p>}

      <div style={{
        display: 'grid',
        gridTemplateColumns: 'repeat(auto-fill, minmax(250px, 1fr))',
        gap: '20px'
      }}>
        {data?.data?.map(product => (
          <div key={product.id} style={{
            border: '1px solid #ddd',
            padding: '15px',
            borderRadius: '8px'
          }}>
            <img
              src={product.image}
              alt={product.name}
              style={{ width: '100%', height: '200px', objectFit: 'cover' }}
            />
            <h3>{product.name}</h3>
            <p style={{ color: '#666' }}>{product.description}</p>
            <p style={{ fontSize: '24px', fontWeight: 'bold' }}>
              ¥{product.price}
            </p>
            <button
              onClick={() => handleCheckout(product.id)}
              style={{
                width: '100%',
                padding: '10px',
                background: '#007bff',
                color: 'white',
                border: 'none',
                borderRadius: '5px',
                cursor: 'pointer'
              }}
            >
              立即购买
            </button>
          </div>
        ))}
      </div>
    </div>
  );
}

8. 环境配置(.env.local)

# CMS
STRAPI_URL=https://your-strapi.com
STRAPI_API_KEY=your_api_key

# 搜索
ALGOLIA_APP_ID=your_app_id
ALGOLIA_API_KEY=your_api_key

# 支付
STRIPE_SECRET_KEY=sk_test_your_key
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_your_key

4.4 运行 Demo

# 启动开发服务器
npm run dev

# 访问 http://localhost:3000

4.5 架构优势演示

替换 CMS(只需修改一行代码):

// services/cms.js
- export default createCMSService('strapi');
+ export default createCMSService('contentful');

// 整个应用无需其他改动!

替换搜索服务:

// services/search.js
- export default createSearchService('algolia');
+ export default createSearchService('elasticsearch');

替换支付服务:

// services/payment.js
- export default createPaymentService('stripe');
+ export default createPaymentService('paypal');

5. 主流工具与框架

5.1 Headless CMS

CMS类型特点适用场景Star
Strapi开源自托管Node.js、完全可定制中小型项目、私有部署⭐ 60k+
ContentfulSaaS企业级、多语言、GraphQL大型企业、多渠道商业
SanitySaaS + 开源实时协作、结构化内容内容团队协作⭐ 5k+
Payload CMS开源TypeScript、代码优先开发者友好⭐ 18k+
PrismicSaaS切片内容、可视化编辑营销网站商业

5.2 API 网关

工具特点适用场景
GraphQL (Apollo)灵活查询、类型安全、单一端点复杂数据聚合
tRPCTypeScript 端到端类型安全全栈 TypeScript 应用
Kong开源、插件丰富、高性能企业级微服务
AWS API GatewayServerless、AWS 集成云原生应用

5.3 微前端框架

框架原理优点Star
Single-SPA路由驱动成熟、生态好、框架无关⭐ 13k+
Module FederationWebpack 5共享依赖、性能好内置
qiankun基于 Single-SPA开箱即用、中文友好⭐ 15k+
Module FederationWebpack 5+运行时共享、性能优⭐ 2k+

6. 最佳实践

6.1 服务边界划分

✅ 好的划分(按业务能力):

产品服务:产品 CRUD、库存管理
订单服务:订单创建、状态流转
用户服务:认证、个人信息
支付服务:支付、退款

❌ 不好的划分(按技术层):

数据库服务
API 服务
UI 服务

问题:业务逻辑分散,难以维护

6.2 API 设计规范

RESTful API:

GET    /api/products          // 列表
GET    /api/products/:id      // 详情
POST   /api/products          // 创建
PUT    /api/products/:id      // 更新
DELETE /api/products/:id      // 删除

// 关联资源
GET    /api/products/:id/reviews
POST   /api/products/:id/reviews

GraphQL(更灵活):

query {
  product(id: "123") {
    id
    name
    price
    reviews {
      rating
      comment
    }
  }
}

6.3 错误处理与降级

// API 聚合时的降级策略
async function getProductWithRecommendations(id) {
  try {
    const [product, recommendations] = await Promise.allSettled([
      cmsService.getProduct(id),           // 核心数据
      recommendationService.getRelated(id) // 辅助数据
    ]);

    return {
      product: product.status === 'fulfilled' ? product.value : null,
      recommendations: recommendations.status === 'fulfilled'
        ? recommendations.value
        : [] // 推荐失败不影响主流程
    };
  } catch (error) {
    // 核心数据失败才返回错误
    throw error;
  }
}

6.4 性能优化

并行请求:

// ❌ 串行(慢)
const product = await cmsService.getProduct(id);
const reviews = await reviewService.getReviews(id);
const stock = await inventoryService.getStock(id);

// ✅ 并行(快)
const [product, reviews, stock] = await Promise.all([
  cmsService.getProduct(id),
  reviewService.getReviews(id),
  inventoryService.getStock(id)
]);

缓存策略:

import { unstable_cache } from 'next/cache';

export const getCachedProducts = unstable_cache(
  async () => {
    return await cmsService.getProducts();
  },
  ['products'],
  {
    revalidate: 3600, // 1 小时
    tags: ['products']
  }
);

7. 挑战与解决方案

7.1 常见挑战

挑战问题解决方案
服务依赖服务 A 依赖服务 BAPI Gateway、服务网格
数据一致性跨服务事务Saga 模式、事件溯源
复杂度增加微服务数量爆炸合理划分、服务编排
调试困难分布式追踪OpenTelemetry、Jaeger
前端重复代码多个前端重复逻辑共享组件库、BFF 层

7.2 服务通信

同步通信(REST/GraphQL):

// 优点:简单直观
// 缺点:耦合、阻塞

const user = await fetch('/api/users/123').then(r => r.json());

异步通信(消息队列):

// 优点:解耦、高可用
// 缺点:最终一致性

await eventBus.publish('order.created', orderData);

7.3 版本管理

API 版本化:

/api/v1/products  (旧版本)
/api/v2/products  (新版本,兼容并存)

向后兼容:

// 新字段可选
{
  "name": "iPhone",
  "price": 7999,
  "currency": "CNY"  // 新增字段(可选)
}

总结

核心要点

  1. 可组合性:独立、可插拔、可替换
  2. API 优先:先设计 API,再实现功能
  3. Headless:内容与展示分离
  4. 灵活性:自由选择最佳工具

适合你吗?

✅ 适合使用可组合架构的情况:

  • 多端内容分发
  • 大型组织/多团队
  • 需要快速实验迭代
  • 技术栈灵活性要求高

❌ 暂时不适合的情况:

  • 小型单一应用
  • 团队规模小(< 3 人)
  • 强一致性事务要求

学习路径

  1. 理解概念:MACH 架构、Headless
  2. 动手实践:完成本文 Demo
  3. 选择工具:Strapi、Contentful、Sanity
  4. 深入模式:微前端、API Gateway
  5. 生产部署:监控、版本管理、降级

推荐资源

官方文档:

开源项目:

学习课程:


可组合架构让你的系统像乐高一样灵活!开始构建你的可组合应用吧!🧩

comments.logDiscussion Thread
./comments --show-all

讨论区

./loading comments...